fix(security): validate Host header to defeat DNS rebinding (daily review 2026-07-31) - #56
Open
nithiink wants to merge 1 commit into
Open
fix(security): validate Host header to defeat DNS rebinding (daily review 2026-07-31)#56nithiink wants to merge 1 commit into
nithiink wants to merge 1 commit into
Conversation
In tokenless local mode the backend trusts any loopback socket peer, and require_auth only consults the Origin allowlist when an Origin header is present. Same-origin GET/EventSource requests carry no Origin, and no Host allowlist existed, so a DNS-rebinding page (evil.com -> 127.0.0.1) could read /debug/stream and /debug/recent — leaking session transcripts, tool arguments, and file contents. Add config.host_allowed() (loopback + private-LAN + VC_ALLOWED_HOSTS, override via VC_ALLOWED_HOST_REGEX) and enforce it in require_auth and _ws_access_ok, unconditionally (independent of the Origin header). The browser sets Host from the connection name and page JS cannot forge it, so a rebound public name is rejected while genuine localhost/LAN access passes. Refs #55 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmQcs7se3WnceoJYXwjnvY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the single confirmed finding from the daily security review. Fixes #55.
Finding addressed
M1 (Medium) — DNS-rebinding bypass of loopback-only auth (missing Host-header validation)
In the default tokenless local mode,
_access_oktrusts any client whose socket peer is loopback, andrequire_authonly consults the Origin allowlist when an Origin header is present (main.py:212-214). Same-originGET/EventSourcerequests carry no Origin, and no Host-header allowlist existed anywhere, so a DNS-rebinding page (evil.com → 127.0.0.1) could reach the backend over loopback and read/debug/stream+/debug/recent, leaking session transcripts, tool-call arguments, and file contents surfaced in tool results.Fix (minimal, targeted)
host_allowed()— allowlists loopback + private-LAN Host headers (any port), plus exactVC_ALLOWED_HOSTS; regex overridable viaVC_ALLOWED_HOST_REGEX; rejects a missing/empty Hostbackend/config.pyhost_allowed()inrequire_auth, unconditionally (not gated on the Origin header, since the rebinding vector is Origin-less)backend/main.py(require_auth)host_allowed()in_ws_access_ok(WebSocket handshake, close code4403)backend/main.py(_ws_access_ok)backend/tests/test_security_hardening.pyWhy it holds: the browser sets the
Hostheader from the connection's name and page JS cannot override it (Hostis a forbidden header), so a rebound public name likeevil.com:8000is rejected while genuinelocalhost/127.0.0.1/ private-LAN access passes. This is the standard DNS-rebinding defense and does not rely on the Origin header being present.No legitimate flow is affected: the Next
/api/*proxy calls the backend atlocalhost:8000; native/plugin clients connect tolocalhost:8000; phone/LAN in network mode connects to the private-LAN IP — all covered by the allowlist. Custom DNS hostnames get theVC_ALLOWED_HOSTSescape hatch, mirroring the existingVC_ALLOWED_ORIGINSdesign.Verification
host_allowed()logic validated against 16 host cases (loopback/LAN → allow; public/link-local/suffix-injection/empty → reject) — all pass.HostHeaderAllowlistunit tests in the style of the existing suite.unittestdiscovery in this environment is blocked by an unrelated missing dependency,claude_agent_sdk, which is pre-existing and not touched here; the new tests import onlyconfig.)🤖 Generated with Claude Code
https://claude.ai/code/session_01GmQcs7se3WnceoJYXwjnvY
Generated by Claude Code