fix: resolve DNS before SSRF validation to prevent rebinding bypass#386
Open
YizukiAme wants to merge 1 commit intoTHU-MAIC:mainfrom
Open
fix: resolve DNS before SSRF validation to prevent rebinding bypass#386YizukiAme wants to merge 1 commit intoTHU-MAIC:mainfrom
YizukiAme wants to merge 1 commit intoTHU-MAIC:mainfrom
Conversation
Contributor
Author
Follow-up: TOCTOU / IP PinningThis PR fixes the primary DNS rebinding bypass (hostname-only validation), but a time-of-check-time-of-use (TOCTOU) gap remains:
To fully mitigate this, the server would need to pin the resolved IP and connect directly to it (setting Planned follow-up issue (if this PR is merged):
The current fix already blocks the vast majority of real-world DNS rebinding attacks — exploiting the TOCTOU window requires precise millisecond-level DNS TTL manipulation. |
9967f02 to
0cd2d91
Compare
- Make validateUrlForSSRF async; resolve hostnames via dns.promises.lookup
with { all: true, verbatim: true } and reject if any address is private
- Add isPrivateIP helper covering IPv4 private ranges, IPv6 loopback/ULA/
link-local, IPv4-mapped IPv6, deprecated fec0::/10 site-local, 6to4
tunnel (2002::/16), and Teredo tunnel (2001:0000::/32)
- Detect IP literals via net.isIP() and validate directly without DNS
- Fail closed on DNS resolution errors
- Propagate async to resolveModel, resolveModelFromHeaders, and all 20+
route-level callers; preserve existing production-only gating
- Add 14 unit tests covering public pass-through, private literals, DNS
rebinding, mixed DNS answers, tunnel addresses, and DNS failure
Closes THU-MAIC#378
0cd2d91 to
e7dab7f
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
Fix SSRF guard DNS rebinding bypass by resolving hostnames to IP addresses before validation.
Closes #378
Problem
The SSRF guard in
lib/server/ssrf-guard.tsonly checked hostname strings (e.g.localhost,192.168.*), allowing attackers to register a domain resolving to a private IP and bypass all private-network checks. This could expose cloud instance metadata (169.254.169.254), internal APIs, and other local services.Changes
Core Fix (
lib/server/ssrf-guard.ts)validateUrlForSSRFis nowasync— resolves hostnames viadns.promises.lookupwith{ all: true, verbatim: true }and rejects if any returned address is private/localisPrivateIP()helper covers:127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,0.0.0.0::1,::,fc00::/7(ULA),fe80::/10(link-local),fec0::/10(deprecated site-local)::ffff:127.0.0.1,::ffff:7f00:00012002::/16): extracts embedded IPv4 from bits 16-472001:0000::/32): extracts XOR-inverted client IPv4 from bits 96-127net.isIP()— validated directly without DNS lookup, avoiding false positives from "DNS failure = block"Async Propagation (20+ files)
resolveModel()andresolveModelFromHeaders()inlib/server/resolve-model.tsare nowasyncvalidateUrlForSSRF()callers and indirect callers viaresolveModel/resolveModelFromHeadersupdated withawaitNODE_ENV === 'production') and per-route SSRF policies are preservedTests (
tests/server/ssrf-guard.test.ts)14 test cases covering:
ftp://,file://,javascript:localhost,.local127.0.0.1,10.x,172.16-31.x,192.168.x,169.254.x,0.0.0.0::1,fd00::,fe80::,fec0::,::ffff:127.0.0.1127.0.0.1→ blockedVerification
pnpm vitest run tests/server/ssrf-guard.test.ts— 14/14 passedpnpm build— no type errorsOut of Scope
Assisted by Claude Opus 4.6 and GPT-5.4.