Skip to content

CVE-2026-49857: Verified Reproduction

CVE-2026-49857: auth-fetch-mcp SSRF via IPv4-mapped IPv6 loopback bypass

CVE-2026-49857 is verified against ymw0407/auth-fetch-mcp · npm. Affected versions: <= 3.0.1. Fixed in 3.0.2. Vulnerability class: SSRF. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00205.

REPRO-2026-00205 ymw0407/auth-fetch-mcp · npm SSRF Jul 2, 2026 CVE entry .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
17m 11s
Tool calls
166
Spend
$2.60
01 · Overview

What Is CVE-2026-49857?

CVE-2026-49857 is a high-severity SSRF (CWE-918) in the auth-fetch-mcp MCP server's URL security guard, allowing IPv4-mapped IPv6 loopback addresses to bypass its private-IP protection. Pruva reproduced it (reproduction REPRO-2026-00205).

02 · Severity & CVSS

CVE-2026-49857 Severity

CVE-2026-49857 is rated high severity.

HIGH threat level
Weakness CWE-918 (SSRF) — Server-Side Request Forgery (SSRF)

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected ymw0407/auth-fetch-mcp Versions

ymw0407/auth-fetch-mcp · npm versions <= 3.0.1 are affected.

How to Reproduce CVE-2026-49857

$ pruva-verify REPRO-2026-00205
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00205/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-49857

Authorization bypass — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

URL http://[::ffff:127.0.0.1]:18080/ passed to download_media tool via MCP JSON-RPC tools/call

Attack chain
  1. MCP tools/call download_media
  2. assertSafeUrl()
  3. isPrivateV6() hex-normalization bypass
  4. ctx.request.get() fetches loopback 127.0.0.1:18080
Runnable proof: reproduction_steps.sh
Captured evidence: vulnerable testvulnerable victim servervulnerable mcp requestsvulnerable mcp stdoutfixed testfixed victim serverfixed mcp requestsfixed mcp stdout
How the agent worked 395 events · 166 tool calls · 17 min
17 minDuration
166Tool calls
105Reasoning steps
395Events
Agent activity over 17 min
Support
17
Hypothesis
2
Repro
175
Judge
25
Variant
172
0:0017:11

Root Cause and Exploit Chain for CVE-2026-49857

Versions: ≤3.0.1 (fixed in 3.0.2)

The auth-fetch-mcp MCP server (npm package auth-fetch-mcp, versions ≤3.0.1) contains a Server-Side Request Forgery (SSRF) vulnerability in its URL security guard. The assertSafeUrl() function in src/security.ts is designed to block requests to private/loopback IP addresses, but its isPrivateV6() helper fails to detect IPv4-mapped IPv6 addresses after the Node.js WHATWG URL parser hex-normalizes them. When a user supplies a URL like http://[::ffff:127.0.0.1]:PORT/, the URL parser normalizes the hostname to ::ffff:7f00:1 (hex form). The guard checks net.isIPv4("7f00:1"), which returns false because the suffix is in hex notation, not dotted-decimal. As a result, the loopback address is classified as non-private and the security guard is bypassed, allowing the MCP server to fetch arbitrary internal/loopback URLs via the download_media and auth_fetch tools.

  • Package/component affected: auth-fetch-mcp npm package, specifically src/security.ts (assertSafeUrlisPrivateV6isPrivateV4 guard chain)
  • Affected versions: ≤3.0.1 (fixed in 3.0.2)
  • Risk level: High
  • Consequences: An attacker (via a malicious MCP client or prompt injection) can cause the MCP server to fetch arbitrary internal/loopback URLs, bypassing the SSRF protection. The download_media tool downloads the fetched content to disk and returns the file path to the caller, enabling information disclosure from internal services (e.g., cloud metadata endpoints at 169.254.169.254, internal APIs on 127.0.0.1/10.x/192.168.x, etc.). The auth_fetch tool renders fetched internal pages in a browser and returns extracted content.

Impact Parity

  • Disclosed/claimed maximum impact: SSRF — bypass of the private-IP guard to fetch internal/loopback URLs via the MCP server's auth_fetch or download_media tools.
  • Reproduced impact from this run: Full SSRF confirmed. The MCP server (v3.0.1) processed a download_media tool call with URL http://[::ffff:127.0.0.1]:18080/, bypassed assertSafeUrl(), fetched content from a loopback HTTP server via Playwright's ctx.request.get(), saved the response to disk, and returned the file path. The downloaded file contained the secret marker from the internal server, proving the server-side request reached the loopback target.
  • Parity: full
  • Not demonstrated: The reproduction targeted a loopback HTTP server (127.0.0.1:18080). Cloud metadata endpoint (169.254.169.254) and other private ranges were not exercised at runtime, but the same bypass mechanism applies to all private IPv4 ranges via their IPv4-mapped IPv6 hex forms.

Root Cause

The vulnerability is in the isPrivateV6() function in src/security.ts:

function isPrivateV6(ip: string): boolean {
  const lower = ip.toLowerCase();
  if (lower === "::" || lower === "::1") return true;
  if (lower.startsWith("fe80:") || lower.startsWith("fe80::")) return true;
  if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
  if (lower.startsWith("ff")) return true;
  if (lower.startsWith("::ffff:")) {
    const v4 = lower.slice(7);
    if (net.isIPv4(v4)) return isPrivateV4(v4);  // ← BUG: only handles dotted-decimal
  }
  return false;
}

The bug: When the WHATWG URL parser processes http://[::ffff:127.0.0.1]:PORT/, it hex-normalizes the IPv4-mapped IPv6 hostname to ::ffff:7f00:1 (where 7f = 127, 00 = 0, 01 = 1). The isPrivateV6() function extracts the suffix 7f00:1 and checks net.isIPv4("7f00:1"), which returns false because the suffix is in hex notation, not dotted-decimal form. The function then falls through to return false, classifying the loopback address as non-private.

The call chain:

  1. MCP client sends tools/call with download_media and URL http://[::ffff:127.0.0.1]:PORT/
  2. download_media handler calls assertSafeUrl(url) in src/tools.ts:233
  3. assertSafeUrl() calls new URL(rawUrl) — the WHATWG parser normalizes ::ffff:127.0.0.1::ffff:7f00:1
  4. assertSafeUrl() extracts hostname = ::ffff:7f00:1, calls isPrivateOrLinkLocal("::ffff:7f00:1")
  5. isPrivateOrLinkLocal() calls isPrivateV6("::ffff:7f00:1") (since net.isIPv6() returns true)
  6. isPrivateV6() checks ::ffff: prefix, extracts 7f00:1, net.isIPv4("7f00:1") = falsereturns false (bypass!)
  7. assertSafeUrl() returns the parsed URL — security check passed
  8. ctx.request.get(safeUrl.toString()) fetches http://[::ffff:7f00:1]:PORT/ → OS maps to 127.0.0.1:PORTSSRF succeeds

Fix commit: 177ec5f8ee9c2d5749035777e562f699971b0da9 — adds hex group parsing to reconstruct the IPv4 address from the two trailing hex groups and run it through isPrivateV4():

const groups = v4.split(":");
if (groups.length === 2 && groups.every((g) => /^[0-9a-f]{1,4}$/.test(g))) {
  const hi = parseInt(groups[0], 16);
  const lo = parseInt(groups[1], 16);
  const mapped = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
  return isPrivateV4(mapped);
}

Reproduction Steps

  1. Reference: bundle/repro/reproduction_steps.sh (self-contained, uses bundle/repro/mcp_client.js)
  2. What the script does:
    • Reads bundle/project_cache_context.json to locate the prepared project cache and Playwright browser cache
    • Clones/reuses the auth-fetch-mcp repo at the project cache path
    • Installs Chrome Headless Shell for Playwright (manual download from Google's Chrome for Testing CDN, since npx playwright install doesn't support Ubuntu 26.04)
    • Installs system libraries required by Chrome
    • Builds vulnerable version v3.0.1 (commit 98f381d)
    • Starts a local "victim" HTTP server on 127.0.0.1:18080 that returns a unique secret marker
    • Spawns the real MCP server (node dist/index.js) as a child process
    • Sends JSON-RPC initialize + tools/call download_media with URL http://[::ffff:127.0.0.1]:18080/ over stdio
    • Checks if the downloaded file contains the secret marker (SSRF confirmed) or if the response contains "Refusing to fetch" (blocked)
    • Repeats for fixed version v3.0.2 (commit d4dedaf, fix 177ec5f)
    • Writes runtime_manifest.json with evidence
  3. Expected evidence:
    • Vulnerable v3.0.1: victim server receives HTTP request from 127.0.0.1, downloaded file contains the secret marker, tool returns downloaded: 1
    • Fixed v3.0.2: victim server receives no request, tool returns error: "Refusing to fetch [::ffff:7f00:1]...", downloaded: 0

Evidence

  • Log files (under bundle/logs/):

    • reproduction_steps.log — full script output
    • vulnerable_test.log — vulnerable version test output
    • vulnerable_result.json — structured result: ssrfConfirmed: true, downloadedContent: "SSRF_SECRET_MARKER_..."
    • vulnerable_victim_server.log — shows Request from 127.0.0.1 path=/ (SSRF request received)
    • vulnerable_mcp_stdout.log — MCP server JSON-RPC responses
    • vulnerable_mcp_requests.log — JSON-RPC requests sent
    • fixed_test.log — fixed version test output
    • fixed_result.json — structured result: blocked: true, error: "Refusing to fetch [::ffff:7f00:1]..."
    • fixed_victim_server.log — shows NO request received (only "Listening" line)
    • fixed_mcp_stdout.log — MCP server JSON-RPC responses showing the block
  • Key excerpts:

    • Vulnerable victim server log: [VICTIM:18080] Request from 127.0.0.1 path=/
    • Vulnerable downloaded file: SSRF_SECRET_MARKER_1783015602673_hgnjlkyh (matches marker from internal server)
    • Vulnerable tool result: {"status":"ok","downloaded":1,"total":1,"files":[{"url":"http://[::ffff:127.0.0.1]:18080/","localPath":".../file-1.bin","size":41}]}
    • Fixed tool result: {"status":"ok","downloaded":0,"total":1,"files":[{"url":"http://[::ffff:127.0.0.1]:18080/","error":"Refusing to fetch [::ffff:7f00:1] (resolves to private/loopback/link-local address ::ffff:7f00:1)..."}]}
  • Environment:

    • Node.js v24.18.0, npm 11.16.0
    • Ubuntu 26.04 LTS (Resolute Raccoon)
    • Playwright 1.58.2 with Chrome Headless Shell 145.0.7632.6 (manually installed)
    • MCP SDK @modelcontextprotocol/sdk ^1.27.1 (from package-lock.json)

Recommendations / Next Steps

  • Upgrade: Update to auth-fetch-mcp@3.0.2 or later, which includes the fix (commit 177ec5f).
  • Suggested fix approach (already implemented in 3.0.2): Parse the two trailing hex groups of ::ffff: prefixed IPv6 addresses back into dotted-decimal IPv4 form and run through isPrivateV4(). Additionally, consider using a well-maintained SSRF protection library (e.g., ssrf-check or equivalent) rather than custom IP range checks.
  • Testing recommendations: Add unit tests for assertSafeUrl() covering:
    • IPv4-mapped IPv6 in both dotted-decimal (::ffff:127.0.0.1) and hex (::ffff:7f00:1) forms
    • All private ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 (link-local/metadata)
    • Public IPv4-mapped addresses should still pass (e.g., ::ffff:8.8.8.8)
    • Integration tests that verify the MCP tool rejects loopback URLs end-to-end

Additional Notes

  • Idempotency: The script was run twice consecutively with identical results (both runs: vulnerable=SSRF confirmed, fixed=blocked). The script cleans up previous test state (removes old HOME directories, overwrites result files) and uses unique markers per run.
  • Chrome installation workaround: Playwright 1.58.2 does not support npx playwright install on Ubuntu 26.04. The script manually downloads Chrome Headless Shell and Chrome for Testing from Google's CDN (storage.googleapis.com/chrome-for-testing-public/) and places them in the Playwright browser cache directory. The PLAYWRIGHT_BROWSERS_PATH environment variable is set so Playwright finds the manually installed browser regardless of HOME.
  • MCP transport: The auth-fetch-mcp server uses stdio JSON-RPC (StdioServerTransport), not HTTP. The reproduction interacts with it via its real JSON-RPC API by spawning the server process and sending protocol messages over stdin/stdout. This is the actual API surface of the product — the tool-calling endpoint that processes attacker-supplied URLs.
  • Port choice: The victim server uses port 18080 to avoid conflicts with common services.

CVE-2026-49857 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:56
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-49857 · REPRO-20
0:05
0:08
web search
0:09
web search
0:11
0:13
0:14
web search
0:16
0:18
0:28
0:28
extract_facts
no facts extracted
0:30
0:30
0:30
supportrepro
0:39
0:41
0:41
0:41
0:43
0:43
0:43
0:48
0:48
0:48
$ls -la /data/pruva/project-cache/c1700ca0-202a-4b72-bf90-7542807cb1eb/ 2>/dev/null && echo "---REPO---" && ls -la /data/pruva/project-cache/c1700ca0-202a-4b72-bf90-7542807cb1eb/repo 2>/dev/null && echo "---MIRRORS---" && ls -la /data/pruva/project-cache/c1700ca0-202a-4b72-bf90-7542807cb1eb/repo-mirrors 2>/dev/null
total 0
drwxr-xr-x 1 vscode vscode   24 Jul  2 17:59 .
drwxr-xr-x 1 vscode vscode 1750 Jul  2 17:59 ..
drwxr-xr-x 1 vscode vscode    0 Jul  2 17:59 repo-mirrors
---REPO---
0:54
0:54
$node --version 2>/dev/null; npm --version 2>/dev/null; which node; which npm; echo "---"; git --version
v24.18.0
11.16.0
/usr/bin/node
/usr/bin/npm
---
git version 2.55.0
0:54
0:56
08 · How to Fix

How to Fix CVE-2026-49857

Upgrade ymw0407/auth-fetch-mcp · npm to 3.0.2 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-49857 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-49857

How does the CVE-2026-49857 SSRF bypass work in practice?

An attacker (via a malicious MCP client or prompt injection) supplies a URL such as http://[::ffff:127.0.0.1]:PORT/ to the auth_fetch or download_media MCP tool. The bypassed guard lets the server fetch arbitrary internal/loopback URLs; download_media downloads the fetched content to disk and returns the file path, enabling information disclosure from internal services.

Which auth-fetch-mcp versions are affected by CVE-2026-49857, and where is it fixed?

Versions <= 3.0.1 are affected. It is fixed in 3.0.2.

How severe is CVE-2026-49857?

It is rated high severity: the guard bypass lets an attacker cause the MCP server to fetch and return content from arbitrary internal or loopback endpoints, such as cloud metadata services.

How can I reproduce CVE-2026-49857?

Download the verified script from this page and run it in an isolated environment against auth-fetch-mcp@3.0.1 with default settings. Invoke the auth_fetch or download_media tool with URL http://[::ffff:127.0.0.1]:<PORT>/ and confirm the server fetches the loopback URL and returns the response; confirm 3.0.2 blocks the request.
11 · References

References for CVE-2026-49857

Authoritative sources for CVE-2026-49857 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.