Session auth-context reuse ignores JWT exp — expired JWT replayable within session_auth_reuse_ttl
MediumPublished Aug 25, 2026
### 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 t...
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)
More IBM advisories
All IBM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | Parse-gap bypasses in mcp-context-forge: SQL Sanitizer (privesc/DROP/mass-DELETE) and resource_filter SSRF | High8.8 | v1.0.7 |
| Aug 25 | Scoped admin OAuth DCR endpoints ignore token_teams restrictions and expose global registered clients | Medium6.4 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in url_to_markdown_server via Unvalidated URL Fetching | Critical9.1 | v1.0.7 |
| Aug 25 | Server-Side Request Forgery (SSRF) in mcp-rss-search via Unvalidated URL Fetching | High8.6 | v1.0.7 |
| Aug 25 | Cross-tenant read of plugin-binding policies (BOLA) in ContextForge MCP Gateway | High | v1.0.5 |
| Aug 25 | Default JWT Secret in Docker Compose Enables Forged Platform-Admin Tokens | Critical9.8 | v1.0.7 |