# GHSA-953h-535j-jg2v: The MCP proxy's client-facing session ID embeds the authenticated caller's subject (JWT `sub` claim) specifically as an anti-hija

> Attackers can hijack MCP sessions by forging IDs due to missing subject verification and insecure default seeds, allowing unauthorized access to other users' data.

- **Severity:** Not scored
- **Exploited in the wild:** Not known
- **Login needed:** Any signed-in user
- **Attackable:** Over the internet
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 19 Oct 2026

## What is GHSA-953h-535j-jg2v?

GHSA-953h-535j-jg2v is an authentication bypass vulnerability in github.com/envoyproxy/ai-gateway, published on 19 Oct 2026. Any signed-in user can exploit it over the network, with no action from the victim.

### Official description

## 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 if `backendSessionIDs()` 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) call `sessionFromID` with no subject argument at all.
- `internal/mcpproxy/mcpproxy.go` line 259 (`clientToGatewaySessionIDFromEntries`)
  is where the subject is embedded at creation, confirming the intended design.
- Aggravating factor: `manifests/charts/ai-gateway-helm/values.yaml` line 297
  ships `seed: "default-insecure-seed"` as the out-of-the-box value for
  `controller.mcp.sessionEncryption.seed`, which `templates/deployment.yaml`
  passes straight to `--mcpSessionEncryptionSeed` (also literally defaulted to
  `"default-insecure-seed"` in `cmd/controller/main.go` and
  `cmd/extproc/mainlib/main.go` when the flag is omitted entirely). Session IDs
  are `salt || nonce || ciphertext` (see `internal/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)

1. Deploy Envoy AI Gateway with an `MCPRoute` that has an OAuth/JWT
   `SecurityPolicy` attached (so `internalapi.MCPSubjectHeader` is populated)
   and at least one backend MCP server, using the Helm chart's default
   `values.yaml` (`controller.mcp.sessionEncryption.seed` left at
   `"default-insecure-seed"`).
2. User A authenticates with a valid JWT (`sub: user-a`) and sends an
   `initialize` JSON-RPC request to the route. The gateway creates backend
   session(s) and returns `Mcp-Session-Id: <encrypted-blob-A>`, whose plaintext
   is `{route}@user-a@{backend}:{base64(sessionID)}:{capHex}`.
3. 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 structure `sessionFromID`
   expects.
4. Attacker sends a subsequent JSON-RPC request (e.g. `tools/call`) to the same
   route with header `Mcp-Session-Id: <forged-or-captured-blob-A>`. The gateway
   decrypts it, extracts `perBackendSessionIDs` for `backend`, and forwards the
   attacker's call using that backend session — with no comparison to the
   attacker's own JWT subject at any point in `sessionFromID` /
   `backendSessionIDs()`.

## Suggested fix

- In `sessionFromID`, thread the current request's `extractSubject(r)` value
  through to `backendSessionIDs()` (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.seed` to be randomly generated per
  install (e.g. via a Helm `lookup`/`randAlphaNum` in 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.

## Which versions are affected by GHSA-953h-535j-jg2v?

2 open source packages ship an affected version, including github.com/envoyproxy/ai-gateway.


| Package | Ecosystem | Affected | Fixed in |
| --- | --- | --- | --- |
| `github.com/envoyproxy/ai-gateway` | Go | From 0.4.0-rc1 | 1.2.0-rc1 |
| `github.com/envoyproxy/ai-gateway` | Go | From 0.4.0-rc1 | 1.2.0-rc1 |

## How do I fix GHSA-953h-535j-jg2v?

Upgrade github.com/envoyproxy/ai-gateway to 1.2.0-rc1 or later.

## Is GHSA-953h-535j-jg2v being exploited?

There is no known exploitation of GHSA-953h-535j-jg2v in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for GHSA-953h-535j-jg2v and fix it if it is affected.

What it is: GHSA-953h-535j-jg2v is an authentication bypass vulnerability in github.com/envoyproxy/ai-gateway, published on 19 Oct 2026. Any signed-in user can exploit it over the network, with no action from the victim.
Exploitation: There is no known exploitation of GHSA-953h-535j-jg2v in the wild and no public exploit code.

Affected packages and fixed versions:
- github.com/envoyproxy/ai-gateway (Go): affected from 0.4.0-rc1; fixed in 1.2.0-rc1
- github.com/envoyproxy/ai-gateway (Go): affected from 0.4.0-rc1; fixed in 1.2.0-rc1

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/GHSA-953h-535j-jg2v/ (Markdown: https://insights.sbomify.com/GHSA-953h-535j-jg2v/index.md)
```

## Sources

- OSV: https://osv.dev/vulnerability/GHSA-953h-535j-jg2v

---
From sbomify Insights: https://insights.sbomify.com/GHSA-953h-535j-jg2v/
Summaries are generated and may contain mistakes; the official description above is the record.
