Skip to content

Require a local Host for loopback trust (DNS rebinding) - #24

Open
Deep-Sixed wants to merge 5 commits into
mainfrom
claude/sleepy-curie-g8429q-loopback-host
Open

Deep-Sixed wants to merge 5 commits into
mainfrom
claude/sleepy-curie-g8429q-loopback-host

Conversation

@Deep-Sixed

Copy link
Copy Markdown
Owner

Problem

admin_gate / admin_denied admit any loopback peer as an admin, and token-less mode opens inference to it, based on the peer address alone. A hostile page open in a local operator's browser can rebind its own hostname to 127.0.0.1. Its requests then come from loopback, count as same-origin, and can send X-Cerberus-CSRF. That is enough to read the active config and events, activate or roll back revisions, run provider probes, and use the open inference endpoint.

Change

  • Loopback trust now also requires the Host header to name this machine: localhost, *.localhost, 127.0.0.0/8 or ::1.
  • Operators can add other names local clients use via the new server.trusted_hosts setting (empty by default).
  • This applies to the admin gate and to token-less inference.
  • Tests switch their base URL from http://test to http://localhost. This is mechanical; test behavior is unchanged.
  • SECURITY.md explains the rule and warns against a same-host reverse proxy that forwards to 127.0.0.1 with a loopback Host.

Tests

  • New tests/test_loopback_host.py covers header parsing, a rebound Host being refused for admin reads, mutations and token-less inference, and local Hosts still being accepted.
  • Full suite: 336 passed. Ruff is clean.
  • Run locally on Python 3.13.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ


Generated by Claude Code

Loopback peers were admitted as admins, and could use token-less
inference, on peer address alone. A hostile page in a local operator's
browser can rebind its hostname to 127.0.0.1: its requests then come
from loopback, are same-origin, and can send X-Cerberus-CSRF, so it could
read the active config, activate or roll back revisions, and spend
provider quota.

Loopback trust now also requires the Host header to name this machine
(localhost, *.localhost, 127.0.0.0/8, ::1) or a name the operator lists
in server.trusted_hosts. Tests move from Host "test" to "localhost".
SECURITY.md notes the same-host reverse proxy case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ
claude added 4 commits October 5, 2026 14:00
Merging main brought in tests/test_jev_router_dispatch.py, whose client
used Host "t". Loopback trust now also requires a local Host, so the
test client moves to localhost like every other test in this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ
Merging main brought in tests/test_jev_dispatch.py, whose client used
Host "t". Loopback trust now also requires a local Host, so the test
client moves to localhost like every other test in this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lj4neZ1qzw7tpiCJ8oedxJ
@Deep-Sixed
Deep-Sixed marked this pull request as ready for review October 11, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants