GHSA-953h-535j-jg2v: agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
The MCP proxy in Envoy AI Gateway (`github.com/envoyproxy/ai-gateway` 0.4.0-rc1 up to, but not including, 1.2.0-rc1) lets any caller resume another user's MCP session if they have its `Mcp-Session-Id`. The Helm chart's default encryption seed, `default-insecure-seed`, is public, so an attacker can forge session IDs. Upgrade to 1.2.0-rc1 and set a random seed.
Published Oct 19, 2026 · Analysis Oct 21, 2026
What an attacker can do
The client-facing session ID is an encrypted blob that embeds the caller's JWT sub as an anti-hijacking check. The resume path never uses it. In internal/mcpproxy, backendSessionIDs() parses the subject and discards it, and sessionFromID() never receives the current caller's subject. extractSubject(r) runs only when the session is created.
The chart made it worse. controller.mcp.sessionEncryption.seed defaults to default-insecure-seed, and the binaries use the same default when the flag is omitted. Session IDs are salt || nonce || ciphertext under AES-GCM, so anyone who knows the seed and the default PBKDF2 iteration count can decrypt IDs and encrypt new ones for any subject and backend session.
An attacker who is another authenticated user, or who holds a captured ID, can send tools/call or resources/read through the victim's initialized backend session. On a multi-tenant gateway that crosses the tenant boundary. The advisory reports a code-level reproduction, not one run on a live cluster.
Who is affected
Affected: github.com/envoyproxy/ai-gateway >= 0.4.0-rc1 and < 1.2.0-rc1, with an MCPRoute in use. The forging path applies when controller.mcp.sessionEncryption.seed is still default-insecure-seed, in the chart's values.yaml or the flag default.
go list -m github.com/envoyproxy/ai-gateway
helm get values <release> -a | grep -A2 sessionEncryptionFor a running deployment, check the controller args for --mcpSessionEncryptionSeed.
github.com/envoyproxy/ai-gateway
- Ecosystem
- Go
- Affected
- >= 0.4.0-rc1, < 1.2.0-rc1
- Fixed in
- 1.2.0-rc1
- Check
go list -m github.com/envoyproxy/ai-gateway
How to fix it
Upgrade to github.com/envoyproxy/ai-gateway 1.2.0-rc1, which carries the fix referenced in the advisory's pull requests 2740 and 2755. Set controller.mcp.sessionEncryption.seed to a long random secret of your own rather than the default, and restart the controller so old session IDs stop working.
The advisory suggests the fix should reject a resume when the decrypted subject differs from the current caller's, and that the chart should not ship a fixed seed. The sources do not list further workarounds.
How to tell if you were hit
Look in gateway logs for MCP requests where the Mcp-Session-Id was issued to one JWT subject but is used with a different sub. Look for sessions resumed from new client addresses, and tools/call or resources/read calls you cannot match to the owning user. Check your Helm values and controller flags for default-insecure-seed. If it was in use, treat existing session IDs as forgeable.
Beam records agent MCP tool calls, which helps review what an agent invoked through a gateway, but it cannot see server-side session reuse.
Original advisory
From the GitHub Advisory Database (CC BY 4.0).
Summary
The MCP proxy's client-facing session ID embeds the authenticated caller's subject (JWT sub claim) specifically as an anti-hijacking discriminator — matching the Model Context Protocol specification's documented session-hijacking guidance (https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#session-hijacking). However, the session-resume path never checks that discriminator against the current caller's identity, so any client that obtains a session ID (its own or another user's) can resume that session and act with it — including on routes with per-user backend sessions. This is aggravated by the official Helm chart shipping a literal, publicly known default seed ("default-insecure-seed") for the key that encrypts these session IDs.
Impact
An attacker who obtains (or, under the default seed, forges) another user's Mcp-Session-Id value can resume that user's MCP session against the backend MCP server(s) bound to the route — reading conversation/tool-call state and invoking further JSON-RPC methods (e.g. tools/call, resources/read) in the context of that other user's already-initialized backend session. On a multi-tenant AI Gateway deployment, this crosses the exact trust boundary the subject-embedding was added to protect.
Location
internal/mcpproxy/session.go—clientToGatewaySessionID.backendSessionIDs()(around line 707-765): parses{routeName}@{subject}@{backends}and returns only(perBackendSessionIDs, route, error). The comment at the subject-parsing site reads: "The subject ... is retained inside the encrypted session ID for anti-hijacking purposes but is not needed during parsing." — the subject is discarded, never returned, never compared.internal/mcpproxy/mcpproxy.go—sessionFromID(id, lastEvent)(around line 282-323): resumes a session purely from the decrypted blob; it is never passed the current request's authenticated subject, so there is nothing to compare against even ifbackendSessionIDs()did return it.internal/mcpproxy/legacy.go—extractSubject(r)(around line 1549-1562) is called exactly once, only at session creation (handleInitializeRequest->newSession, line ~307/484/491) to embed the subject when the session ID is minted. All three session-resume call sites (serveGET/servePOST/serverDELETEpaths, lines ~133, 167, 216) callsessionFromIDwith no subject argument at all.internal/mcpproxy/mcpproxy.goline 259 (clientToGatewaySessionIDFromEntries) is where the subject is embedded at creation, confirming the intended design.- Aggravating factor:
manifests/charts/ai-gateway-helm/values.yamlline 297 shipsseed: "default-insecure-seed"as the out-of-the-box value forcontroller.mcp.sessionEncryption.seed, whichtemplates/deployment.yamlpasses straight to--mcpSessionEncryptionSeed(also literally defaulted to"default-insecure-seed"incmd/controller/main.goandcmd/extproc/mainlib/main.gowhen the flag is omitted entirely). Session IDs aresalt || nonce || ciphertext(seeinternal/mcpproxy/crypto.go), so knowing the seed and the (also-defaulted) PBKDF2 iteration count is sufficient to derive the AES-GCM key and both decrypt observed session IDs and encrypt new ones for an arbitrary chosen subject/backend-session combination.
Reproduction (code-level, not run against a live cluster)
- Deploy Envoy AI Gateway with an
MCPRoutethat has an OAuth/JWTSecurityPolicyattached (sointernalapi.MCPSubjectHeaderis populated) and at least one backend MCP server, using the Helm chart's defaultvalues.yaml(controller.mcp.sessionEncryption.seedleft at"default-insecure-seed"). - User A authenticates with a valid JWT (
sub: user-a) and sends aninitializeJSON-RPC request to the route. The gateway creates backend session(s) and returnsMcp-Session-Id: <encrypted-blob-A>, whose plaintext is{route}@user-a@{backend}:{base64(sessionID)}:{capHex}. - Attacker (any other authenticated user, or — since the seed is public and default — even without ever having a legitimate session at all) constructs
{route}@user-a@{backend}:{base64(<any-or-known-backend-session-id>)}:fff, derives the AES-GCM key via PBKDF2(sha256, "default-insecure-seed", salt, 100000, 32) for a salt of their choosing, and encrypts it themselves to produce a session ID that decrypts to the same structuresessionFromIDexpects. - Attacker sends a subsequent JSON-RPC request (e.g.
tools/call) to the same route with headerMcp-Session-Id: <forged-or-captured-blob-A>. The gateway decrypts it, extractsperBackendSessionIDsforbackend, and forwards the attacker's call using that backend session — with no comparison to the attacker's own JWT subject at any point insessionFromID/backendSessionIDs().
Suggested fix
- In
sessionFromID, thread the current request'sextractSubject(r)value through tobackendSessionIDs()(or a wrapping call) and reject resumption (401/403) when the decrypted subject does not match the current caller's subject — restoring the anti-hijacking check the embedding was built for. - Independently, change the Helm chart's default
controller.mcp.sessionEncryption.seedto be randomly generated per install (e.g. via a Helmlookup/randAlphaNumin a pre-install hook, or a required value with no default), rather than shipping a literal, fixed string. At minimum, fail startup (rather than just logging) when the seed equals the documented insecure default in a non-dev/insecure-allowed mode.
Detected by
Aeon vuln-scanner — agentic dataflow review (internal/mcpproxy entrypoint, Envoy AI Gateway data-plane MCP proxy), followed by manual adversarial verification against the exact HEAD commit of main (472da7db7984f769c9a77671d12c0354e75fc3ba, confirmed to match repos/theagentrouter/agent-router/commits/main at time of report). Dependency-CVE findings from this scan (osv-scanner, go.mod/package-lock.json) are not included here — they are all already covered by open Dependabot PRs (#2555, #2643, #2672, #2497, #2615) and are omitted to avoid duplicate filings.
Credited to aeonframework, nacx, PraveenKumarInjam, kanywst.
Questions
- Is the gateway vulnerable if I changed the seed?
Forging is much harder, but the missing subject check remains. A stolen
Mcp-Session-Idcould still be resumed by another user until you reach 1.2.0-rc1.- The advisory is filed under agent-router. Which package do I patch?
The advisory lists
github.com/envoyproxy/ai-gatewayas the affected Go module, fixed in 1.2.0-rc1. The code paths it cites are in the Envoy AI Gateway MCP proxy.- Is this exploited in the wild?
No source says so. The report gives a code-level reproduction that was not run against a live cluster.
References
Read for this analysis:
- https://github.com/advisories/GHSA-953h-535j-jg2v
- https://github.com/theagentrouter/agent-router/security/advisories/GHSA-953h-535j-jg2v
- https://github.com/theagentrouter/agent-router/pull/2740
- https://github.com/theagentrouter/agent-router/pull/2755
- https://github.com/theagentrouter/agent-router/commit/0d1e73037076d72251342d0ea578a36ffb7002c7
- https://github.com/theagentrouter/agent-router/commit/994579660ae2a9f2feef3cb77766e155746858b4
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026CVE-2026-76894
ckan-mcp-server: DataStore Table UI resource does not validate postMessage origin (in) and posts to targetOrigin "*" (out)
low3.1Oct 16, 2026
