headroomlabs-ai/headroom · error · HTTPException
Not Found
Error message
Not Found
What it means
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).
Source
Thrown at headroom/proxy/server.py:3415
return JSONResponse(status_code=200, content=payload)
# Loopback-only debug introspection (Unit 5). A remote IP gets 404 —
# debug endpoints are invisible to external scanners.
from headroom.proxy.debug_introspection import (
collect_tasks as _collect_tasks,
)
from headroom.proxy.loopback_guard import require_loopback as _require_loopback
from headroom.proxy.loopback_guard import require_same_origin as _require_same_origin
def _require_loopback_or_trusted_dashboard_client(request: Request) -> None:
"""Allow loopback callers, or gateway-forwarded dashboard clients.
Mirrors the trust chain already used by /stats and /stats-lifetime
(see _request_can_view_dashboard_metadata) so the settings UI works
the same way behind a reverse-proxy/gateway (issue #2466).
"""
if not _request_can_view_dashboard_metadata(request, trusted_dashboard_client_cidrs):
raise HTTPException(status_code=404)
def _require_same_origin_or_trusted_dashboard_client(request: Request) -> None:
"""Same-origin CSRF guard for settings writes, trusted-dashboard aware.
``require_same_origin`` only accepts an ``Origin`` that itself names a
loopback host, so a browser POST from a trusted-gateway dashboard
client was rejected even though the paired GET routes allow that same
caller (issue #2466). For non-loopback callers, accept an ``Origin``
that matches this request's own Host header, provided the caller is
already an IP-literal-Host, CIDR-trusted dashboard client. Loopback
callers keep the stricter loopback-only origin check unchanged.
"""
if not _request_is_loopback(request):
origin = request.headers.get("origin")
host_header = request.headers.get("host")
if (
origin
and origin != "null"View on GitHub (pinned to 322425c43b)
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.
Example fix
# before curl http://<lan-host>:<port>/v1/settings # 404 # after # run locally curl http://127.0.0.1:<port>/v1/settings # or configure trusted dashboard client CIDRs before proxying
Defensive patterns
Strategy: validation
Validate before calling
import ipaddress, os
def client_trusted(ip: str) -> bool:
addr = ipaddress.ip_address(ip)
return addr.is_loopback or any(
addr in ipaddress.ip_network(c)
for c in os.environ.get("HEADROOM_TRUSTED_DASHBOARD_CLIENT_CIDRS", "").split(",")
if c.strip()
) Try / catch
resp = await client.get(url)
if resp.status_code == 404:
treat_as_unauthorized_or_missing(resp) # do not blindly retry Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Dashboard behind a reverse proxy without configuring trusted CIDRs; accessing the proxy from LAN; curl without expected forwarding headers.
Related errors
- upstream request failed: {0}
- Copilot token exchange failed with HTTP {exc.code}: {body}
- token endpoint returned HTTP {e.code}
- invalid upstream URL: {0}
- metrics response build error: {e}
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/bc85aafb4bba933a.
Report an issue: GitHub.