BLOCKER: legacy MCP sessions are not bound to their authenticated owner #789

Open
opened 2026-10-02 13:10:47 +00:00 by kayg · 4 comments
Owner

Protocol audit assigned under #663; source base c4a61e8cf0. Source review only; no hostile payload, live exploit or crash-threshold measurement. The owner rule blocks authorization holes and crash/DoS risks. No product code is changed by this audit.

Evidence: crates/calternal-server/src/mcp.rs:1248–1272 enables legacy sessions
and supplies the default rmcp LocalSessionManager. require_mcp_credential
(:1282–1311) verifies that the current caller has a bearer credential and
MCP access; it does not compare the caller with the owner of Mcp-Session-Id.
Locked rmcp 3.5.0 session/local.rs:34, :60–85, :118–145 stores sessions by
ID alone. In tower.rs:1892–1986, GET/resume accesses that session's stream;
:2353–2378 DELETE closes it. Neither path checks an authenticated owner.
Round-7a uses the same manager and guard.

Impact: a different valid MCP caller who obtains another session ID can use
session-control and cached stream paths belonging to that session. Random IDs
make guessing difficult, but are not an owner check. This is conditional on
obtaining the ID; no ID leak or guessing success is claimed. Tool calls still
dispatch with the current request's bearer credential (mcp.rs:217–251), so
this report does not claim that tool dispatch itself impersonates the owner.

Repair: bind legacy sessions to a stable authenticated User and credential
identity. Check the binding on every POST, GET/resume and DELETE before the
SDK touches the session. Deny a foreign or revoked binding without returning
cached events. Keep credential strings and session IDs out of logs.

Regression coverage: two synthetic Users and two credentials for one User;
foreign-session read/resume/delete denial; authorized reconnect; revoke,
expiry and plugin disable; stateless requests remain scoped to the caller.

Duplicate check: searched all issue states for XML depth, connection cap, SSE revocation and MCP session. Read related #457, #328, #329 and #668. No matching repair issue identified. Track the repair with source-level tests and a safe local validation of the repaired boundary. Do not close this issue from the audit job.

Protocol audit assigned under #663; source base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Source review only; no hostile payload, live exploit or crash-threshold measurement. The owner rule blocks authorization holes and crash/DoS risks. No product code is changed by this audit. Evidence: `crates/calternal-server/src/mcp.rs:1248–1272` enables legacy sessions and supplies the default rmcp LocalSessionManager. `require_mcp_credential` (`:1282–1311`) verifies that the current caller has a bearer credential and MCP access; it does not compare the caller with the owner of Mcp-Session-Id. Locked rmcp 3.5.0 `session/local.rs:34`, `:60–85`, `:118–145` stores sessions by ID alone. In `tower.rs:1892–1986`, GET/resume accesses that session's stream; `:2353–2378` DELETE closes it. Neither path checks an authenticated owner. Round-7a uses the same manager and guard. Impact: a different valid MCP caller who obtains another session ID can use session-control and cached stream paths belonging to that session. Random IDs make guessing difficult, but are not an owner check. This is conditional on obtaining the ID; no ID leak or guessing success is claimed. Tool calls still dispatch with the current request's bearer credential (`mcp.rs:217–251`), so this report does not claim that tool dispatch itself impersonates the owner. Repair: bind legacy sessions to a stable authenticated User and credential identity. Check the binding on every POST, GET/resume and DELETE before the SDK touches the session. Deny a foreign or revoked binding without returning cached events. Keep credential strings and session IDs out of logs. Regression coverage: two synthetic Users and two credentials for one User; foreign-session read/resume/delete denial; authorized reconnect; revoke, expiry and plugin disable; stateless requests remain scoped to the caller. Duplicate check: searched all issue states for XML depth, connection cap, SSE revocation and MCP session. Read related #457, #328, #329 and #668. No matching repair issue identified. Track the repair with source-level tests and a safe local validation of the repaired boundary. Do not close this issue from the audit job.
Author
Owner

Legacy SDK sessions now have a bounded owner registry using stable User ID plus credential class/ID (hash for sessions). The middleware denies unbound/different owners before SDK POST, GET/resume or DELETE handling. Initialization reserves one of 1024 slots; DELETE and a periodic authority/inactivity reaper release slots and close SDK sessions. No credential or session ID is logged.
Validation is in progress. No completion or live exploit claim.

Legacy SDK sessions now have a bounded owner registry using stable User ID plus credential class/ID (hash for sessions). The middleware denies unbound/different owners before SDK POST, GET/resume or DELETE handling. Initialization reserves one of 1024 slots; DELETE and a periodic authority/inactivity reaper release slots and close SDK sessions. No credential or session ID is logged. Validation is in progress. No completion or live exploit claim.
Author
Owner

Independent read-only review of target b3e7c14ad for #785.

P2: the added tests do not prove MCP owner denial through HTTP.
tests/adversarial/protocol_lifecycle.py:43 initializes, reconnects and
deletes with one credential. crates/calternal-server/src/protocol_authority.rs:255
tests principal equality. The manager tests at
crates/calternal-server/src/mcp_sessions.rs:273 cover allocation and cleanup.
None asserts the HTTP denial required by #789 and DESIGN §21.

Removing the check at crates/calternal-server/src/mcp.rs:1399 can leave
these added tests passing. This is a missing regression, not evidence that
the current check grants foreign access.

Concrete fix: add an HTTP test with two fixture Users and two credentials
for one User. Assert owner denial for POST, GET/resume and DELETE, and verify
that the original owner still succeeds. Cover revoke, expiry and Plugin
disable separately. Keep all fixture credentials and routing handles private.

Existing #789 owns this regression; no duplicate issue was created.
Review head: 4e739be474. No build, test, server
or browser was run under the LIGHT job restriction.

Independent read-only review of target b3e7c14ad for #785. P2: the added tests do not prove MCP owner denial through HTTP. `tests/adversarial/protocol_lifecycle.py:43` initializes, reconnects and deletes with one credential. `crates/calternal-server/src/protocol_authority.rs:255` tests principal equality. The manager tests at `crates/calternal-server/src/mcp_sessions.rs:273` cover allocation and cleanup. None asserts the HTTP denial required by #789 and DESIGN §21. Removing the check at `crates/calternal-server/src/mcp.rs:1399` can leave these added tests passing. This is a missing regression, not evidence that the current check grants foreign access. Concrete fix: add an HTTP test with two fixture Users and two credentials for one User. Assert owner denial for POST, GET/resume and DELETE, and verify that the original owner still succeeds. Cover revoke, expiry and Plugin disable separately. Keep all fixture credentials and routing handles private. Existing #789 owns this regression; no duplicate issue was created. Review head: 4e739be4743d2fbc63d91ae2af9dd5e61a5ec652. No build, test, server or browser was run under the LIGHT job restriction.
Author
Owner

Finding #789: the review called out missing HTTP-boundary coverage for owner binding on MCP session handles. I added a live-app regression using two Users plus a second installation credential for the owner. It exercises foreign POST/GET/DELETE denial, owner continuation, owner DELETE, and session revocation/expiry, MCP surface disable, and Plugin disable/restore. Server gates are pending.

Finding #789: the review called out missing HTTP-boundary coverage for owner binding on MCP session handles. I added a live-app regression using two Users plus a second installation credential for the owner. It exercises foreign POST/GET/DELETE denial, owner continuation, owner DELETE, and session revocation/expiry, MCP surface disable, and Plugin disable/restore. Server gates are pending.
Author
Owner

MCP HTTP-boundary regressions are committed as 4d2e86385.

cargo clippy --offline -p calternal-server --all-targets -- -D warnings passed:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 20s

The real-server test command is next. Clippy required the real generated apps/web/build because calternal-server embeds its frontend; those generated files are ignored and will be removed after the gates.

MCP HTTP-boundary regressions are committed as `4d2e86385`. `cargo clippy --offline -p calternal-server --all-targets -- -D warnings` passed: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 20s ``` The real-server test command is next. Clippy required the real generated `apps/web/build` because `calternal-server` embeds its frontend; those generated files are ignored and will be removed after the gates.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#789
No description provided.