Skip to content

fix(fetch): block private network URL fetches - #4226

Open
kgarg2468 wants to merge 1 commit into
modelcontextprotocol:mainfrom
kgarg2468:kgarg/harden-fetch-ssrf
Open

fix(fetch): block private network URL fetches#4226
kgarg2468 wants to merge 1 commit into
modelcontextprotocol:mainfrom
kgarg2468:kgarg/harden-fetch-ssrf

Conversation

@kgarg2468

Copy link
Copy Markdown
Contributor

Summary

  • validate fetch URLs use http/https and resolve only to public IP addresses
  • block localhost, private, loopback, link-local, and metadata-service targets
  • follow redirects manually so redirected targets are validated before each request

Fixes #4143.

Tests

  • cd src/fetch && uv run pytest tests/test_server.py -q
  • cd src/fetch && uv run ruff check src/mcp_server_fetch/server.py tests/test_server.py
  • git diff --check

desledishant10 added a commit to desledishant10/mcp-witness that referenced this pull request Jun 2, 2026
PR modelcontextprotocol/servers#4226 by @kgarg2468 opened 2026-05-22,
explicitly listing 'Fixes #4143' in the body. Commit summary:

- Validate fetch URLs use http/https
- Resolve only to public IP addresses
- Block localhost, private, loopback, link-local, and metadata-service
  targets
- Follow redirects manually so redirected targets are validated before
  each request

The per-redirect validation step is more rigorous than the original
disclosure asked for — closes a 302-bypass that the basic scheme/host
check would have missed.

16/16 CI checks pass on the PR. Awaiting maintainer approval.

Updated:
- disclosures/2026-05-12-mcp-fetch-http-request-ssrf.md (status field
  + new Updates entry dated 2026-05-22)
- findings/2026-05-11-MCP-D-003-fetch-direct-environment-dependent-ssrf.md
  (outcome line: 'demonstrated' → 'demonstrated + fix PR in review')
- README.md findings table (fix PR link added)
- CHANGELOG.md Unreleased section (disclosure-status entry)

The community half (mcp-server-http-request) still has no maintainer
response. Day +14 ping is due 2026-05-26 (4 days from now).
@desledishant10

Copy link
Copy Markdown

Thanks for picking this up so quickly, @kgarg2468 really appreciate it.
The fix addresses everything the original disclosure asked for (scheme allowlist + reserved-range denylist), and the per-redirect validation is a nice catch I hadn't explicitly called out. Without it, an attacker could host a public URL that 302s to http://169.254.169.254/... and bypass the initial host check. Glad to see it covered here.
Happy to spin up the same EC2 reproduction setup against this branch to confirm the metadata-service request now refuses cleanly, if that would be useful for the review. Let me know.
Tracking the PR alongside the original disclosure record at https://github.com/desledishant10/mcp-scan/blob/main/disclosures/2026-05-12-mcp-fetch-http-request-ssrf.md so the timeline stays public.

@LuuOW LuuOW left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical audit: Verified MCP server implementation for consistency with current SDK patterns.

desledishant10 added a commit to desledishant10/mcp-witness that referenced this pull request Jun 21, 2026
Third of three commits. The harness is now end-to-end runnable.

Contents this commit:

- poc/ssrf/quick_probe.py: pure-Python no-Docker probe. Spins up the
  IMDS mock on a random loopback port (loopback stands in for link-
  local since both are RFC-reserved addresses that post-PR-#4226
  refuses; we can't bind 169.254.169.254 in a regular process) and
  runs two side-by-side fetches against it:
    1. Pre-fix shape: bare urllib.request.urlopen with no validation
       -> the AKIA-FAKE token comes back.
    2. Post-fix shape (PR #4226 logic distilled to a function):
       scheme allowlist + RFC-reserved-range denylist applied to the
       URL host -> URL is refused before the HTTP fetch.
  No mcp-server-fetch install required. Verified end-to-end:
    - exits 0 with vulnerability shape demonstrated
    - exits 1 with --exit-nonzero-on-vuln set (monitoring/CI mode)
    - exits 2 if IMDS mock fails to bind
    - exits 3 if either side didn't match expected pattern

- poc/ssrf/Makefile: targets mirror poc/dns-rebind/Makefile for
  consistency:
    make demo-quick    pure Python, ~3 seconds
    make demo-full     containerized end-to-end, ~60 seconds first run
    make demo          demo-quick then demo-full
    make demo-fixed    demo-full with MCP_FETCH_VERSION set to a post-
                       PR-#4226 release to verify the fix path
    make clean         tear down containers + remove built images
  demo-fixed refuses to run without MCP_FETCH_VERSION set, since
  there's no sensible default post-fix version to pin.

- poc/ssrf/README.md: architecture diagram, expected output for both
  vulnerable + fixed runs, exit-code table, layout reference, and
  cross-links to:
    - findings/2026-05-11-MCP-D-003-fetch-direct-environment-dependent-ssrf.md
    - modelcontextprotocol/servers#4143 (disclosure)
    - modelcontextprotocol/servers#4226 (fix PR)
    - docs/audit-runbook-ec2-ssrf-verification.md (real-EC2 runbook
      that this harness complements rather than replaces)
    - poc/dns-rebind/ (sibling harness for the other vuln class)
  Explicitly notes the harness is NOT embargoed since the SSRF was
  disclosed via the public GitHub issue on 2026-05-12 and the fix
  shipped 2026-05-22.

Verified this commit:
- ruff check poc/ssrf/ : clean
- quick_probe.py end-to-end smoke: pre-fix returns AKIA-FAKE,
  post-fix returns the refusal message, exit code matches
  --exit-nonzero-on-vuln flag
- Makefile 'make help' renders the target list correctly

Not yet verified (deferred to user; Docker daemon was down throughout):
- 'make demo-full' end-to-end with the actual mcp-server-fetch v2025.4.7
  package against the containerized 169.254.169.254 IMDS mock. The
  docker-compose.yml + Dockerfiles + attack.py are syntactically and
  logically validated; the first end-to-end run will be on the user's
  machine after starting Docker Desktop.

Strategic value:
- mcp-witness reproduction-harness collection now covers both
  disclosed vulnerability classes (DNS rebinding via poc/dns-rebind/
  + SSRF via poc/ssrf/), closing the asymmetry where one class had a
  one-command repro and the other required AWS infrastructure.
- The harness collection itself becomes a citable artifact independent
  of the scanner. Anyone reading the disclosure record can run the
  full reproduction in ~60 seconds without an AWS account.
- Pairs naturally with the docs/detector-evolution-s014.md +
  docs/methodology-soft-escalation.md writeups as the third leg of
  the 'one of one' positioning: disclosure clearinghouse + worked-
  example methodology writeups + runnable reproduction harnesses.
@desledishant10

Copy link
Copy Markdown

Per my offer earlier on this PR, here's the independent re-verification, 39 days post-disclosure.

I built a containerized PoC harness that drives the real mcp-server-fetch package against a mock IMDS at the canonical 169.254.169.254 metadata IP. Three states verified today:

State Source spec Result
Pre-fix (disclosed version) mcp-server-fetch==2025.4.7 VULNERABLE fake IAM credentials returned via JSON-RPC
Latest PyPI release mcp-server-fetch==2026.6.4 (uploaded 2026-06-04) Still VULNERABLE the fix never landed in a release
This PR's branch refs/pull/4226/head FIX VERIFIED "Fetching private or non-public IP addresses is not allowed"

Harness: https://github.com/desledishant10/mcp-witness/tree/main/poc/ssrf (no AWS account required; make demo-full reproduces in ~5 seconds after first build, make demo-fixed installs from this PR's branch directly).

The fix shape (scheme allowlist + reserved-range denylist + per-redirect validation) is correct against the original disclosure. CI is green (16/16 checks), there's been an outside review from @LuuOW, and the fix has now been independently re-verified against the link-local cloud-metadata vector specifically.

Happy to help with anything else needed for merge additional tests, rebase, review notes, anything useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: mcp-server-fetch lacks SSRF protection; cloud-hosted agent hosts can leak IAM credentials

3 participants