# REPRO-2026-00318: mcp-toolbox authorization bypass: unauthenticated tool invocation via direct HTTP API ## Summary Status: published Severity: high CVSS: Unknown CWE: CWE-863 (Incorrect Authorization) (Incorrect Authorization) Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00318 CVE: CVE-2026-14537 ## Package Name: googleapis/genai-toolbox Ecosystem: github Affected: >= v1.3.0 (2026-05-21) and <= v1.4.0 (2026-06-04) Fixed: v1.5.0 (2026-06-18) ## Root Cause # CVE-2026-14537 — Root Cause Analysis ## Summary google/mcp-toolbox (repository `googleapis/genai-toolbox`) versions v1.3.0–v1.4.0 suffer from an incorrect-authorization vulnerability (CWE-863). When the server is configured with an MCP-enabled authorization service (`mcpEnabled: true`, OAuth scopes via `scopesRequired`) **and** the legacy direct HTTP API is enabled (`--enable-api`), the legacy endpoint `POST /api/tool/{toolName}/invoke` executes tools without enforcing the MCP authorization policy. An unauthenticated remote attacker can invoke tools that the operator believes are protected by OAuth scopes, because scope enforcement exists only on the `/mcp` endpoint path. ## Impact - Component: `internal/server/api.go` (`toolInvokeHandler`) together with `internal/server/server.go` (`mcpAuthMiddleware` mounted only under `/mcp`). - Affected versions: v1.3.0 (2026-05-21) through v1.4.0 (2026-06-04). - Fixed in: v1.5.0 (2026-06-18), fix commit `a6ff910a602adece11f0a6581d6211e5927f7182` ("fix(server): fail if MCP auth is enabled together with enable-api (#3435)"). - Risk: high (CVSS 4.0 8.1). Any tool protected solely by the MCP authorization model (an `mcpEnabled` authService plus tool-level `scopesRequired`, with no legacy `authRequired`) is remotely invocable with no credentials at all, including destructive tools (e.g. `sqlite-execute-sql`, SQL execution tools against production databases). ## Impact Parity - Disclosed/claimed maximum impact: authorization bypass — unauthenticated remote invocation of scope-protected tools via the direct HTTP API (`expected_impact=authz_bypass`, surface `api_remote`). - Reproduced impact in this run: identical — HTTP 200 and actual tool execution (arbitrary SQL against the configured SQLite source) via `POST /api/tool/protected-tool/invoke` with no `Authorization` header, while the same unauthenticated caller receives HTTP 401 on `/mcp`. - Parity: `full`. - Not demonstrated: nothing beyond the claimed impact (no code execution was claimed or required). ## Root Cause Authorization in mcp-toolbox v1.3.0/v1.4.0 is split across two independent enforcement points: 1. **MCP path** (`/mcp`): `mcpAuthMiddleware` (`internal/server/server.go`) validates the Bearer token via `ValidateMCPAuth` (including authService `scopesRequired`), and the MCP `tools/call` handler additionally enforces tool-level scopes through `mcputil.ValidateScopes(ctx, tool.GetScopesRequired(), ...)` (`internal/server/mcp/v20250618/method.go`). 2. **Legacy HTTP API path** (`/api`, enabled by `--enable-api`): the router in `internal/server/api.go` has **no** MCP auth middleware, and `toolInvokeHandler` only enforces the legacy `authRequired` mechanism (`tool.Authorized(verifiedAuthServices)`), which returns `true` unconditionally when a tool declares no `authRequired` (`IsAuthorized`: "no authorization requirement"). Tool-level `scopesRequired` is never consulted on this path. Consequently, a tool protected only by the modern MCP scope model (`scopesRequired`, no legacy `authRequired`) is fully open on the legacy HTTP API: the unauthenticated request passes `IsAuthorized([])` and the tool executes. The fix in v1.5.0 does not add scope checks to the legacy endpoint; instead it makes the dangerous configuration fail closed at startup: `cmd/root.go` and `internal/server/server.go` (`InitializeConfigs`) refuse to run when any authService `IsMCPEnabled()` and `EnableAPI` are both set ("MCP Auth cannot be enabled together with the legacy HTTP API"), and a new `IsMCPEnabled()` method was added to the `AuthServiceConfig` interface for that check. Fix commit: `a6ff910a602adece11f0a6581d6211e5927f7182` (PR #3435). ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh` (self-contained; installs Go 1.26.3 if needed, clones/uses the prepared repo cache, builds the real product at v1.4.0 and v1.5.0). 2. The script starts a local OIDC authorization-server stub (`bundle/repro/oidc_stub.py`), then launches the real v1.4.0 server with `bundle/repro/tools.yaml` (generic authService `mcpEnabled: true` + `scopesRequired: [read:files]`; tool `protected-tool` of type `sqlite-execute-sql` with `scopesRequired: [execute:sql]` and **no** `authRequired`) and flags `--enable-api --toolbox-url --port 5000`. 3. It then sends the attacker request `POST /api/tool/protected-tool/invoke` with body `{"sql": "SELECT 'CVE-2026-14537-PWNED' AS marker"}` and **no** `Authorization` header, followed by a contrast request to `/mcp` without a token, and finally attempts to start v1.5.0 with the identical config and flags. Expected evidence: - v1.4.0 legacy API: HTTP 200 with the marker `CVE-2026-14537-PWNED` in the JSON response (tool executed unauthenticated) — **vulnerable**. - v1.4.0 `/mcp`: HTTP 401 with a `WWW-Authenticate` challenge — the MCP path enforces authorization correctly. - v1.5.0: exits at startup logging "MCP Auth cannot be enabled together with the legacy HTTP API" and never serves the API — **fixed (fail closed)**. ## Evidence - `bundle/logs/reproduction_steps.log` — full run transcript. - `bundle/logs/vuln_server.log` — v1.4.0 server startup (MCP auth + legacy API both active). - `bundle/logs/vuln_api_invoke_status.txt` / `vuln_api_invoke_body.json` — HTTP status and response body of the unauthenticated invoke (200 + marker). - `bundle/logs/vuln_mcp_noauth_status.txt` / `vuln_mcp_noauth_body.json` — 401 from `/mcp` without a token (contrast control). - `bundle/logs/fixed_server.log` — v1.5.0 startup refusal message. - `bundle/logs/oidc_stub.log` — OIDC discovery requests made by the server at startup (proves the real MCP auth stack was initialized). - `bundle/repro/runtime_manifest.json` — structured runtime evidence manifest. Environment: linux/amd64, Go 1.26.3, mcp-toolbox built from source at tags v1.4.0 (d67cfbe8ddc) and v1.5.0, python3 OIDC stub on 127.0.0.1:8099. ## Recommendations / Next Steps - Upgrade to v1.5.0 or later; do not run `--enable-api` together with MCP-enabled authorization services on affected versions. - Operators on v1.3.0/v1.4.0 who must keep the legacy API should add explicit legacy `authRequired` entries to every tool (the legacy mechanism is still enforced on `/api`), or front the server with a proxy that blocks `/api`. - Long-term: the legacy `/api` endpoints are deprecated; migrate clients to the standard `/mcp` JSON-RPC endpoint where MCP authorization is enforced. ## Additional Notes - The reproduction is idempotent: the script rebuilds only when the resolved commit changes, restarts all services on each run, and cleans up background processes via a trap. - Edge cases: tools that DO declare legacy `authRequired` referencing the mcpEnabled generic authService are *not* bypassed on `/api` (claims come from the MCP context, which is empty there, yielding 401); the bypass applies to tools protected only via the MCP scope model, which is the configuration the fix commit targets ("clients could potentially bypass MCP authorization policies by using the legacy HTTP API"). - No public PoC existed; this reproduction was built from the fix-commit analysis of PR #3435. ## Reproduction Details Reproduced: 2026-08-01T05:51:48.163Z Duration: 2266 seconds Tool calls: 197 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00318 pruva-verify CVE-2026-14537 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00318&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00318/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-14537 - Source: https://nvd.nist.gov/vuln/detail/CVE-2026-14537 ## Artifacts - bundle/repro/rca_report.md (analysis, 7290 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 11390 bytes) - bundle/logs/fixed_server.log (log, 303 bytes) - bundle/logs/oidc_stub.log (log, 161 bytes) - bundle/logs/reproduction_steps.log (log, 1564 bytes) - bundle/logs/vuln_api_invoke_body.json (other, 53 bytes) - bundle/logs/vuln_api_invoke_status.txt (other, 4 bytes) - bundle/logs/vuln_mcp_noauth_body.json (other, 85 bytes) - bundle/logs/vuln_mcp_noauth_status.txt (other, 4 bytes) - bundle/logs/vuln_server.log (log, 3343 bytes) - bundle/repro/oidc_stub.py (script, 2467 bytes) - bundle/repro/runtime_manifest.json (other, 824 bytes) - bundle/repro/tools.yaml (other, 1142 bytes) - bundle/repro/validation_verdict.json (other, 868 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00318 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00318/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/reproductions/REPRO-2026-00318 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev