"""Exempt loopback addresses (127.0.0.1 / ::1) from Home Assistant's HTTP login-ban handling. Home Assistant has no built-in way to whitelist an IP from the ``http`` ban system. Local tools running on the same host (e.g. a VS Code session that queries ``/api/config`` without a token) therefore show up as failed login attempts from ``127.0.0.1`` and can eventually get localhost banned, which would break legitimate local access. This integration wraps ``process_wrong_login`` so that any request whose remote address is a loopback address is silently ignored. External addresses are still processed normally, so ``login_attempts_threshold`` protection stays intact. """ from __future__ import annotations import logging from ipaddress import ip_address from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) DOMAIN = "loopback_ban_exempt" # Modules that hold a reference to process_wrong_login (either the definition # itself or an ``from ... import process_wrong_login``). All must be patched. _TARGET_MODULES = ( "homeassistant.components.http.ban", "homeassistant.components.auth.login_flow", "homeassistant.components.websocket_api.auth", ) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Patch the ban handler to skip loopback addresses.""" from homeassistant.components.http import ban as ban_module original = ban_module.process_wrong_login async def process_wrong_login(request): """Ignore loopback, otherwise defer to the original handler.""" remote = getattr(request, "remote", None) try: if remote is not None and ip_address(remote).is_loopback: return None except ValueError: pass return await original(request) patched = 0 for mod_path in _TARGET_MODULES: try: module = __import__(mod_path, fromlist=["process_wrong_login"]) except ImportError: continue if getattr(module, "process_wrong_login", None) is not None: module.process_wrong_login = process_wrong_login patched += 1 _LOGGER.info( "Loopback ban exemption active (patched %s reference(s))", patched ) return True