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 i
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
- high · 8.5CVE-2026-54353CVE-2026-54353: @budibase/backend-core SSRF DNS Rebinding Bypass
- high · 7.5CVE-2026-48815CVE-2026-48815: sigstore certificateOIDs Verification Constraint Silently Dropped
- high · 8.8CVE-2026-49987CVE-2026-49987: repomix Argument Injection via --remote-branch (RCE)
- highCVE-2026-49864CVE-2026-49864: wetty DOM XSS via File-Download Filename