Published Build3 min read
The SSRF guard that never ran: when a decimal IP skips DNS entirely
CrawlForge says its URL-fetching MCP server range-checked DNS answers but never checked IP literals, so http://2130706433/ reached loopback. v5.0.0 moves the check to connect time.
Written for builders.See today for builders

What happened
- http://2130706433/ is a valid URL and a browser resolves it to 127.0.0.1, because the WHATWG URL parser normalizes decimal, hex (0x7f000001) and octal integer forms into dotted-quad IPv4.
- CrawlForge's SSRF guard resolved hostnames through DNS and range-checked the resulting addresses.
- Node never routes an IP literal through lookup, so a URL whose host was already an IP address passed the guard without being checked.
- As a result, loopback, link-local and cloud metadata addresses were all reachable in a server whose entire job is fetching URLs a model picked.
- The pre-v5.0.0 flow was: url -> parse -> DNS lookup -> ipBlocked(resolved)? -> fetch; if the host was an IP literal no lookup happened, the guard never ran, and the request went out.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
CrawlForge's release notes for v5.0.0 of its MCP scraping server say the product's SSRF guard could be walked past with a URL as plain as http://2130706433/, because the guard resolved hostnames through DNS and range-checked the answers, while Node never routes an IP literal through lookup [1][2][3]. In a server whose entire job is fetching URLs a model picked, that left loopback, link-local and cloud metadata addresses reachable [4].
The mechanism deserves precision, because nothing about it is exotic. The WHATWG URL parser normalizes decimal, hex and octal integer host forms into dotted-quad IPv4, so http://2130706433/ and http://0x7f000001/ both mean 127.0.0.1 [1]. The guard's pipeline was parse, DNS lookup, range check, fetch [5]. A host that was already an address skipped the lookup, and because the check hung off the lookup, no check happened [3][5].
v5.0.0 puts the decision in two places: ipBlocked() now runs on IP-literal hostnames at pre-flight, and the undici dispatcher's buildConnector is wrapped with a per-connect check that also covers every redirect hop [6]. Three adjacent holes closed with it. IPv4-mapped IPv6 forms such as ::ffff:169.254.169.254 are normalized to the embedded IPv4 before range checks in both default and strict modes, which the writeup says kills a DNS-controlled AAAA-record bypass [7]. BLOCKED_DOMAINS was declared and read by nothing, and is now enforced at pre-flight [8]. The allowlist is evaluated per hop, where an allowlisted first hop used to unguard every redirect after it [9].
Five paths had no guard at all: scrape_with_actions, map_site, process_document PDF downloads, webhook delivery and health checks, and deep_research webhook notifications [10]. scrape_with_actions now re-checks page.url() after navigation and closes the page on a redirect into a blocked range; CrawlForge describes the prior behaviour as a Playwright internal-network read primitive [11].
The same audit reached past SSRF. /oauth/authorize now requires proof of the operator's API key before issuing a code, using constant-time digest comparison, which closes an anonymous register-authorize-token flow that minted operator-billed bearer tokens [12]. Telemetry payloads pass through maskSecrets() before leaving the process [13], and deep_research stopped writing LLM API keys to Winston file logs [14]. checkCredits now separates 401/403 from 5xx instead of reporting both as insufficient credits [15].
Phase 2 of the audit counted 52 places where tools were silently wrong [16]. The one with operational teeth: crawl_deep awaited BFS child pages from inside an occupied queue slot, so the per-task 30-second queue timeout bounded the whole recursive crawl, discarding every page already fetched with a bare "Promise timed out", and low concurrency settings including concurrency: 1 deadlocked [17].
The counts: unit tests went from 480 to 914, a net 434 [18][19]; npm audit from 16 vulnerabilities to 0 [20]. The single breaking change is engines.node moving from >=18.0.0 to >=20.16.0 [21], driven by Node 18 reaching end of life in April 2025 and by pdf-parse 2.4.5 [22]. Tool schemas, output shapes, credit costs and the 27-tool count are unchanged [23]. MCP protocol compliance held at 100.0% COMPLIANT with 0 errors at every phase gate [24], which mostly establishes that a conformance suite is not a security test.
What to watch in your own fetchers: any security decision that is a side effect of a resolver call rather than a step in its own right. The bypass class here is structural, not a missing regex, and it applies to every service handing model-chosen URLs to an HTTP client. Check whether your denylist runs when the host needs no lookup, and whether it runs again on hop two.
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
http://2130706433/ is a valid URL and a browser resolves it to 127.0.0.1, because the WHATWG URL parser normalizes decimal, hex (0x7f000001) and octal integer forms into dotted-quad IPv4.
- [2]
CrawlForge's SSRF guard resolved hostnames through DNS and range-checked the resulting addresses.
- [3]
Node never routes an IP literal through lookup, so a URL whose host was already an IP address passed the guard without being checked.
- [4]
As a result, loopback, link-local and cloud metadata addresses were all reachable in a server whose entire job is fetching URLs a model picked.
- [5]
The pre-v5.0.0 flow was: url -> parse -> DNS lookup -> ipBlocked(resolved)? -> fetch; if the host was an IP literal no lookup happened, the guard never ran, and the request went out.
- [6]
v5.0.0 runs ipBlocked() on IP-literal hostnames at pre-flight and wraps the undici dispatcher's buildConnector with a per-connect check, so a redirect hop straight to an internal address is blocked as well.
Sources & coverage · 1 publisher
The reporting this story was synthesized from, earliest first. Every link goes to the original.
- dev.toSimonAug 14CrawlForge v5.0.0: Security, Correctness, MCP Spec
Cited in this coverage: CrawlForge v5.0.0 release writeup, dev.to
Additional citations
- CrawlForge v5.0.0 release writeup

