fix(http): validate incoming Host/Origin and outbound fetch targets - #33
Draft
reneaaron wants to merge 1 commit into
Draft
fix(http): validate incoming Host/Origin and outbound fetch targets#33reneaaron wants to merge 1 commit into
reneaaron wants to merge 1 commit into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
reneaaron
force-pushed
the
fix/http-request-validation
branch
3 times, most recently
from
August 25, 2026 09:29
ce6fdda to
7dceef2
Compare
Add a hostOriginGuard middleware to the HTTP endpoints (/mcp, /sse, /messages). It accepts a request only if its Host header is allowlisted and its Origin, when present, is explicitly permitted. The default allowlist is loopback only; ALLOWED_HOSTS replaces it and ALLOWED_ORIGINS permits browser origins. Clients that send no Origin header at all, which is every non-browser MCP client, are unaffected. Bind the HTTP listener to loopback by default instead of every interface, so it is not reachable from the rest of the network. BIND_HOST opts a deployment back into binding all interfaces. Validate the destination of every fetch_l402 request: only http(s) URLs that resolve exclusively to globally-routable addresses are fetched, so loopback, link-local, RFC1918, CGNAT and IPv6 local ranges are refused. The hostname is resolved rather than pattern-matched, so a public name pointing at a private address is refused too. Redirects are not followed, since a validated URL could otherwise redirect to a private one and each hop would be a separate L402 challenge paid without the user seeing it. Set ALLOWED_HOSTS and BIND_HOST on the Fly deployment so mcp.getalby.com keeps working.
reneaaron
force-pushed
the
fix/http-request-validation
branch
from
August 25, 2026 21:21
7dceef2 to
c9e0982
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.
Summary
Tightens what the HTTP transport accepts as an incoming request, and what
fetch_l402is willing to reach on the way out.Changes
hostOriginGuardmiddleware (src/security.ts), applied to all HTTP endpoints (/mcp,/sse,/messages). A request is accepted only if itsHostheader is allowlisted and itsOrigin, when present, is explicitly permitted. The default allowlist is loopback only;ALLOWED_HOSTSreplaces it,ALLOWED_ORIGINSpermits browser origins and is empty by default. MCP clients that send noOriginheader (i.e. everything that isn't a browser) are unaffected. 403 responses name the variable to set.assertPublicUrldestination check infetch_l402. Onlyhttp(s)URLs whose hostname resolves exclusively to globally-routable addresses are fetched; loopback, link-local, RFC1918, CGNAT and IPv6 local/ULA ranges are refused, and IPv4-mapped IPv6 is unwrapped before classification. Range classification usesipaddr.js(already present transitively viaexpress→proxy-addr) rather than string matching, so obfuscated forms likehttp://2130706433/andhttp://0177.0.0.1/are handled by validating the resolved address. There is no escape hatch — non-public addresses are refused unconditionally.redirect: "error") rather than followed. A validated public URL could otherwise redirect to a private one, and since each hop is a separatefetchWithL402call, each would independently pay an L402 challenge with no user confirmation.fly.tomlsetsALLOWED_HOSTS = 'mcp.getalby.com'so the hosted deployment keeps working.Testing
tscpasses. 25 assertions run against the built code, driving the realfetch_l402handler and the real middleware:127.0.0.1,[::1],localhost,169.254.169.254,10/8,192.168/16,100.64/10,[::ffff:127.0.0.1],[fd00::1],2130706433,0177.0.0.1,file:; ordinary public hosts allowed.redirect: "error"is confirmed to reachfetch; a refused redirect surfaces a clear message rather than an opaqueTypeError; ordinary public responses are unaffected.::1,[::1]:3000, and anALLOWED_HOSTSentry carrying a port all match; a foreignHostis rejected; settingALLOWED_HOSTSstopslocalhostbeing accepted.Notes
assertPublicUrlvalidates the resolved address, but the fetch resolves the hostname again independently. A DNS server that answers this check with a public address and the fetch with a private one therefore defeats it. Closing that means connecting to the validated address, which@getalby/lightning-toolscannot currently express.ALLOWED_ORIGINSto whatever feat: enable cors #28 intends to permit.