{"record":{"id":"bc85aafb4bba933a","repo":"headroomlabs-ai/headroom","slug":"not-found-bc85aa","errorCode":null,"errorMessage":"Not Found","messagePattern":"Not Found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"headroom/proxy/server.py","lineNumber":3415,"sourceCode":"        return JSONResponse(status_code=200, content=payload)\n\n    # Loopback-only debug introspection (Unit 5). A remote IP gets 404 —\n    # debug endpoints are invisible to external scanners.\n    from headroom.proxy.debug_introspection import (\n        collect_tasks as _collect_tasks,\n    )\n    from headroom.proxy.loopback_guard import require_loopback as _require_loopback\n    from headroom.proxy.loopback_guard import require_same_origin as _require_same_origin\n\n    def _require_loopback_or_trusted_dashboard_client(request: Request) -> None:\n        \"\"\"Allow loopback callers, or gateway-forwarded dashboard clients.\n\n        Mirrors the trust chain already used by /stats and /stats-lifetime\n        (see _request_can_view_dashboard_metadata) so the settings UI works\n        the same way behind a reverse-proxy/gateway (issue #2466).\n        \"\"\"\n        if not _request_can_view_dashboard_metadata(request, trusted_dashboard_client_cidrs):\n            raise HTTPException(status_code=404)\n\n    def _require_same_origin_or_trusted_dashboard_client(request: Request) -> None:\n        \"\"\"Same-origin CSRF guard for settings writes, trusted-dashboard aware.\n\n        ``require_same_origin`` only accepts an ``Origin`` that itself names a\n        loopback host, so a browser POST from a trusted-gateway dashboard\n        client was rejected even though the paired GET routes allow that same\n        caller (issue #2466). For non-loopback callers, accept an ``Origin``\n        that matches this request's own Host header, provided the caller is\n        already an IP-literal-Host, CIDR-trusted dashboard client. Loopback\n        callers keep the stricter loopback-only origin check unchanged.\n        \"\"\"\n        if not _request_is_loopback(request):\n            origin = request.headers.get(\"origin\")\n            host_header = request.headers.get(\"host\")\n            if (\n                origin\n                and origin != \"null\"","sourceCodeStart":3397,"sourceCodeEnd":3433,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/server.py#L3397-L3433","documentation":"Dashboard-adjacent protected routes deliberately return HTTP 404 'Not Found' when the caller is neither loopback nor a trusted dashboard client, mirroring the trust chain of /stats and /stats-lifetime. The 404 hides existence of settings endpoints from untrusted networks rather than revealing authorization state (issue #2466 context).","triggerScenarios":"Hitting a settings/metadata route from a non-loopback address whose IP is not within trusted_dashboard_client_cidrs; missing or wrong X-Forwarded-For behind a reverse proxy so classification fails.","commonSituations":"Dashboard behind a reverse proxy without configuring trusted CIDRs; accessing the proxy from LAN; curl without expected forwarding headers.","solutions":["Call the endpoint from 127.0.0.1/::1 for local use.","Configure the trusted dashboard client CIDR env/settings so the gateway-forwarded client IP is recognized.","Verify X-Forwarded-For handling matches the documented proxy setup."],"exampleFix":"# before\ncurl http://<lan-host>:<port>/v1/settings  # 404\n\n# after\n# run locally\ncurl http://127.0.0.1:<port>/v1/settings\n# or configure trusted dashboard client CIDRs before proxying","handlingStrategy":"validation","validationCode":"import ipaddress, os\ndef client_trusted(ip: str) -> bool:\n    addr = ipaddress.ip_address(ip)\n    return addr.is_loopback or any(\n        addr in ipaddress.ip_network(c)\n        for c in os.environ.get(\"HEADROOM_TRUSTED_DASHBOARD_CLIENT_CIDRS\", \"\").split(\",\")\n        if c.strip()\n    )","typeGuard":null,"tryCatchPattern":"resp = await client.get(url)\nif resp.status_code == 404:\n    treat_as_unauthorized_or_missing(resp)  # do not blindly retry","preventionTips":["Bind locally for single-user use.","Behind a proxy, configure trusted CIDRs and correct forwarded headers.","Treat 404 on these routes as a trust failure signal."],"tags":["http","authorization","network"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}