{"record":{"id":"e7349d9303f36c0f","repo":"apache/shardingsphere","slug":"session-attribution-does-not-match-this-mcp-sessio","errorCode":null,"errorMessage":"Session attribution does not match this MCP session.","messagePattern":"Session attribution does not match this MCP session\\.","errorType":"http","errorClass":"MCPTransportSecurityException","httpStatus":400,"severity":"error","filePath":"mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/ShardingSphereServerTransportSecurityValidator.java","lineNumber":69,"sourceCode":"        originHeaderConstraint.validate(getFirstHeaderValue(headers, \"Origin\"));\n        String sessionId = getSessionId(headers);\n        if (sessionId.isBlank() || !sessionManager.hasSession(sessionId)) {\n            return;\n        }\n        validateSessionIdentity(headers, sessionId);\n        protocolVersionHeaderConstraint.validate(getFirstHeaderValue(headers, HttpHeaders.PROTOCOL_VERSION));\n    }\n    \n    private void validateSessionIdentity(final Map<String, List<String>> headers, final String sessionId) throws ServerTransportSecurityException {\n        if (!sessionAttributionResolver.isEnabled()) {\n            return;\n        }\n        Optional<MCPSessionIdentity> boundSessionIdentity = sessionManager.findSessionIdentity(sessionId);\n        if (boundSessionIdentity.isEmpty()) {\n            return;\n        }\n        if (!isSameAttribution(boundSessionIdentity.get(), sessionAttributionResolver.resolve(headers, sessionId))) {\n            throw new MCPTransportSecurityException(400, \"Session attribution does not match this MCP session.\",\n                    MCPTransportSecurityException.CATEGORY_SESSION_ATTRIBUTION_MISMATCH);\n        }\n    }\n    \n    private boolean isSameAttribution(final MCPSessionIdentity expected, final MCPSessionIdentity actual) {\n        return expected.getSubject().equals(actual.getSubject()) && expected.getSource().equals(actual.getSource()) && expected.getAttributes().equals(actual.getAttributes());\n    }\n    \n    private String getSessionId(final Map<String, List<String>> headers) {\n        return headers.entrySet().stream()\n                .filter(entry -> HttpHeaders.MCP_SESSION_ID.equalsIgnoreCase(entry.getKey()) && !entry.getValue().isEmpty()).findFirst()\n                .map(optional -> Objects.toString(optional.getValue().getFirst(), \"\")).orElse(\"\");\n    }\n    \n    private String getFirstHeaderValue(final Map<String, List<String>> headers, final String headerName) {\n        return headers.entrySet().stream()\n                .filter(entry -> headerName.equalsIgnoreCase(entry.getKey()) && !entry.getValue().isEmpty()).findFirst().map(optional -> Objects.toString(optional.getValue().getFirst(), \"\").trim())\n                .orElse(\"\");","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/ShardingSphereServerTransportSecurityValidator.java#L51-L87","documentation":"Thrown by the HTTP transport security validator when the session-attribution identity resolved from the current request's headers does not equal the identity that was bound to the MCP session when it was created (subject, source, and attributes must all match). It protects session hijacking/pinning: a session id issued to one client cannot be reused by another origin, user, or token. The server returns HTTP 400 with category CATEGORY_SESSION_ATTRIBUTION_MISMATCH. Validation is skipped when the resolver is disabled or the session id is unknown, so this error only fires on an existing session whose header-derived attribution drifted.","triggerScenarios":"Calling any MCP HTTP endpoint with a valid Mcp-Session-Id header while the attribution headers (e.g. authenticated subject, source, or attribute headers consumed by SessionAttributionResolver.resolve) differ from those present at Initialize time. Typical: replaying a session id from a different bearer token, a different proxy that rewrites identity headers, or a client that adds/removes an identity header between requests.","commonSituations":"Token rotation mid-session (new subject claim), a gateway that forwards different headers per request, multiple tabs/agents sharing a session id with different auth, or a changed issuer/attribute set after re-login while reusing an old session id.","solutions":["Start a new MCP session (re-run initialize) with the current identity headers and use the new session id for all subsequent calls.","Verify the client sends exactly the same identity/attribution headers on every request of a session (check proxy or middleware that mutates them).","If session pinning to identity is not wanted in your deployment, disable the session attribution resolver (sessionAttributionResolver.isEnabled() returns false skips the check).","Check server logs for which of subject/source/attributes diverged and align the client's headers."],"exampleFix":"// before: initialize with token A, then reuse session id with token B\ninitialize -> sessionId=S1 (Authorization: Bearer tokenA)\ncall tool with S1 (Authorization: Bearer tokenB)  // 400 attribution mismatch\n\n// after: re-initialize whenever credentials change\ninitialize (Authorization: Bearer tokenB) -> sessionId=S2\ncall tool with S2 (Authorization: Bearer tokenB)","handlingStrategy":"validation","validationCode":"// Re-initialize when identity inputs change, and pin headers per session\nfunction identityFingerprint(headers) {\n  return JSON.stringify([headers['authorization'], headers['x-forwarded-user'], headers['x-mcp-source']]);\n}\nconst session = { id: null, fingerprint: null };\nfunction ensureSession(initHeaders) {\n  const fp = identityFingerprint(initHeaders);\n  if (session.id === null || session.fingerprint !== fp) {\n    session.id = mcp.initialize(initHeaders).sessionId;\n    session.fingerprint = fp;\n    session.headers = initHeaders; // reuse EXACT same headers afterwards\n  }\n  return session;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await mcp.request('tools/call', params, sessionHeaders);\n} catch (e) {\n  if (e.status === 400 && e.category === 'SESSION_ATTRIBUTION_MISMATCH') {\n    session.id = null;               // force re-initialize with current identity\n    return retryAfterReinitialize(params);\n  }\n  throw e;\n}","preventionTips":["Send byte-identical identity headers on every request of a session.","Re-run initialize after any credential change or re-login.","Never share a session id between clients authenticated as different subjects.","Log which header (subject/source/attributes) diverged when the error appears."],"tags":["mcp","security","session","http-transport","authentication"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}