Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(rust-mcp-session): compare stored token_digest against presented token#2180

Merged
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/rust-mcp-session-verify-token-digest
May 12, 2026
Merged

fix(rust-mcp-session): compare stored token_digest against presented token#2180
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/rust-mcp-session-verify-token-digest

Conversation

@finnoybu

Copy link
Copy Markdown
Contributor

Summary

agent-governance-rust/agentmesh/src/mcp/session.rs and its byte-identical copy in agent-governance-rust/agentmesh-mcp/src/mcp/session.rs. McpSessionAuthenticator records sha256_hex(token) in McpSession.token_digest at issue time but never compares it during authenticate, so the captured digest is dead state. Authentication today accepts any token that passes HMAC verification and matches the payload's session id / agent id / expiry — even if the stored session record carries a digest for a different token (e.g. a tampered store, or a re-issued token reusing the same session id).

The HMAC signature is the primary authenticator, so today's exploitability is low (an attacker has to forge HMAC first), but the digest is plainly intended as a defence-in-depth binding between issued token and persisted session. Leaving it uncompared turns a documented invariant into wishful thinking.

Change

In authenticate(), after the existing agent/expiry/metadata checks, recompute sha256_hex(token) and compare it to session.token_digest via a local constant_time_eq helper (XOR accumulation; early-return only on length mismatch since session digests are fixed-length 64-char hex). On mismatch, return AccessDenied { reason: "session token digest mismatch" }. No external dependency added — subtle would be a heavier hammer than this single comparison needs.

Applied identically in both copies of mcp/session.rs since the two crates publish byte-identical copies of the MCP subtree.

Tests

  • rejects_session_with_mismatched_token_digest — issue a session, overwrite the store-side digest with all-zeros, assert authenticate() refuses with the new reason.
  • constant_time_eq_matches_regular_eq — covers empty inputs, equal inputs, single-byte difference, and length mismatch in both orders.
  • cargo test --workspace --lib mcp::session — 8 passed (4 per crate), 0 failed.

Test plan

  • New digest check fires only after HMAC + metadata checks pass, so existing failure paths still surface their original error reasons
  • issues_and_authenticates_sessions and enforces_concurrent_limit still pass — happy-path authentication is unaffected
  • Constant-time helper has no early-exit on byte difference (verified by inspection of the XOR-accumulator loop)

Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, Rust].

…token

`McpSessionAuthenticator` records `sha256_hex(token)` in `McpSession`
.token_digest` at issue time but never compares it during `authenticate`,
so the captured digest is dead state — authentication accepts any token
that passes HMAC verification and matches the payload's session id /
agent id / expiry, even when the stored session record carries a digest
for a different token.

Add a constant-time check in `authenticate`: after the agent / expiry /
metadata checks, compute `sha256_hex(token)` and compare it to
`session.token_digest`. Failure returns `AccessDenied { reason: "session
token digest mismatch" }`. A local `constant_time_eq` helper (XOR
accumulation; early-return only on length mismatch since session digests
are fixed-length 64-char hex) keeps the failure path timing-independent
without taking on a `subtle` direct dependency.

Applied identically in both `agentmesh/src/mcp/session.rs` and
`agentmesh-mcp/src/mcp/session.rs` — the two crates publish byte-identical
copies of the MCP subtree.

Tests:
- `rejects_session_with_mismatched_token_digest` — issue a session, tamper
  the store-side digest to all-zeros, assert authenticate() refuses.
- `constant_time_eq_matches_regular_eq` — covers empty inputs, equal
  inputs, single-byte difference, and length mismatch in both orders.
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — Security Review

Security Review

Severity Finding Fix
LOW The authentication process did not previously compare the stored token digest against the presented token, allowing for potential misuse of session tokens. Implemented a comparison of the computed SHA-256 digest of the presented token against the stored digest in a constant-time manner to prevent timing attacks.

@github-actions github-actions Bot added the size/M Medium PR (< 200 lines) label May 12, 2026
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

API Compatibility

Severity Change Impact
Breaking Added token digest comparison in authenticate() method Changes authentication behavior; previously valid tokens may be rejected if their digest does not match the stored digest, potentially affecting existing clients relying on the previous behavior.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

Docs Sync

  • constant_time_eq() in session.rs -- missing docstring
  • README.md -- section on authentication needs update
  • CHANGELOG.md -- missing entry for behavioral change in token authentication logic

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — View details

TL;DR: 1 blocker, 0 warnings. The digest comparison in the authentication process is missing, which could lead to security issues.

# Sev Issue Where
1 CRITICAL Missing comparison of token digest in authentication agent-governance-rust/agentmesh/src/mcp/session.rs

Add a comparison of the computed token digest against the stored digest in the authenticate() method to ensure integrity and prevent potential security bypasses.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `agent-governance-rust/agentmesh/src/mcp/session.rs`

agent-governance-rust/agentmesh/src/mcp/session.rs

  • rejects_session_with_mismatched_token_digest -- tests that authentication fails when the token digest does not match the stored session record.
  • constant_time_eq_matches_regular_eq -- verifies that the constant-time equality function behaves correctly across various input scenarios.

agent-governance-rust/agentmesh-mcp/src/mcp/session.rs

  • rejects_session_with_mismatched_token_digest -- tests that authentication fails when the token digest does not match the stored session record.
  • constant_time_eq_matches_regular_eq -- verifies that the constant-time equality function behaves correctly across various input scenarios.

@github-actions

Copy link
Copy Markdown

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential NONE
Overall MEDIUM

Automated check by AGT Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label May 12, 2026
@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ❌ Failed Issues detected
🛡️ Security Scan ✅ Completed Analysis complete
🔄 Breaking Changes ✅ Completed Analysis complete
📝 Docs Sync ✅ Completed Analysis complete
🧪 Test Coverage ✅ Completed Analysis complete

Verdict: ❌ Changes needed

@imran-siddique imran-siddique merged commit d1d67a0 into microsoft:main May 12, 2026
13 of 14 checks passed
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…token (microsoft#2180)

`McpSessionAuthenticator` records `sha256_hex(token)` in `McpSession`
.token_digest` at issue time but never compares it during `authenticate`,
so the captured digest is dead state — authentication accepts any token
that passes HMAC verification and matches the payload's session id /
agent id / expiry, even when the stored session record carries a digest
for a different token.

Add a constant-time check in `authenticate`: after the agent / expiry /
metadata checks, compute `sha256_hex(token)` and compare it to
`session.token_digest`. Failure returns `AccessDenied { reason: "session
token digest mismatch" }`. A local `constant_time_eq` helper (XOR
accumulation; early-return only on length mismatch since session digests
are fixed-length 64-char hex) keeps the failure path timing-independent
without taking on a `subtle` direct dependency.

Applied identically in both `agentmesh/src/mcp/session.rs` and
`agentmesh-mcp/src/mcp/session.rs` — the two crates publish byte-identical
copies of the MCP subtree.

Tests:
- `rejects_session_with_mismatched_token_digest` — issue a session, tamper
  the store-side digest to all-zeros, assert authenticate() refuses.
- `constant_time_eq_matches_regular_eq` — covers empty inputs, equal
  inputs, single-byte difference, and length mismatch in both orders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk size/M Medium PR (< 200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants