Session auth-context reuse ignores JWT exp — expired JWT replayable within session_auth_reuse_ttl
Summary
When session auth-context reuse is enabled, a stored auth context's expiry is computed solely from a fixed config TTL (now + session_auth_reuse_ttl). It does not consider the JWT's own exp claim, so a JWT that has passed its exp can still be reused (replayed) for the remainder of the reuse window — per-request JWT expiry enforcement is skipped on a reuse hit.
Affected
- Rust runtime
crates/mcp_runtime(src/lib.rs), when session auth-context reuse is active (session_auth_reuse_ttl_seconds> 0) with public ingress. - Version
1.0.2and currentmain.
Detail
InternalAuthContextcarries no JWTexp/ expiry field.- On store, the reuse expiry is set as
auth_context_expires_at_epoch_ms = unix_epoch_millis() + session_auth_reuse_ttl— no clamping to the JWTexp. can_reuse_session_authonly checksunix_epoch_millis() >= auth_context_expires_at_epoch_ms(the TTL-based value).- Net effect: an expired credential remains accepted for up to
session_auth_reuse_ttlafter itsexp, because the reuse path returns the cached auth context instead of re-validating the (now expired) JWT.
Impact
Authentication-lifetime bypass, bounded by the reuse TTL. Requires the reuse feature enabled and a matching binding fingerprint. Default configuration may have reuse disabled (please confirm on your side).
Suggested fix
- Add the JWT...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| mcp-contextforge-gateway PyPI | < v1.0.5 | v1.0.5 |
Details and references
### Summary When session auth-context reuse is enabled, a stored auth context's expiry is computed solely from a fixed config TTL (`now + session_auth_reuse_ttl`). It does **not** consider the JWT's own `exp` claim, so a JWT that has passed its `exp` can still be reused (replayed) for the remainder of the reuse window — per-request JWT expiry enforcement is skipped on a reuse hit. ### Affected - Rust runtime `crates/mcp_runtime` (`src/lib.rs`), when session auth-context reuse is active (`session_auth_reuse_ttl_seconds` > 0) with public ingress. - Version `1.0.2` and current `main`. ### Detail - `InternalAuthContext` carries no JWT `exp` / expiry field. - On store, the reuse expiry is set as `auth_context_expires_at_epoch_ms = unix_epoch_millis() + session_auth_reuse_ttl` — no clamping to the JWT `exp`. - `can_reuse_session_auth` only checks `unix_epoch_millis() >= auth_context_expires_at_epoch_ms` (the TTL-based value). - Net effect: an expired credential remains accepted for up to `session_auth_reuse_ttl` after its `exp`, because the reuse path returns the cached auth context instead of re-validating the (now expired) JWT. ### Impact Authentication-lifetime bypass, bounded by the reuse TTL. Requires the reuse feature enabled and a matching binding fingerprint. Default configuration may have reuse disabled (please confirm on your side). ### Suggested fix - Add the JWT `exp` to the stored auth context and cap reuse expiry at `min(now + session_auth_reuse_ttl, jwt_exp)`. - Ensure the auth layer that produces the context forwards `exp` so the runtime can clamp. ### Notes Reported via private vulnerability disclosure per `SECURITY.md`. Happy to provide a minimal reproduction or test against a patch.
- Severity from
- GitHub (reviewed advisory)