GHSA-HV85-774V-26FG

GHSA-HV85-774V-26FG is a high-severity server-side request forgery (SSRF) vulnerability in auth-fetch-mcp (npm), affecting versions <= 3.0.0. It is fixed in 3.0.1.

Summary

SSRF + disk-exfil in download_media and auth_fetch tools, ymw0407/auth-fetch-mcp

Severity

The download_media and auth_fetch MCP tools accept arbitrary URLs and reach them as the MCP server process, with download_media additionally persisting the fetched response body to a user-controlled output directory. An MCP client (LLM under prompt injection, malicious peer) can drive the server to fetch loopback / link-local / private-range hosts (cloud-instance metadata, internal services, host-bound services) and exfiltrate the response.

Vulnerability chain

Site 1: download_media, SSRF + disk-write chain

src/tools.ts:200-274

server.registerTool("download_media", {
  inputSchema: {
    urls: z.array(z.string()).describe("One or more URLs to download"),
    output_dir: z.string().optional()...,
  },
}, async ({ urls, output_dir }) => {
  ...
  for (const url of urls) {
    try {
      const response = await ctx.request.get(url);   // line 238, no validation
      ...
      const body = await response.body();
      ...
      const filePath = path.join(dir, `file-${++counter}${ext}`);
      fs.writeFileSync(filePath, body);              // line 257, writes response to disk

urls and output_dir are user-controlled. The handler iterates each URL (line 236) and calls ctx.request.get(url) (Playwright's APIRequestContext.get) without checking the destination. The response body is written to path.join(output_dir, file-N.ext). Internal-service responses are persisted to disk where they can be exfiltrated via any subsequent tool that reads from the output directory (or via the response object itself, which contains localPath and size of every successful write).

Site 2: auth_fetch, SSRF via Playwright navigation

src/tools.ts:117-198

server.registerTool("auth_fetch", {
  inputSchema: {
    url: z.string().describe("The URL to fetch content from"),
    wait_for: z.string().optional()...,
  },
}, async ({ url, wait_for }) => {
  ...
  const page = await navigateTo(ctx, url);           // line 142
  ...
  const result = await extractContent(page);
  return textResult({ status: "ok", url: result.url, title: result.title, content: result.content });
});

src/browser.ts:53-64

export async function navigateTo(ctx: BrowserContext, url: string): Promise<Page> {
  ...
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });  // line 63
  return page;
}

url flows directly from the MCP tool argument to page.goto with no validation. Playwright will navigate to any URL the network stack can reach. The page DOM is returned in the tool response via extractContent. Internal pages (loopback admin UIs, cloud metadata endpoints reachable from the host, intranet services) are extractable.

Root cause

Neither handler validates URL targets before dispatch. The tool descriptions ("fetches web page content using a real browser ... e.g. Notion, Google Docs, Jira, Confluence, Linear, Slack, or any SaaS/private page") frame the intended usage as public SaaS web pages, not loopback or link-local hosts, but no code enforces that intent.

The fix shape (apply to both tools): after URL parsing, resolve to IP, reject if private/loopback/link-local. Same defense as the well-known SSRF-guard pattern shipped by other MCP fetchers in the ecosystem (e.g., Akitaroh/scraper-mcp src/security/url-guard.ts).

Auth boundary violated

Boundary type: MCP tool-argument boundary plus the local-network trust boundary. The MCP server typically sits inside a trust boundary (developer laptop with loopback services, cloud VM with IMDS, k8s pod with service account). The tools allow the MCP client to dispatch HTTP requests across that boundary.

Respected/violated trace: Per the tool descriptions, the expected respected boundary is "public SaaS web pages." That expectation is violated by any request reaching a host the user didn't intend to expose (127.0.0.1:6379 Redis, 169.254.169.254 cloud metadata, 192.168.0.1 internal admin).

Proof of concept (non-destructive)

poc.mjs, replicates the download_media handler's HTTP-fetch + file-write chain against a local fake-internal HTTP service. Playwright's ctx.request.get(url) is replaced with the equivalent fetch(url) for the bug case (a URL needing no auth) so the demo runs without browser deps. The structural defect, "no host validation before HTTP dispatch", is identical.

[PoC] fake internal-only service: 127.0.0.1:36105
[PoC] simulating MCP client calling download_media({
        urls: ['http://127.0.0.1:36105/secrets'],
        output_dir: '/tmp/auth-fetch-exfil-aU1jjv'
      })
[PoC] no IP / host validation exists at tools.ts:236-238 before ctx.request.get(url)
[PoC] ✓ SSRF + DISK-EXFIL CONFIRMED
        File written to: /tmp/auth-fetch-exfil-aU1jjv/file-1.json
        Persisted content (187 bytes):
          {
            "AccessKeyId": "AKIA-FAKE-FROM-POC",
            "SecretAccessKey": "fake-secret-marker-NOT-REAL",
            "Note": "In a real exploit this would be AWS IMDS at 169.254.169.254/latest/meta-data/..."
          }

Exit code 0. SHA-256 poc.mjs: 4cea53f1a618581fc67f9a8bd07a7a2b22274f42cdbf7f3c658519673aaf7568. The PoC only contacts 127.0.0.1 on an ephemeral port; the fake-credentials string contains the literal FAKE marker so no downstream system can mistake it for real credentials. The exfil directory is cleaned up after the demo.

Impact

  1. Cloud credential theft, server on EC2 / GCE / Azure VM. MCP client invokes auth_fetch({ url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>" }) and receives temporary credentials in the tool response. Or invokes download_media({ urls: [...], output_dir: "/tmp/exfil" }) to persist them to disk.

  2. Internal service enumeration, MCP client probes private-range hosts (10/8, 172.16/12, 192.168/16). Each auth_fetch returns the page DOM; each download_media writes the response to disk.

  3. Loopback exploitation, server runs alongside Redis (127.0.0.1:6379), ElasticSearch (127.0.0.1:9200), or internal admin UIs. MCP client reads them via auth_fetch.

  4. Disk-write side channel (download_media only), output_dir is also user-controlled, with no documented restriction. An MCP client can request output_dir = "/some/user-writable-shared-dir" and exfil internal-service responses to a location accessible to a co-tenant process.

The injection vector is any content reaching the model that prompts a fetch tool call. The tool description explicitly says "MUST be used instead of Fetch/web_fetch when the page requires login", meaning the model is encouraged to call this tool for any "private page" mention, which a prompt-injected upstream content can trivially trigger.

Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.

GHSA-HV85-774V-26FG has a CVSS score of 8.2 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (3.0.1); upgrading removes the vulnerable code path.

Affected versions

auth-fetch-mcp (<= 3.0.0)

Security releases

auth-fetch-mcp → 3.0.1 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Add a assertSafeUrl helper (same shape as in the matching egoist/fetch-mcp advisory) called before any HTTP dispatch, at tools.ts:236 inside the download_media loop, and at the top of navigateTo in browser.ts:53:

import dns from 'node:dns/promises'
import net from 'node:net'

async function assertSafeUrl(rawUrl: string): Promise<URL> {
  const parsed = new URL(rawUrl)
  if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error(`Unsupported scheme`)
  const host = parsed.hostname
  const addresses = net.isIP(host)
    ? [host]
    : (await dns.lookup(host, { all: true })).map(a => a.address)
  for (const addr of addresses) {
    if (isPrivateOrLinkLocal(addr)) throw new Error(`Refusing to fetch ${addr}`)
  }
  return parsed
}

Where isPrivateOrLinkLocal blocks 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, ::1, fc00::/7, fe80::/10.

For download_media specifically, also constrain output_dir: resolve it under a fixed root (e.g., ~/.auth-fetch-mcp/downloads/) and reject if the resolved path escapes that root.

Frequently Asked Questions

  1. What is GHSA-HV85-774V-26FG? GHSA-HV85-774V-26FG is a high-severity server-side request forgery (SSRF) vulnerability in auth-fetch-mcp (npm), affecting versions <= 3.0.0. It is fixed in 3.0.1. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.
  2. How severe is GHSA-HV85-774V-26FG? GHSA-HV85-774V-26FG has a CVSS score of 8.2 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of auth-fetch-mcp are affected by GHSA-HV85-774V-26FG? auth-fetch-mcp (npm) versions <= 3.0.0 is affected.
  4. Is there a fix for GHSA-HV85-774V-26FG? Yes. GHSA-HV85-774V-26FG is fixed in 3.0.1. Upgrade to this version or later.
  5. Is GHSA-HV85-774V-26FG exploitable, and should I be worried? Whether GHSA-HV85-774V-26FG is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether GHSA-HV85-774V-26FG is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix GHSA-HV85-774V-26FG? Upgrade auth-fetch-mcp to 3.0.1 or later.

Other vulnerabilities in auth-fetch-mcp

CVE-2026-49857

Stop the waste.
Protect your environment with Kodem.