From 2390728cc38b1236279820971439e74f4d88b8ff Mon Sep 17 00:00:00 2001 From: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:12:18 +0300 Subject: [PATCH] fix: resolve 4 bugs found in HA integration code review - Auto-authorize HA events in gateway (system-generated, not user messages) - Guard _read_events against None/closed WebSocket after failed reconnect - Use UUID for send() message_id instead of polluting WS sequence counter - entity_id parameter now takes precedence over data["entity_id"] --- gateway/platforms/homeassistant.py | 5 ++++- gateway/run.py | 6 ++++++ tests/tools/test_homeassistant_tool.py | 6 +++--- tools/homeassistant_tool.py | 5 +++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/gateway/platforms/homeassistant.py b/gateway/platforms/homeassistant.py index 749cdf1e..08dfa099 100644 --- a/gateway/platforms/homeassistant.py +++ b/gateway/platforms/homeassistant.py @@ -17,6 +17,7 @@ import json import logging import os import time +import uuid from datetime import datetime from typing import Any, Dict, List, Optional, Set @@ -228,6 +229,8 @@ class HomeAssistantAdapter(BasePlatformAdapter): async def _read_events(self) -> None: """Read events from WebSocket until disconnected.""" + if self._ws is None or self._ws.closed: + return async for ws_msg in self._ws: if ws_msg.type == aiohttp.WSMsgType.TEXT: try: @@ -390,7 +393,7 @@ class HomeAssistantAdapter(BasePlatformAdapter): timeout=aiohttp.ClientTimeout(total=10), ) as resp: if resp.status < 300: - return SendResult(success=True, message_id=str(self._next_id())) + return SendResult(success=True, message_id=uuid.uuid4().hex[:12]) else: body = await resp.text() return SendResult(success=False, error=f"HTTP {resp.status}: {body}") diff --git a/gateway/run.py b/gateway/run.py index 76ed3666..198629ce 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -490,6 +490,12 @@ class GatewayRunner: 4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true) 5. Default: deny """ + # Home Assistant events are system-generated (state changes), not + # user-initiated messages. The HASS_TOKEN already authenticates the + # connection, so HA events are always authorized. + if source.platform == Platform.HOMEASSISTANT: + return True + user_id = source.user_id if not user_id: return False diff --git a/tests/tools/test_homeassistant_tool.py b/tests/tools/test_homeassistant_tool.py index 6235474e..b57df069 100644 --- a/tests/tools/test_homeassistant_tool.py +++ b/tests/tools/test_homeassistant_tool.py @@ -130,13 +130,13 @@ class TestBuildServicePayload: payload = _build_service_payload() assert payload == {} - def test_data_does_not_overwrite_entity_id(self): + def test_entity_id_param_takes_precedence_over_data(self): payload = _build_service_payload( entity_id="light.a", data={"entity_id": "light.b"}, ) - # data.update overwrites entity_id set earlier - assert payload["entity_id"] == "light.b" + # explicit entity_id parameter wins over data["entity_id"] + assert payload["entity_id"] == "light.a" # --------------------------------------------------------------------------- diff --git a/tools/homeassistant_tool.py b/tools/homeassistant_tool.py index 4a01382f..b351cfec 100644 --- a/tools/homeassistant_tool.py +++ b/tools/homeassistant_tool.py @@ -106,10 +106,11 @@ def _build_service_payload( ) -> Dict[str, Any]: """Build the JSON payload for a HA service call.""" payload: Dict[str, Any] = {} - if entity_id: - payload["entity_id"] = entity_id if data: payload.update(data) + # entity_id parameter takes precedence over data["entity_id"] + if entity_id: + payload["entity_id"] = entity_id return payload