# auth-fetch-mcp has SSRF Protection Bypass via IPv4-mapped IPv6 Loopback

auth-fetch-mcp's isPrivateV6() misses hex-normalized IPv4-mapped IPv6 loopback, letting auth_fetch reach 127.0.0.1 services via http://[::ffff:127.0.0.1]:PORT/.

- Payout: Research
- Severity: high
- Program: auth-fetch-mcp
- Found by: EQSTLab
- Disclosed: 2026-07-01
- CVE: CVE-2026-49857
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pvrj-8cg3-j5f8
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pvrj-8cg3-j5f8
- Tags: area:ai domain:ai impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:llm tech:nodejs technique:parser-differential technique:source-review vuln:ssrf

**Target:** MCP server URL validation (src/security.ts assertSafeUrl/isPrivateV6) feeding Playwright page.goto and HTTP client
**Root cause:** isPrivateV6() only checks the ::ffff: suffix with net.isIPv4(), but the WHATWG URL parser hex-normalizes IPv4-mapped addresses (::ffff:127.0.0.1 -> ::ffff:7f00:1), so the suffix is no longer dotted-decimal and the private-IP check falls through to false.
**Steps:**
1. Start a loopback-only service: `node -e 'require("http").createServer((q,r)=>r.end("<h1>INTERNAL_SECRET_MARKER</h1>")).listen(31337,"127.0.0.1")'`
2. Run `npx auth-fetch-mcp@3.0.1` with default config
3. Invoke MCP tool auth_fetch with `{"url":"http://[::ffff:127.0.0.1]:31337/"}`
4. URL parser normalizes host to `[::ffff:7f00:1]`; `net.isIPv4('7f00:1')` is false so assertSafeUrl does not throw
5. Click Capture; response body contains INTERNAL_SECRET_MARKER, confirming loopback reach
**How found:** Code review of isPrivateV6() plus observation that Node's WHATWG URL hex-normalizes IPv4-mapped IPv6 hosts, so the dotted-decimal assumption in the guard breaks; confirmed with baseline-block vs bypass PoC in Docker --network=host.
**Exploitation:** Attacker supplies or influences the url argument of auth_fetch (or urls[] of download_media), e.g. via prompt injection in a visited page. The IPv4-mapped IPv6 literal passes assertSafeUrl, page.goto/ctx.request.get fetches the internal loopback service, and the extracted page content is returned to the MCP caller, leaking internal service responses.
**Escalation:** Same bypass applies to download_media, writing internal responses to the local downloads directory; any private IPv4 range reachable via mapped literal.
**Insight:** When auditing SSRF guards, check how the runtime's URL parser canonicalizes hosts before the check: IPv4-mapped IPv6 (::ffff:x.x.x.x) becomes hex (::ffff:7f00:1), defeating net.isIPv4-style suffix checks. Test both dotted and hex forms.

## Proof of concept (copied exactly from the original report)

**Loopback-only internal HTTP service for PoC**

```
node -e 'require("http").createServer((q,r)=>r.end("<h1>INTERNAL_SECRET_MARKER</h1>")).listen(31337,"127.0.0.1")'
```

