mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-21 22:54:00 +08:00
fix: Fail-closed pinned resolver, close session on HA stop, drop dead workflow step
- _PinnedResolver raises on unpinned hosts instead of falling back to live DNS: nothing legitimate resolves other hosts on a pinned session (redirects are off), so fail closed - dedicated session is also closed on EVENT_HOMEASSISTANT_CLOSE: core stop does not unload entries, and the raw ClientSession has no HA auto-cleanup, which left 'Unclosed client session' logs at shutdown - hassfest.yaml: remove the version-print step, hassfest runs inside the action's docker image and is never on the runner PATH
This commit is contained in:
@@ -16,7 +16,7 @@ import asyncio
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME, EVENT_HOMEASSISTANT_CLOSE
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -379,6 +379,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Register update listener for options changes
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
# HA Core stop does not unload entries, so close the dedicated
|
||||
# session on the CLOSE event too; unload removes this listener
|
||||
# and closes the session via APIClient.shutdown() instead.
|
||||
async def _async_close_session_on_stop(_event) -> None:
|
||||
if not session.closed:
|
||||
await session.close()
|
||||
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_CLOSE, _async_close_session_on_stop
|
||||
)
|
||||
)
|
||||
|
||||
_LOGGER.debug("Setup completed for %s", instance_name)
|
||||
|
||||
return True
|
||||
|
||||
@@ -17,7 +17,6 @@ from urllib.parse import urlparse
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.abc import AbstractResolver
|
||||
from aiohttp.resolver import DefaultResolver
|
||||
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -86,10 +85,8 @@ class _PinnedResolver(AbstractResolver):
|
||||
def __init__(
|
||||
self,
|
||||
pinned: dict[str, list[tuple[str, int]]],
|
||||
fallback: AbstractResolver | None = None,
|
||||
) -> None:
|
||||
self._pinned = pinned
|
||||
self._fallback = fallback or DefaultResolver()
|
||||
|
||||
async def resolve(
|
||||
self,
|
||||
@@ -99,7 +96,11 @@ class _PinnedResolver(AbstractResolver):
|
||||
) -> list[dict[str, Any]]:
|
||||
entries = self._pinned.get(host.lower())
|
||||
if entries is None:
|
||||
return await self._fallback.resolve(host, port, family)
|
||||
# Every request on a pinned session must target the validated
|
||||
# host. Resolving anything else means a request escaped the pin
|
||||
# (a new call site or a config bug) — fail closed rather than
|
||||
# fall back to live, unvalidated DNS.
|
||||
raise OSError(f"Refusing to resolve unpinned host: {host}")
|
||||
return [
|
||||
{
|
||||
"hostname": host,
|
||||
@@ -113,7 +114,7 @@ class _PinnedResolver(AbstractResolver):
|
||||
]
|
||||
|
||||
async def close(self) -> None:
|
||||
await self._fallback.close()
|
||||
"""Nothing to close; the resolver holds only the pinned map."""
|
||||
|
||||
def _family_for(ip: str) -> int:
|
||||
"""Return AF_INET or AF_INET6 based on the IP literal."""
|
||||
|
||||
Reference in New Issue
Block a user