http-session-binding

A session id is bound to the caller that opened it

shipped

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-session identity A ({bearer:a}) opens a session and lists tools; identity B ({bearer:b}) opens a session of its own (B's control)
  • other-caller tools/list on A's Mcp-Session-Id with B's token
  • no-credential tools/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.

KeyDefaultStandardMeaning
identities.anonenot lockedToken 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.bnonenot lockedToken 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.issuernonenot lockedWhich 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.headersnonenot lockedHTTP 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.