high · 7.4CVE-2026-49857Jul 1, 2026

CVE-2026-49857: auth-fetch-mcp SSRF Protection Bypass via IPv4-mapped IPv6 Loopback

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

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…

Packageauth-fetch-mcp
Ecosystemnpm
Affected<= 3.0.1
Fixed in3.0.2

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.

json
// 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.

References: [1][2]

Related research