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 i

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