fix(mcp): block full IPv6 link-local range fe80::/10 in SSRF check#1326
Merged
threepointone merged 2 commits intomainfrom Apr 17, 2026
Merged
fix(mcp): block full IPv6 link-local range fe80::/10 in SSRF check#1326threepointone merged 2 commits intomainfrom
threepointone merged 2 commits intomainfrom
Conversation
isBlockedUrl documented its intent as fe80::/10 but the previous
startsWith("fe80") check only covered fe80::/16, letting valid
link-local addresses in the fe81::..febf:: range slip through.
Replace the prefix match with a regex anchored to the full /10 boundary
(first hextet fe80..febf), factor IPv6 range logic into isPrivateIPv6
for symmetry with isPrivateIPv4, and add regression tests for the
previously-leaking prefixes plus negative cases at the /10 boundary
(fe7f::, fec0::). The existing fe80::1%25eth0 test was passing via
the malformed-URL catch branch rather than the regex; split it into
two tests so the regex path is exercised unambiguously.
Fixes #1325
Made-with: Cursor
🦋 Changeset detectedLatest commit: 1d7cc8a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Introduce waitForOverlappingSubmits test helper and use it across message-concurrency tests to deterministically wait for overlapping submits before asserting which turns ran. Expose getOverlappingSubmitCountForTest() on SlowStreamAgent to return the agent's internal _latestOverlappingSubmitSequence so tests can observe overlapping submit counts (note: the first submit on an empty queue is not counted). Also increase chunkCount and chunkDelayMs for several test streams to reduce flakiness under CPU pressure.
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
isBlockedUrlinpackages/agents/src/mcp/client.tsclaimed to blockfe80::/10(per the inline comment) but the previousstartsWith(\"fe80\")check only coveredfe80::/16— the first hextet is the first 16 bits, while/10fixes only the first 10 bits. Addresses with first hextetsfe81..febfare valid RFC 4291 §2.5.6 link-local and were slipping past the SSRF defense.Fix: replace the literal prefix match with a regex anchored to the true
/10boundary (only hex digits8,9,a,bhave high two bits10), factor the IPv6 range checks into anisPrivateIPv6helper for symmetry withisPrivateIPv4, and document the bit-math inline so this doesn't drift again.Impact
169.254.0.0/16rule still covers the main cloud-metadata vector (EC2/GCE); this PR closes a narrower gap that was real per RFC but low-exploitability.Tests
fe81::1,feab::1,febf::1,FE8F::1(case canonicalization).fe7f::1,fec0::1.fe80::1%25eth0test was passing via the "malformed URL" catch branch rather than the regex (zone IDs are rejected by the WHATWG URL parser in both Node and Workers). Split into two tests so the regex path is exercised unambiguously, and kept the zone-ID case as explicit malformed-URL coverage.SSRF test count: 19 → 26 in the describe block. All 141 tests in
client-manager.test.tspass.npm run typecheckclean across all 73 projects. Oxlint clean.Review notes (not in this PR)
While reviewing I found a few adjacent edge cases worth recording but deliberately out of scope:
[::192.168.1.1]canonicalizes to[::c0a8:101]and is not blocked. Near-zero exploitability (it's a pure IPv6 address in::/96, not translated to IPv4 on modern stacks), but a theoretical gap worth a separate hardening PR if we want belt-and-suspenders.\"follow\"redirect mode. Separate design discussion if we want to address.::ffff:IPv4-mapped branch: the WHATWG URL parser does not canonicalize hex-form tails to dotted form (I verified empirically), so the hex branch is the primary path, not defense-in-depth as the comment previously claimed.Reported in #1325.
Test plan
npx vitest run src/tests/mcp/client-manager.test.ts -t \"SSRF URL validation\"→ 26 passnpx vitest run src/tests/mcp/client-manager.test.ts→ 140 pass (full file)npm run typecheck→ 73/73 projects cleannpx oxlinton changed files → 0 warnings, 0 errorsnpm run formatappliedpatchonagentsCloses #1325
Made with Cursor