CVE-2026-49857: auth-fetch-mcp SSRF Protection Bypass via IPv4-mapped IPv6 Loopback
A flaw in auth-fetch-mcp's private-IP blocklist lets an attacker reach loopback and internal services by disguising the target address as an IPv4-mapped IPv6 literal, which Node.js silently rewrites…
The problem
The assertSafeUrl() guard in src/security.ts blocks direct loopback URLs such as http://127.0.0.1/ and http://[::1]/ correctly. Its isPrivateV6() helper handles the ::ffff: prefix by slicing off the suffix and calling net.isIPv4() on it.
The problem is that Node.js's WHATWG URL parser normalizes ::ffff:127.0.0.1 to ::ffff:7f00:1 before any application code runs. After normalization the suffix is 7f00:1, which is not dotted-decimal, so net.isIPv4() returns false and the guard falls through, returning false (safe).
The same bypass works for any private IPv4 range encoded as an IPv4-mapped IPv6 literal. Both the auth_fetch and download_media MCP tools are sinks: responses are returned to the caller or written to disk, making this a non-blind SSRF.
Proof of concept
A working proof-of-concept for CVE-2026-49857 in auth-fetch-mcp, with the exact payload below.
// MCP tool invocation sent to auth-fetch-mcp v3.0.1
{
"tool": "auth_fetch",
"arguments": {
"url": "http://[::ffff:127.0.0.1]:31337/"
}
}
// Node.js rewrites the host before assertSafeUrl() sees it:
// Input: http://[::ffff:127.0.0.1]:31337/
// Normalized: http://[::ffff:7f00:1]:31337/
// net.isIPv4('7f00:1') === false => isPrivateV6() returns false => ALLOW
// Variant using hex form directly (also bypasses):
// "url": "http://[::ffff:7f00:1]:31337/"Root cause is CWE-918 (SSRF). The guard pattern-matches the string suffix after ::ffff: expecting dotted-decimal IPv4, but the WHATWG URL Living Standard requires parsers to hex-encode the embedded IPv4 octets, so the suffix is always in HHHH:HHHH form by the time application code reads url.hostname.
The patch adds a regex ^([0-9a-f]{1,4}):([0-9a-f]{1,4})$ that captures the two hex groups, converts them to a dotted-decimal string with integer arithmetic (hi >> 8, hi & 255, lo >> 8, lo & 255), then passes that string to the existing isPrivateV4() check.
This correctly identifies 7f00:1 as 127.0.0.1 and c0a8:101 as 192.168.1.1.
The advisory also recommends a defence-in-depth layer: a Playwright BrowserContext route guard in src/browser.ts that re-runs assertSafeUrl() on every navigation, catching redirect-based bypasses that the string check alone cannot stop.
The fix
Upgrade to auth-fetch-mcp v3.0.2. The patch extends isPrivateV6() to decode hex-encoded IPv4-mapped suffixes before the private-range check, closing the normalization gap. No configuration change is required after upgrading.
Reported by Shmulik Cohen.
Related research
- highCVE-2026-69257CVE-2026-69257: Flowise SSRF Protection Bypass via IPv4-Mapped IPv6 Addresses
- highCVE-2026-69192CVE-2026-69192: ip-address Address4 Leading-Zero Octet SSRF Bypass
- highCVE-2026-54729CVE-2026-54729: dssrf SSRF Bypass via DNS NXDOMAIN on 1.1.1.1
- high · 7.4CVE-2026-54660CVE-2026-54660: swagger-typescript-api Authorization Token Exfiltration via Cross-Origin $ref