-
Notifications
You must be signed in to change notification settings - Fork 183
Description
Depends on #3866
Bind each session to the authentication token that created it, preventing a stolen session ID from being reused with different credentials. Without this, an attacker who obtains a valid session ID can make requests on behalf of the original user even after the original token has changed or been revoked.
This is a blocking requirement — the sessionManagementV2 flag must not be enabled in any production environment until this is implemented.
Implementation
Phase 1: Basic Token Binding (SHA256) ✅ COMPLETED
- During CreateSession(): compute SHA256(bearerToken) and store the hash in the session metadata alongside the session ID. The raw token must never be stored.
- On each subsequent request to that session: extract the current bearer token from the request, recompute its hash, and compare it against the stored value (implemented at session level, not middleware).
- On mismatch: reject the request and immediately terminate the session (call session.Close() and remove from storage) to prevent further use.
- Anonymous sessions (requests with no bearer token): store empty string hash at creation time and skip hash validation on subsequent requests — the session is bound to "no credentials" and any request that does present a token should be rejected.
- Constant-time comparison to prevent timing attacks
- Comprehensive unit and integration tests
Status: Basic SHA256 token binding is complete and working. Session hijacking is mitigated.
Phase 2: Enhanced Security (HMAC-SHA256) 🔄 IN PROGRESS
Per RFC THV-0038 security recommendations, upgrade to HMAC-SHA256 with server-managed secret:
"To prevent offline attacks if session state is leaked (e.g., from Redis/Valkey), prefer a keyed hash (e.g., HMAC-SHA256 with a server-managed secret and a per-session salt)."
Why upgrade? Plain SHA256 is vulnerable to offline attacks if session storage (Redis/Valkey) is compromised:
- Attackers can build rainbow tables to crack common tokens
- No server-managed secret prevents brute force attempts
- No per-session salt for additional entropy
Implementation tasks:
- Add HMAC secret to server configuration (load from environment/secure storage)
- Implement salt generation (crypto/rand, 16 bytes per session)
- Update HashToken() to use HMAC-SHA256 instead of SHA256
- Store salt in session metadata alongside hash
- Update validateCaller() to use HMAC with stored salt
- Add secret rotation support (with grace period for active sessions)
- Update tests for HMAC validation
- Add migration path for existing SHA256 sessions
- Update documentation
Security benefits:
- ✅ Prevents rainbow table attacks (HMAC with secret)
- ✅ Mitigates offline brute force (requires server secret)
- ✅ Per-session entropy (unique salt per session)
- ✅ Secret rotation capability
Configuration example:
server:
session:
hmac_secret: ${SESSION_HMAC_SECRET} # 32+ bytes
hmac_secret_previous: ${SESSION_HMAC_SECRET_PREVIOUS} # For rotation
hmac_grace_period: 1hSecurity Note
This mitigates session hijacking via stolen session IDs but does not replace transport security (TLS) or short session TTLs, both of which remain required. Token hash binding is one layer of a defence-in-depth strategy.
Phase 1 provides good security for most deployments. Phase 2 adds defense-in-depth against session storage leaks, recommended for production deployments handling sensitive data.
Acceptance Criteria
Phase 1: SHA256 Token Binding ✅ COMPLETED
- CreateSession() computes and stores SHA256(bearerToken) in session metadata at creation time
- The raw bearer token is never stored in the session or written to logs — only the hash
- Every subsequent request to the session runs hash validation before the request is processed
- A request presenting a token whose hash does not match the stored value is rejected
- The session is terminated immediately on a mismatch — Close() is called and the session is removed from storage
- Anonymous sessions (no bearer token at creation) skip hash validation on subsequent requests; a request that suddenly presents a token is rejected
- Hash validation is implemented at the session level with caller parameter on all methods
- Unit tests cover: valid token passes, mismatched token rejected and session terminated, anonymous session created and used successfully, anonymous session rejecting a request that presents a token
- Integration tests cover token binding behavior
Note: Session-level validation implemented instead of middleware per architectural requirements. HTTP 401 not achievable due to MCP SDK constraints (returns HTTP 200 with error content).
Phase 2: HMAC-SHA256 Enhancement 🔄 IN PROGRESS
- HMAC secret loaded from secure configuration
- Per-session salt generation and storage
- HashToken() uses HMAC-SHA256 with secret and salt
- Constant-time comparison continues to work
- Secret rotation with grace period
- Tests for HMAC validation and rotation
- Migration path for existing SHA256 sessions
- Documentation updated