Summary
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
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.
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/serverDELETE
paths, 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.yaml
passes 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 structuresessionFromID
expects. - 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().
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 matchrepos/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.
Impact
An attacker who obtains (or, under the default seed, forges) another user'sMcp-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.
Credentials are embedded in source code or a binary, making them accessible to anyone who can read the artifact. Typical impact: unauthorized access using the static credential.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
- 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.
Frequently Asked Questions
- What is GHSA-953H-535J-JG2V? GHSA-953H-535J-JG2V is a medium-severity use of hard-coded credentials vulnerability in github.com/envoyproxy/ai-gateway (go), affecting versions >= 0.4.0-rc1, < 1.2.0-rc1. It is fixed in 1.2.0-rc1. Credentials are embedded in source code or a binary, making them accessible to anyone who can read the artifact.
- Which versions of github.com/envoyproxy/ai-gateway are affected by GHSA-953H-535J-JG2V? github.com/envoyproxy/ai-gateway (go) versions >= 0.4.0-rc1, < 1.2.0-rc1 is affected.
- Is there a fix for GHSA-953H-535J-JG2V? Yes. GHSA-953H-535J-JG2V is fixed in 1.2.0-rc1. Upgrade to this version or later.
- Is GHSA-953H-535J-JG2V exploitable, and should I be worried? Whether GHSA-953H-535J-JG2V is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-953H-535J-JG2V is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-953H-535J-JG2V? Upgrade
github.com/envoyproxy/ai-gatewayto 1.2.0-rc1 or later.