http-session-binding
A session id is bound to the caller that opened it
The sections below are generated from the CLI (methodology 0.3-draft, CLI 0.3.0); the same text is printed by sealmark explain http-session-binding.
- pack
http- weight
- advisory · not scoredcandidate
- scope
- server as deployed (Mcp-Session-Id handling across two callers; MCP authorization specification, session binding)
- decided by
- http
- since
- 0.3-draft (methodology 0.3-draft)
- config keys
checks.http-session-binding.enabledchecks.http-session-binding.severityidentities.aidentities.boauth.issuerserver.headers
Probes
own-sessionidentity A ({bearer:a}) opens a session and lists tools; identity B ({bearer:b}) opens a session of its own (B's control)other-callertools/list on A's Mcp-Session-Id with B's tokenno-credentialtools/list on A's Mcp-Session-Id with every credential header removed
Settings and thresholds
Every check also takes checks.http-session-binding.enabled and checks.http-session-binding.severity; they are described once in the configuration reference.
This check has no numeric threshold.
| Key | Default | Standard | Meaning |
|---|---|---|---|
identities.a | none | not locked | Token the harness sends as caller A wherever the server config says {bearer} or {bearer:a}. null mints one per run. Set it when the server validates tokens it did not issue. ${VAR} is read from the environment. Treated as a secret: redacted in config show and in reports (flag --identity-a). |
identities.b | none | not locked | Token the harness sends as caller B ({bearer:b}). null mints one per run. Must differ from identities.a. Treated as a secret (flag --identity-b). |
oauth.issuer | none | not locked | Which OAuth issuer the server under test trusts. stub starts the harness's own authorization server on loopback (flag --oauth): {issuer} and {jwks} name it, and the caller identities ({bearer}, {bearer:a}, {bearer:b}) become tokens it signed for {audience}, sent as Authorization: Bearer. Point the server's issuer, JWKS URL and audience at those placeholders in server.env or server.args. external declares a server that trusts its own issuer: only the checks that need no harness-signed token run (oauth-discovery). null declares nothing. |
server.headers | none | not locked | HTTP headers sent to the server (url transport). Values under secret-looking names are redacted. |
How the verdict is established
- Scoring. advisory: reported with its verdict but not part of the score.
What to fix
Bind each session to the identity that opened it: on every request, authenticate the token first, then check that its subject is the one the session was created for, and refuse otherwise. A session id is not a credential: a request with a valid session id and no token, or another caller's token, must be refused. This check is advisory in the http pack (a round 7 candidate).
False positives
A server whose sessions are deliberately shared between callers (one tenant, one session pool) fails; the MCP specification says a session id must not be used as authentication and should be bound to the user.
False negatives
Only tools/list is tried on the borrowed session; a server that binds sessions for some methods only is not seen. Identities A and B come from {bearer:a} and {bearer:b}: without --oauth they are minted opaque tokens, and a server that refuses B's token on its own leaves the B leg inconclusive.