Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API -- JSON parsing logic bug on POST endpoints #134323

Open
GrandMoff100 opened this issue Dec 31, 2024 · 1 comment · May be fixed by #134326
Open

REST API -- JSON parsing logic bug on POST endpoints #134323

GrandMoff100 opened this issue Dec 31, 2024 · 1 comment · May be fixed by #134326

Comments

@GrandMoff100
Copy link

The problem

Hello! I came across a bug in the home assistant REST API that happens when you send a string in quotes in the request body. This affects POST /api/template and POST /api/state. Homeassistant responds with a 500 code because it expects a dictionary json but it doesn't check for that precisely, because strings are getting parsed as valid json by aiohttp.

You can reproduce the same error on both endpoints with the following snippets.
To reproduce the error on POST /api/template

import os
import requests


token = os.environ["HOMEASSISTANTAPI_TOKEN"]  # i.e. eyBlahBlah...
url = os.environ["HOMEASSISTANTAPI_URL"]  # i.e. http://localhost:8123/api

try:
    response = requests.post(
        url + "/template",
        headers={"Authorization": f"Bearer {token}"},
        json="'{\"template\": \"{{ 1 + 1 }}\"}'",
    )
    assert response.status_code == 200
except AssertionError:
    print(response.text)

which outputs

500 Internal Server Error

Server got itself in trouble

The corresponding stack track in HA core is:

server-1  | 2024-12-31 01:43:48.012 ERROR (MainThread) [aiohttp.server] Error handling request
server-1  | Traceback (most recent call last):
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_protocol.py", line 480, in _handle_request
server-1  |     resp = await request_handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_app.py", line 569, in _handle
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_middlewares.py", line 117, in impl
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 92, in security_filter_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 83, in forwarded_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 26, in request_context_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 86, in ban_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 242, in auth_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 32, in headers_middleware
server-1  |     response = await handler(request)
server-1  |                ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/helpers/http.py", line 73, in handle
server-1  |     result = await handler(request, **request.match_info)
server-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/decorators.py", line 81, in with_admin
server-1  |     return await func(self, request, *args, **kwargs)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/api/__init__.py", line 482, in post
server-1  |     tpl = _cached_template(data["template"], request.app[KEY_HASS])
server-1  |                            ~~~~^^^^^^^^^^^^
server-1  | TypeError: string indices must be integers, not 'str'

Second, to reproduce the error on POST /api/states

import os
import requests

token = os.environ["HOMEASSISTANTAPI_TOKEN"]  # i.e. eyBlahBlah...
url = os.environ["HOMEASSISTANTAPI_URL"]  # i.e. http://localhost:8123/api

try:
    response = requests.post(
        url + "/states/sun.my_colorful_sun",
        headers={"Authorization": f"Bearer {token}"},
        json='"{"state": "colorful as always"}"',
    )
    assert response.status_code == 200
except AssertionError:
    print(response.text)
500 Internal Server Error

Server got itself in trouble

The corresponding HA core stack trace:

server-1  | 2024-12-31 01:59:17.268 ERROR (MainThread) [aiohttp.server] Error handling request
server-1  | Traceback (most recent call last):
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_protocol.py", line 480, in _handle_request
server-1  |     resp = await request_handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_app.py", line 569, in _handle
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/local/lib/python3.13/site-packages/aiohttp/web_middlewares.py", line 117, in impl
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 92, in security_filter_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 83, in forwarded_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 26, in request_context_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 86, in ban_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 242, in auth_middleware
server-1  |     return await handler(request)
server-1  |            ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 32, in headers_middleware
server-1  |     response = await handler(request)
server-1  |                ^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/helpers/http.py", line 73, in handle
server-1  |     result = await handler(request, **request.match_info)
server-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1  |   File "/usr/src/homeassistant/homeassistant/components/api/__init__.py", line 268, in post
server-1  |     if (new_state := data.get("state")) is None:
server-1  |                      ^^^^^^^^
server-1  | AttributeError: 'str' object has no attribute 'get'

What version of Home Assistant Core has the issue?

core-2024.12.5

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant Core

Integration causing the issue

api

Link to integration documentation on our website

https://www.home-assistant.io/integrations/api/

Diagnostics information

No response

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

My HA core installation is the HA core docker image, but I don't think that's too relevant.

The parsing logic is correct in POST /api/event by checking for isinstance(event_data, dict). Specifically at https://github.com/home-assistant/core/blob/dev/homeassistant/components/api/__init__.py#L337.

This issue might also affect POST /api/services/<domain>/<service> because https://github.com/home-assistant/core/blob/bf59241dabf93da9929709fc0c5a866e2c0b2790/homeassistant/components/api/__init__.py#L385C9-L390C14 doesnt check for isinstance(data, dict) either. But since its possible for services to accept strings in addition to dicts, I'm not sure if thats an issue yet.

@home-assistant
Copy link

Hey there @home-assistant/core, mind taking a look at this issue as it has been labeled with an integration (api) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of api can trigger bot actions by commenting:

  • @home-assistant close Closes the issue.
  • @home-assistant rename Awesome new title Renames the issue.
  • @home-assistant reopen Reopen the issue.
  • @home-assistant unassign api Removes the current integration label and assignees on the issue, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

(message by CodeOwnersMention)


api documentation
api source
(message by IssueLinks)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant