MCP Toolbox, package argument, and Node permission boundaries¶
A July 31 advisory wave yields six reusable operator checks: an MCP OAuth verifier can accept tokens minted for an unrelated audience; a legacy HTTP route can omit tool scopes; dataset policy can fail open on an empty dry-run result; redirect following can move an approved HTTP tool to a different authority; a package name can become an apt-get option; and a diagnostic report API can write beyond a runtime filesystem policy.
Sources:
- Google MCP Toolbox GHSA-656w-qf75-c5gf / CVE-2026-14541: the record describes Google OAuth access tokens being accepted without audience validation when
mcpEnabled: trueis combined with no explicitaudienceorclientId; - Google MCP Toolbox GHSA-f8h2-c55w-8m5p / CVE-2026-14537: the record says legacy direct-HTTP invocation routes bypassed
scopeRequiredwhen--enable-apiwas active in versions 1.3.0 and 1.4.0; - Google MCP Toolbox GHSA-24pp-m59v-92j8 / CVE-2026-14538: the record reports fail-open
allowedDatasetsvalidation when a BigQuery dry run returned an empty reference array for specialized query forms; - Google MCP Toolbox GHSA-3x3x-8ffg-ghcv / CVE-2026-14540: the record reports missing redirect and destination-IP controls in generic HTTP sources and tools through 1.4.0;
- yggdrasil-worker-package-manager GHSA-f9rr-qg3x-wq4m / CVE-2026-18157: the record describes hyphen-prefixed package names being interpreted as
apt-getoptions; and - Node.js GHSA-4qw5-6jhx-9hfx / CVE-2026-58039: the record says
process.reportcould overwrite files outside paths granted by--allow-fs-writein affected 22.x, 24.x, and 26.x releases.
These were unreviewed GitHub Advisory Database records at scan time. Treat versions and claimed behavior as source leads. Reproduce the exact artifact and configuration, identify the reachable sink, and compare against a fixed release before reporting.
Recorder-only labs
Use fake OAuth tokens, disposable MCP tools, a mocked BigQuery client, two owned HTTP listeners, an argument-recording package backend, and a temporary Node filesystem. Never replay real Google tokens, query production datasets, follow redirects toward internal services, install attacker-selected packages, overwrite shell or service configuration, or read files through a write-only proof.
Boundary matrix¶
| Surface | Attacker-controlled value | Authority transition | Bounded positive |
|---|---|---|---|
| MCP OAuth | otherwise valid token with a foreign aud | token validity becomes toolbox authorization | foreign-audience canary reaches one no-op tool while a correct-audience control also succeeds |
| Direct HTTP API | route family and tool name | legacy handler invokes a scoped MCP tool | missing-scope user reaches a marker-only tool only through the legacy route |
| BigQuery policy | specialized query form | empty dry-run references become policy approval | denied synthetic dataset is selected by a recorder despite an allowlist |
| Generic HTTP tool | path/URL input and redirect target | approved initial authority becomes final request authority | owned listener A redirects to owned listener B and B receives the canary |
| Package manager | hyphen-prefixed package token | package data becomes apt-get option grammar | recorder shows the token parsed as an option without running apt-get |
| Node permission model | report filename/path | diagnostic report writer bypasses filesystem grant | report marker appears in a disposable denied sibling directory |
Keep each edge separate. Token acceptance is not data access until a protected tool runs. A route returning 200 is not a scope bypass without the missing-scope control. An empty dry-run result is not dataset access until the execution sink receives the query. A redirect response is not SSRF until the final owned listener is contacted. A suspicious package string is not command execution unless option parsing changes. A report-path bypass is a write-policy finding, not arbitrary file read.
MCP OAuth audience-confusion differential¶
Preconditions¶
- exact MCP Toolbox build under test;
- Google
authServicewithmcpEnabled: true; - one recorder-only protected tool;
- two disposable OAuth clients or a local verifier fixture that can mint signed canary claims; and
- authorization to test the identity configuration.
Procedure¶
- Record whether
audienceorclientIdis explicit in the auth-service configuration. - Mint three non-sensitive canaries: valid issuer/signature plus expected audience, valid issuer/signature plus foreign audience, and invalid signature plus expected audience.
- Submit each token through the same MCP transport and tool call.
- Record token-verification outcome separately from tool invocation.
- Repeat after setting an explicit expected audience and, where available, against a fixed build.
| Token | Signature/issuer | Audience | Expected secure result |
|---|---|---|---|
| positive control | valid | expected toolbox client | no-op tool allowed |
| audience probe | valid | unrelated owned client | rejected before tool dispatch |
| signature control | invalid | expected toolbox client | rejected before tool dispatch |
A useful finding requires the foreign-audience token to cross the protected dispatch boundary. Do not report merely that an access token can be parsed.
Route-family scope coverage¶
Build a route-by-auth matrix for the same tool:
| Route family | No token | Valid token, missing scope | Valid token, required scope |
|---|---|---|---|
| MCP transport | reject | reject | invoke marker |
legacy API with --enable-api | reject | reject | invoke marker |
| API disabled | unavailable | unavailable | unavailable |
Capture handler identity, authorization middleware reached, status, and recorder count. The positive is a missing-scope principal incrementing the tool recorder only through the legacy route. This distinguishes policy drift from harmless status-code differences.
BigQuery dry-run fail-open testing¶
Use a mocked BigQuery adapter or a throwaway project containing only allowed_lab.marker and denied_lab.marker.
- Configure
allowedDatasetsto include onlyallowed_lab. - Establish controls: a direct allowed-table query is accepted and a direct denied-table query is rejected.
- Feed the validator specialized query classes one at a time, including
INFORMATION_SCHEMAreferences and a mocked federated-query construct. - Instrument the dry-run result and execution call. Force or observe an empty referenced-table array without contacting a real external connection.
- The bounded positive is execution receiving a query that names
denied_labafter the validator treated an empty reference set as approval. - On a fixed control, an empty or indeterminate reference set must fail closed unless an independent parser proves every referenced dataset is allowed.
Do not extract schema or row data. A recorder containing the synthetic denied dataset name is sufficient evidence.
Redirect final-destination proof¶
Use two isolated owned listeners:
listener A: approved initial authority; replies 302 to listener B
listener B: distinct owned authority; records method, path, and inert header names
Test direct input and redirect input separately. Vary only one boundary at a time: hostname, resolved address class, port, or scheme. Preserve the complete redirect chain and prove which request attributes are forwarded. Do not target metadata, loopback services belonging to other software, private production ranges, or third-party endpoints.
The report should state whether policy was applied to the initial URL, every redirect hop, and the final resolved destination. An open redirect on listener A alone is not evidence that Toolbox followed it.
Package token to option grammar¶
Replace the APT backend with an argument recorder or intercept the child-process constructor before execution.
- Submit a normal synthetic package name and preserve the resulting argv array.
- Submit a hyphen-prefixed inert token such as
--skillz-canary. - Confirm whether the backend inserts
--before untrusted package operands. - Compare raw argv with the option parser's interpretation; do not infer parsing from the source string alone.
- The positive is a package operand occupying option position or changing parsed option state. Stop before package installation or any option that reads configuration, runs hooks, changes roots, or selects a network source.
A fixed control should reject invalid package grammar and use an end-of-options delimiter where supported.
Node diagnostic-report write confinement¶
Run the affected Node build in a temporary tree:
Grant writes only to allowed/, invoke the reachable process.report write API with a path under denied/, and check only whether a random report marker file was created or replaced. Use a dedicated process and disposable files. Repeat with ordinary fs.writeFile as the denied control and with a fixed Node release.
Capture the exact Node version, permission flags, requested path, canonical path, pre/post hashes of the disposable target, and exit status. Do not target startup files, package manifests, service units, credentials, or application data.
Evidence and reporting¶
For each finding preserve:
- exact package/runtime version and configuration;
- attacker prerequisite and transport/route used;
- raw token claims, argv, redirect chain, query-validator result, or canonical path with secrets removed;
- the guard decision and the separate sink-side recorder event;
- positive, negative, and fixed-version controls; and
- the narrowest demonstrated impact.
Prefer boundary-specific titles such as “legacy API omits required tool scope” or “diagnostic report writer escapes permission-model write grant.” Do not collapse the six workflows into generic “MCP RCE,” “SSRF,” or “sandbox escape” claims.