Repository navigation
Security: limit tool-call input elements, require MCP SDK 1.32.0, ip-address 10.7.4 - #896
Merged
Merged
Conversation
…address 10.7.4 Tool calls: the MCP SDK can count the array elements and object keys in a tool call's arguments before it validates them, but only when the server sets a limit (GHSA-46jr-4wx8-v453), and Iris set none. Arguments that fail validation are reported element by element, so one 1 MB request of 524,229 elements cost the server 260 MB, and 20 requests of 1 MB at once up to 1,148 MB and 41 s to answer. Every MCP server Iris builds, on stdio and HTTP, now sets security.maxToolInputElements (default 100,000; also IRIS_MAX_TOOL_INPUT_ELEMENTS). A call over it gets an isError result before validation and the session goes on. The default clears every legitimate input measured, even one filling the whole 1 MB request (the densest shape found holds 68,506 elements at 1 MB). SDK floor: ^1.30.0 -> ^1.32.0. A published package does not carry the lockfile, so an install that already held 1.30.x or 1.31.x kept it; those versions are affected by six advisories, and an older SDK ignores the new option. The lockfile already installed 1.32.0. ip-address: overrides floor ^10.5.1 -> ^10.7.4 (GHSA-rxp7-6hxx-x8q5); lockfile 10.7.2 -> 10.7.4, regenerated on Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Iris gate — 1 of 2 tripped
|
| Trace | Verdict | Basis | Rules, classes or missing inputs | Evidence |
|---|---|---|---|---|
6a93ac4da21f79280e4e1c769fdf5b3f |
failed | detector_veto + risk_over_loss |
no_pii, pii_leak, credential_leak | no_pii: AWS Access Key (output 45–65) |
| Verdict basis | Traces |
|---|---|
detector_veto |
2 |
clean |
1 |
Unjudged questions: task_completed (3), tool_use_correct (3) — a trace that did not carry what a rule needs.
tests/fixtures/ci-gate/traces.ndjson · 3 evaluated · dataset release-gate: 2 in the gate · exit 1 · what the bases mean
Iris gate — 1 stored, nothing tripped
|
| Verdict basis | Traces |
|---|---|
clean |
1 |
Unjudged questions: task_completed (1), tool_use_correct (1) — a trace that did not carry what a rule needs.
tests/fixtures/ci-gate/clean.ndjson · 1 evaluated · exit 0 · what the bases mean
This branch was successfully deployed
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.
Three dependency-security changes in one pull request: a limit on how many array elements and object keys a tool call's arguments may hold, a higher floor for the MCP SDK, and a patched
ip-address.What changes
1. A tool call's arguments may hold at most 100,000 array elements and object keys, on every transport.
GHSA-46jr-4wx8-v453 (high): the SDK validated every element of a tool call's arguments with no limit on how many, and its fix is an
McpServeroption,maxToolInputElements, that is off by default. Upgrading the SDK alone changes nothing. Iris now sets it on every MCP server it builds: the one stdio serves and the one each HTTP session gets (src/server.ts). The SDK counts the elements before it validates, so a call over the limit gets anisErrorresult naming the limit, never reaches the tool, and the session goes on.security.maxToolInputElementsinconfig.json, orIRIS_MAX_TOOL_INPUT_ELEMENTS: a whole number of at least 1, default100000. Anything else refuses startup, naming the setting.--help, the README's environment-variable table and security list,server.json, anddocs/api-reference.md.createIrisServerdirectly) still gets the default.2.
@modelcontextprotocol/sdkfloor^1.30.0→^1.32.0.A published package does not carry Iris's lockfile, so a project that already held SDK 1.30.x or 1.31.x kept it when it installed Iris. Those versions are affected by GHSA-46jr-4wx8-v453, GHSA-22jm-h49p-29qw, GHSA-rvq5-wwqv-78pq and GHSA-6prh-2h8m-c8cw; 1.30.x also by GHSA-6qxp-vccf-f47h, and 1.30.0 by GHSA-f897-62hf-6gxq. An SDK older than 1.32.0 also ignores the option in part 1. The lockfile already installed 1.32.0 and stays there (1.32.1 changes documentation only).
3.
ip-address10.7.2 → 10.7.4,overridesfloor^10.5.1→^10.7.4.GHSA-rxp7-6hxx-x8q5 (moderate):
Address6strips a zone identifier in quadratic time before its length limit. Iris reachesip-addressonly throughexpress-rate-limit, which parsesreq.ip. Iris leaves Express'strust proxyoff, soreq.ipis the socket's own address and a client cannot hand it a long string. Recorded inSECURITY-EXPOSURE.mdwith the SDK change.Why 100,000
The bytes were already bounded:
security.requestSizeLimit(1 MB) applies to HTTP and stdio alike. The element count was not, and it is what costs memory. Arguments that fail validation are reported element by element, at 63 to 148 characters of error text per element, so the reply to a small request can be many times its size.All counts below use the SDK's own counting (array elements plus object keys, as in
toolInputElementCountin@modelcontextprotocol/sdk/dist/esm/server/mcp.js).Legitimate inputs. These set how high the limit must be.
proof/corpus/max_steps.jsontools/listresult passed astoolsWhat an attacker can send. A 1 MB request carries at most 524,229 elements (an array of zeros). One such request cost the server 260 MB.
Twenty requests at once, the MCP endpoint's default per-minute limit for one client: server peak RSS above its baseline after garbage collection, and the time until all 20 were answered. The payload is the worst one found under each limit. A call over the limit is refused before validation, so the worst case sits just under it.
spansof zerosspansof empty objects100,000 is the first power of ten above the densest legitimate shape at the full 1 MB, so no legitimate input measured can reach it within the default request size. It cuts the worst burst's memory by 40 to 55% and its time about fourfold. It also bounds the cost per call when an operator raises
requestSizeLimit, which until now raised the element ceiling with it. An operator whose HTTP endpoint is reachable beyond loopback can go lower. At 10,000 the burst costs about 250 MB and 1 s, but a trace shaped like the real agent transcripts would be refused somewhere past 700 KB, and atools/list-shaped one past about 300 KB.Why both transports. The advisory says stdio-only servers are not affected. The limit still applies to stdio too, for three reasons. Iris builds stdio and HTTP servers from one factory. Since 0.18.0 one request size limit holds on both, so that a trace is never accepted on one transport and refused on the other. And at the default, no legitimate input measured can reach the limit, so stdio clients see no change.
Measured on Windows 11, i9-12900HK, Node 24.11.0, three runs each (median; the 60,000-object case varied from 530 to 690 MB). To reproduce:
node --import tsx scripts/bench-tool-input-elements.ts(options--limits,--concurrency,--runs,--elements). It is a script, not a test, so the default suite stays fast.Tests
tests/integration/tool-input-elements.test.ts, run on the server stdio serves and over real HTTP:isError: true, stores nothing, and the session keeps servingFive of the six fail with the option removed from
src/server.ts. The sixth, the legitimate 1 MB trace, passes either way, as it should.tests/unit/config-tool-input-elements.test.ts: the default; the file and the environment, with the environment winning; values that refuse startup (0,-1,1.5,1e5,lotsand10,000in the environment;0,-5,2.5,"100000"andnullin the file).tests/unit/middleware/*: their literal security configs gain the new key.Lockfile
Regenerated on Linux (
node:24, npm 11.19.0) withnpm install --package-lock-only --ignore-scripts --no-audit --no-fund. Two places changed: the root package's declared SDK range, and the version,resolvedandintegrityofip-address. No entry was added or removed (git diff origin/main -- package-lock.json | grep -cE '^-\s+"node_modules/'= 0). The counts oflibcfields (10), integrity hashes (392) and@emnapientries (0) are unchanged.npm auditat the root now reports only GHSA-86w9-cpqp-85rv (node-forge, dev tooling, already recorded), andscripts/security/check-exposure-coverage.mjspasses.🤖 Generated with Claude Code