diag: refuse pprof requests DNS rebinding can forge - #299
Merged
Conversation
dinstein
force-pushed
the
fix/diag-rebinding-defense
branch
from
August 10, 2026 03:59
f89db62 to
48e8ee0
Compare
dinstein
marked this pull request as ready for review
August 10, 2026 03:59
diag.Serve bound loopback-only and refused a non-loopback ADDRESS, but the pprof mux had no request-level check. Loopback binding stops the network, not a local browser under DNS rebinding: a page served from evil.example:PORT whose name is rebound to 127.0.0.1 reads as same-origin to the browser and can read /debug/pprof/heap, which carries downstream credentials and tool payloads — a worse payload than the httpbridge attack this mirrors, whose checkOrigin (internal/httpbridge/ingress.go) already documents the mechanism. requestGuard wraps the mux and fails closed on two counts: any Origin header refuses the request outright (there is no browser client here, so unlike the bridge no Origin is ever legitimate), and the Host header must independently prove loopback via netguard.AddrIsLoopback, since under rebinding Host carries the attacker's chosen name. AddrIsLoopback already handles both a bare host and a host:port, so no normalization was needed. Tests cover an Origin equal to Host (the exact rebound shape), a non-loopback Host, and both curl-shaped forms (with and without a port). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
internal/gateway already had TestRunRefusesNonLoopbackProfilingAddr proving the wiring reaches Run rather than stopping inside internal/diag; the daemon's matching refusal (daemon.go, profiling armed before the slow part of startup) had no such test even though the reasoning is identical. TestDaemonRefusesNonLoopbackProfilingAddr calls daemon.Run directly with AGENTHUB_PPROF_ADDR set to a non-loopback address and asserts the error is diag.ErrNotLoopback before OnReady would ever fire. The existing testResolver helper made this cheap — no new harness needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
foundation.md's pprof section and security.md's AddrIsLoopback section both described only the bind-time loopback check, which the previous commit extended with a request-level guard. Both now name requestGuard, its fail-closed direction (any Origin refused, Host must independently prove loopback), and point at internal/httpbridge/ingress.go's checkOrigin for the rebinding mechanism rather than re-deriving it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dinstein
force-pushed
the
fix/diag-rebinding-defense
branch
from
August 10, 2026 04:06
48e8ee0 to
302aee0
Compare
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.
Loopback binding stops the network from reaching
internal/diag's pprof endpoint, but not a localbrowser under DNS rebinding: a page served from
evil.example:PORTwhose name is rebound to127.0.0.1is same-origin from the browser's point of view and can read/debug/pprof/heap, whichcarries downstream credentials and tool payloads.
internal/httpbridge/ingress.go'scheckOriginalready documents and defends against this exact attack one package over;
diag.Serve's mux had noequivalent.
fix(diag): add a request-level guard (any Origin refused, Host must prove loopback) plus teststest(daemon): matching test for the daemon's fatal refusal of a non-loopbackAGENTHUB_PPROF_ADDRdocs(diag): document the request-level defense in the package doc, foundation.md, and security.mdVerification
make ci(build + test + lint) is green, includinginternal/diag,internal/daemon, and thefull suite.
test/e2e's docker-runtime tests were not touched by this change and were not run inthis session's flaky window.