Skip to content

fix(ibac): Bypass MCP session-terminate DELETE as transport_stream#451

Merged
huang195 merged 3 commits into
kagenti:mainfrom
huang195:fix/ibac-mcp-session-terminate
Jun 1, 2026
Merged

fix(ibac): Bypass MCP session-terminate DELETE as transport_stream#451
huang195 merged 3 commits into
kagenti:mainfrom
huang195:fix/ibac-mcp-session-terminate

Conversation

@huang195
Copy link
Copy Markdown
Contributor

@huang195 huang195 commented May 30, 2026

Summary

Extends the transport_stream bypass merged in #450 to cover one more pattern of body-less transport-layer call: MCP Streamable HTTP session terminationDELETE to the MCP endpoint with the Mcp-Session-Id header.

Why

After #450 fixed the body-less GET/HEAD/OPTIONS denials, one symptom remained on the same agent (exgentic-a2a-tool-calling-gsm8k): a 403 at end-of-conversation when the MCP client SDK fires the session-cleanup DELETE.

From the live session timeline post-#450:

i=14 11:43:52.455  inbound  response  A2A message/stream     200    (user task done)
i=15 11:43:55.074  outbound denied   ibac deny/blocked      403    DELETE http://exgentic-mcp-gsm8k-mcp:8000/mcp
                                     "Action involves deleting data, which is not
                                      strictly necessary for user intent."

Note: the leading-space artifact is gone (listener pctx.Method from #450 is doing its job — the action description correctly shows DELETE http://...). The judge's reasoning is technically sound; it just doesn't know that DELETE /mcp with the Mcp-Session-Id header is protocol cleanup, not a user-actionable delete.

The benchmark task already succeeded at event 14; this 403 is cosmetic noise on cleanup. But it's noise that compounds: every conversation leaves a trailing 403 in the IBAC log, drives up judge_unavailable rate when the judge is loaded, and makes the abctl session timeline look like the agent is misbehaving when it isn't.

What changes

authlib/plugins/ibac/plugin.go — replace the single-condition step-5b check with a wider helper:

// before
if isTransportRetrieval(pctx.Method) && len(pctx.Body) == 0 { ... }

// after
if isTransportShaped(pctx) { ... }

New helper:

func isTransportShaped(pctx *pipeline.Context) bool {
    if len(pctx.Body) > 0 {
        return false
    }
    if isTransportRetrieval(pctx.Method) {
        return true
    }
    if pctx.Method == "DELETE" && pctx.Headers.Get("Mcp-Session-Id") != "" {
        return true
    }
    return false
}

The existing isTransportRetrieval(method) helper stays — it's now an inner check called from isTransportShaped. Keeping it makes the two patterns testable independently and documents the GET/HEAD/OPTIONS shape on its own.

Why the Mcp-Session-Id header is the right distinguisher

The MCP Streamable HTTP spec defines the Mcp-Session-Id header as the way clients reference a server-issued session ID. The header is set by the MCP client SDK (e.g., the official mcp Python package), never by user input. So:

  • Body-less DELETE + Mcp-Session-Id → MCP transport cleanup. Bypass.
  • Body-less DELETE without Mcp-Session-Id → real action (DELETE /api/users/42, DELETE /resources/abc). Judge.
  • Body-having DELETE → real action regardless. Judge.

An attacker controlling the agent could in principle set the Mcp-Session-Id header on a non-MCP DELETE to trick the bypass — but they could also just put their attack in a POST body, which IBAC has always judged. The bypass doesn't open new attack surface; it just stops misjudging legitimate transport.

Test plan

  • go test ./... in authbridge/authlib — all 29 packages pass
  • TestOnRequest_TransportStream_MCPSessionTerminate — body-less DELETE with Mcp-Session-Id header → skip/transport_stream, judge not called
  • TestOnRequest_TransportStream_BodylessDELETEWithoutHeaderIsJudged — body-less DELETE without the header → judged (the header is the distinguisher)
  • Existing TestOnRequest_TransportStreamBypass_BodylessPOSTIsJudged still passes — body-less DELETE without Mcp-Session-Id reaches the judge
  • After redeploy, confirm in the live agent's session timeline: event 15-style DELETE rows become hidden skip/transport_stream entries instead of deny/blocked

Notes

This is the third symptom in a chain we've worked through:

  1. Body-less GET (the SSE channel-open) → fixed in fix(ibac): Bypass transport-layer calls and let agents take self-actions #450 with transport_stream for retrieval methods
  2. No-intent denials at startup → fixed in fix(ibac): Bypass transport-layer calls and let agents take self-actions #450 with no_intent_policy: allow default
  3. Body-less DELETE at session termination → this PR

After this lands, all four denies in the original timeline (rows 3, 5, 9, 10 in the pre-#450 abctl listing, plus row 15 in the post-#450 listing) will skip cleanly. The agent's full conversation lifecycle from MCP handshake through user turn through session cleanup runs without a single false-positive IBAC denial.

Threat model is unchanged from #450: real action requests (anything with a body, or any side-effect HTTP method without an MCP session header) still reach the judge.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Summary by CodeRabbit

  • Improvements

    • Refined authorization so certain body-less requests — including session-termination DELETEs carrying a specific session header and subscription-management housekeeping calls — are treated as transport events and bypass intent/judge checks; DELETEs with a body or without that header continue to be evaluated.
  • Tests

    • Added tests covering session-termination bypass, body-less DELETE behavior without the header, and DELETE-with-body being evaluated.

Review Change Stack

After PR kagenti#450 landed the body-less GET/HEAD/OPTIONS bypass, one
class of MCP traffic still reaches the judge and gets denied: the
MCP Streamable HTTP session-termination signal — DELETE to the MCP
endpoint with the Mcp-Session-Id header. The MCP client SDK fires
this at end-of-conversation to release server-side session state.

Symptom seen on a real agent (exgentic-a2a-tool-calling-gsm8k):
after the user's task completes successfully, the agent issues
DELETE /mcp with Mcp-Session-Id; the judge sees the bare request
line (no body) and reasonably answers something like "Action
involves deleting data, which is not strictly necessary for user
intent." 403 on routine protocol cleanup.

Extend the transport_stream bypass to cover this shape. The
Mcp-Session-Id header is set by the client SDK (never user input),
so it's a precise distinguisher between transport cleanup and a
real "DELETE /api/users/42" action call — the latter has no MCP
session header and still reaches the judge.

Refactor: replace the single-condition isTransportRetrieval check
at step 5b with a wider isTransportShaped helper that wraps both
patterns. Keeps the existing isTransportRetrieval(method) helper
for testability and readability.

Tests:
- TestOnRequest_TransportStream_MCPSessionTerminate (positive:
  body-less DELETE + Mcp-Session-Id → skip/transport_stream)
- TestOnRequest_TransportStream_BodylessDELETEWithoutHeaderIsJudged
  (negative: body-less DELETE without the header → judged, the
  header is the load-bearing distinguisher)
- Existing TestOnRequest_TransportStreamBypass_BodylessPOSTIsJudged
  still validates body-less DELETE without Mcp-Session-Id reaches
  the judge — comment cross-references the new test.

Threat model unchanged: an attacker can't smuggle a payload
through a body-less request, and the Mcp-Session-Id header is set
by the client SDK rather than user input, so the bypass condition
isn't attacker-controllable in any way that bypasses other action
shapes.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

📝 Walkthrough

Walkthrough

The PR refactors IBAC's transport-stream bypass into isTransportShaped, so body-less retrieval-shaped requests and body-less DELETE requests with Mcp-Session-Id are skipped (recording transport_stream), and expands MCP housekeeping to include subscription-management endpoints, with tests for both positive and negative cases.

Changes

MCP Session Termination Transport-Stream Bypass

Layer / File(s) Summary
Transport-shaped bypass predicate and integration
authbridge/authlib/plugins/ibac/plugin.go
Updated step-5b bypass documentation to enumerate retrieval-shaped body-less requests and body-less DELETE with Mcp-Session-Id. Refactored OnRequest to call new isTransportShaped(pctx) and added the helper; extended MCP housekeeping comments and included resources/subscribe and resources/unsubscribe in the housekeeping set.
Test coverage for MCP session termination and standard DELETE
authbridge/authlib/plugins/ibac/plugin_test.go
Updated MCP housekeeping comments and test-case list. Added tests: body-less DELETE /mcp with Mcp-Session-Id bypasses judge and records transport_stream; body-less DELETE without header is judged once; DELETE /mcp with Mcp-Session-Id plus a non-empty body is judged once.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • kagenti/kagenti-extensions#450: Refactored the same transport_stream early-bypass behavior in OnRequest and extended transport-related tests in plugin_test.go, with this PR building on that work by adding MCP session termination detection via body-less DELETE + Mcp-Session-Id header.
  • kagenti/kagenti-extensions#443: Related edits to IBAC transport-bypass and MCP housekeeping handling; overlaps in rationale and test coverage changes.

Suggested reviewers

  • pdettori

Poem

🐰 A quiet DELETE hops in with care,
A session-id tucked safe in its lair,
No judge this time, the pipeline sighs,
The rabbit nods as the transport flies,
Hooray — housekeeping finishes with flair 🫧

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: extending the transport_stream bypass to handle MCP session-termination DELETE requests with the Mcp-Session-Id header.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@authbridge/authlib/plugins/ibac/plugin.go`:
- Around line 644-655: The DELETE + Mcp-Session-Id header check in
isTransportShaped(pctx *pipeline.Context) uses a sessionful header that was
removed in the sessionless 2026-07-28/SEP-2567 revision; update
isTransportShaped to either remove the pctx.Method == "DELETE" &&
pctx.Headers.Get("Mcp-Session-Id") != "" branch entirely if this service targets
the sessionless spec, or replace it with the correct termination mechanism from
the intended spec (e.g., a different header or transport-revision flag). Locate
the isTransportShaped function and modify the DELETE conditional accordingly so
it no longer relies on the removed Mcp-Session-Id header, and add a comment
referencing the protocol revision used to avoid regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8844ae8d-2447-42f3-b045-d5d7935acf04

📥 Commits

Reviewing files that changed from the base of the PR and between b9ee2ed and e5208d8.

📒 Files selected for processing (2)
  • authbridge/authlib/plugins/ibac/plugin.go
  • authbridge/authlib/plugins/ibac/plugin_test.go

Comment on lines +644 to +655
func isTransportShaped(pctx *pipeline.Context) bool {
if len(pctx.Body) > 0 {
return false
}
if isTransportRetrieval(pctx.Method) {
return true
}
if pctx.Method == "DELETE" && pctx.Headers.Get("Mcp-Session-Id") != "" {
return true
}
return false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MCP Streamable HTTP Mcp-Session-Id header specification

💡 Result:

The Model Context Protocol (MCP) has undergone a significant architectural change regarding session management. As of the current date (May 2026), the Mcp-Session-Id header has been deprecated and removed from the protocol specification [1][2][3]. Historical Context and Specification: Prior to this change (notably in specifications from 2025 and early 2026), the Streamable HTTP transport utilized the Mcp-Session-Id header to maintain session state [2][3][4]. Under that previous model: 1. Initialization: The client performed an initialize request [2][3]. 2. Session Assignment: The server generated a unique session ID and returned it in the Mcp-Session-Id response header [5][6][7]. 3. Session Persistence: Clients were required to include this Mcp-Session-Id header in all subsequent HTTP requests [5][8][4]. The server used this identifier to associate requests with specific session state, capabilities, and negotiated protocol versions [3][7]. Current Status (Sessionless Architecture): The protocol has shifted to a stateless architecture [1][2]. The updated specification removes both the initialize/initialized handshake and the Mcp-Session-Id header entirely [2][3]. Requests are now designed to be independent and self-contained; any state required by the server must be managed explicitly by the client and included within request parameters (e.g., using explicit state handles) [1][3]. Consequently, any existing reliance on Mcp-Session-Id for routing or stateful behavior is no longer supported in the current MCP specification [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="authbridge/authlib/plugins/ibac/plugin.go"

# Print the exact lines around the helper for context
sed -n '620,675p' "$FILE"

Repository: kagenti/kagenti-extensions

Length of output: 1940


🌐 Web query:

site:modelcontextprotocol.io Mcp-Session-Id streamable HTTP

💡 Result:

In the Model Context Protocol (MCP), Mcp-Session-Id is an HTTP header used specifically within the Streamable HTTP transport to manage session state between a client and server [1][2][3]. Key details regarding its usage include: Initialization: A server MAY assign a session ID during initialization by including the Mcp-Session-Id header in the HTTP response that contains the InitializeResult [1][3]. Client Requirements: If a server provides an Mcp-Session-Id during initialization, the client MUST include this header in all subsequent HTTP requests made to that server for the duration of the session [1][3]. Validation: Servers that require a session ID SHOULD return an HTTP 400 Bad Request status for any non-initialization requests that do not include the header [1][3]. If a server receives a request with an invalid or expired session ID, it MUST respond with HTTP 404 Not Found, prompting the client to re-initialize [1][3]. Termination: Clients can explicitly terminate a session by sending an HTTP DELETE request to the MCP endpoint, including the Mcp-Session-Id header [1][4][3]. This header facilitates stateful communication in environments where multiple clients may connect to the same server, allowing the server to maintain distinct sessions [5][6]. Note that in some newer protocol revisions, the Streamable HTTP transport architecture has evolved, and users should always refer to the specific version of the specification they are implementing [7].

Citations:


🌐 Web query:

modelcontextprotocol.io transports streamable HTTP sessionless Mcp-Session-Id

💡 Result:

In MCP Streamable HTTP, the header is indeed Mcp-Session-Id, but whether it’s required depends on which Streamable HTTP protocol revision you’re targeting. 1) Streamable HTTP revisions that use Mcp-Session-Id (stateful protocol layer) - In the older Streamable HTTP revisions (e.g., protocol version 2025-03-26), servers MAY assign a session ID by including an Mcp-Session-Id header on the HTTP response containing the InitializeResult. [1] - If the server returns an Mcp-Session-Id during initialization, clients using Streamable HTTP MUST include that same Mcp-Session-Id header on all subsequent HTTP requests to that server. [2][1] - Session termination/behavior is also defined: servers can terminate sessions; clients receiving 404 for a request with Mcp-Session-Id must start a new session by sending a new InitializeRequest without a session ID; and clients SHOULD send an HTTP DELETE with Mcp-Session-Id to explicitly terminate. [2][1] 2) 2026-07-28 revision (“stateless” protocol layer): Mcp-Session-Id removed - The MCP Transport documentation’s draft “Transports” page describes a 2026-07-28 change where Streamable HTTP behavior was revised and specifically notes removal of “protocol-level sessions” and that requests/streams resumability mechanisms from earlier revisions are no longer part of this revision. [3] - The MCP release candidate blog post for 2026-07-28 states both that the Mcp-Session-Id header and the protocol-level session are removed, and that with both gone, any MCP request can go to any server instance (i.e., no sticky routing / shared session store needed at the protocol layer). [4] - SEP-2567 (“Sessionless MCP via Explicit State Handles”) further explains the intent: it removes the protocol-level session concept and the Mcp-Session-Id header, making the protocol sessionless. [5] So, for “modelcontextprotocol.io transports streamable HTTP sessionless Mcp-Session-Id”: - If you’re building against the sessionful Streamable HTTP revision, you must propagate Mcp-Session-Id on subsequent requests after it’s returned with InitializeResult. [2][1] - If you’re building against the 2026-07-28 (sessionless) revision, the protocol removes Mcp-Session-Id; you should not rely on it for request routing or continuity—use explicit state handles (per SEP-2567) or whatever mechanism the newer spec provides for carrying state. [3][5][4]

Citations:


Validate MCP Streamable HTTP spec revision for Mcp-Session-Id termination
In authbridge/authlib/plugins/ibac/plugin.go (lines 644-655), the header name Mcp-Session-Id matches the Streamable HTTP transport in the sessionful protocol revisions (used to scope session state and used for explicit termination via DELETE). If this code is meant to target the sessionless 2026-07-28/SEP-2567 revision, Mcp-Session-Id is removed from the protocol, so the DELETE + Mcp-Session-Id check should be updated accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/authlib/plugins/ibac/plugin.go` around lines 644 - 655, The DELETE
+ Mcp-Session-Id header check in isTransportShaped(pctx *pipeline.Context) uses
a sessionful header that was removed in the sessionless 2026-07-28/SEP-2567
revision; update isTransportShaped to either remove the pctx.Method == "DELETE"
&& pctx.Headers.Get("Mcp-Session-Id") != "" branch entirely if this service
targets the sessionless spec, or replace it with the correct termination
mechanism from the intended spec (e.g., a different header or transport-revision
flag). Locate the isTransportShaped function and modify the DELETE conditional
accordingly so it no longer relies on the removed Mcp-Session-Id header, and add
a comment referencing the protocol revision used to avoid regressions.

huang195 added 2 commits May 30, 2026 08:02
Reviewer flagged that the test suite covers DELETE+Mcp-Session-Id
without a body (bypass) and DELETE without the header (judge), but
not DELETE+Mcp-Session-Id WITH a body — the third corner that the
body-first guard in isTransportShaped quietly handles. Mirrors how
GET-with-body has its own case alongside body-less GET.

Add TestOnRequest_TransportStream_MCPSessionTerminate_WithBodyIsJudged
asserting the invariant: even with the right header, a non-empty
body routes the request to the judge. Pure test addition; no
production-code change.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Auditing MCP protocol coverage of the housekeeping bypass turned up
two methods that fit the same shape as the existing list (initialize,
*list, etc.) but go through a body-having JSON-RPC POST so the new
transport_stream bypass doesn't apply:

  - resources/subscribe: client tells the server to push notifications
    when a resource changes
  - resources/unsubscribe: pair to the above; release the subscription

Both are subscription-state bookkeeping — protocol mechanics, not
user-meaningful actions. An agent that maintains resource subscriptions
mid-conversation would see these calls hit the judge today; the judge
might happen to allow them since they don't look like exfiltration,
but it's wasted judge calls and a missed bypass shape.

Add both to the isMCPHousekeeping switch list and extend the existing
TestOnRequest_MCPHousekeepingBypass table to cover them. Doc-comment
on the helper expanded to mention subscription management alongside
connection setup and capability discovery.

Side-effect MCP methods (tools/call, prompts/get, resources/read)
still reach the judge — those are exactly what IBAC exists for.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
authbridge/authlib/plugins/ibac/plugin.go (1)

640-664: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Scope the Mcp-Session-Id DELETE bypass to confirmed MCP traffic.

As written, any body-less DELETE with that header skips IBAC, including non-MCP endpoints. That makes a header value alone a policy-bypass signal for a common side-effect method. Please gate this branch on an MCP-only signal, not just Mcp-Session-Id, and add a regression test for a non-MCP DELETE carrying the header.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/authlib/plugins/ibac/plugin.go` around lines 640 - 664, The
DELETE-with-Mcp-Session-Id check in isTransportShaped currently treats any
body-less DELETE with that header as transport-shaped, allowing non-MCP
endpoints to bypass IBAC; change the condition in isTransportShaped to require
both the Mcp-Session-Id header and an MCP-specific signal on the request/context
(e.g., an MCP protocol flag on pipeline.Context or a specific MCP-only
path/header such as pctx.Headers.Get("Mcp-Protocol") == "mcp" or
pctx.IsMCP(true)) so only confirmed MCP traffic is exempted (update/introduce
the MCP detector helper if needed and ensure isTransportRetrieval logic is
unchanged), and add a regression test that sends a body-less DELETE with
Mcp-Session-Id to a non-MCP endpoint and asserts IBAC is not skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@authbridge/authlib/plugins/ibac/plugin.go`:
- Around line 640-664: The DELETE-with-Mcp-Session-Id check in isTransportShaped
currently treats any body-less DELETE with that header as transport-shaped,
allowing non-MCP endpoints to bypass IBAC; change the condition in
isTransportShaped to require both the Mcp-Session-Id header and an MCP-specific
signal on the request/context (e.g., an MCP protocol flag on pipeline.Context or
a specific MCP-only path/header such as pctx.Headers.Get("Mcp-Protocol") ==
"mcp" or pctx.IsMCP(true)) so only confirmed MCP traffic is exempted
(update/introduce the MCP detector helper if needed and ensure
isTransportRetrieval logic is unchanged), and add a regression test that sends a
body-less DELETE with Mcp-Session-Id to a non-MCP endpoint and asserts IBAC is
not skipped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b093e938-7d6e-44af-9d56-5d235b3789df

📥 Commits

Reviewing files that changed from the base of the PR and between 3336818 and 4255dd5.

📒 Files selected for processing (2)
  • authbridge/authlib/plugins/ibac/plugin.go
  • authbridge/authlib/plugins/ibac/plugin_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • authbridge/authlib/plugins/ibac/plugin_test.go

Copy link
Copy Markdown
Contributor

@pdettori pdettori left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped extension of the transport_stream bypass.

Reviewed: Go security logic, test coverage, commit conventions, CI status.

  • Body-first guard in isTransportShaped is the load-bearing security check — prevents payload smuggling regardless of headers. Well-tested.
  • Mcp-Session-Id header is spec-defined (MCP Streamable HTTP 2025-03-26) and SDK-set, not user-controllable in practice.
  • Three new tests lock in all three corners: bypass (DELETE+header+no-body), non-bypass (DELETE+no-header), non-bypass (DELETE+header+body).
  • resources/subscribe + resources/unsubscribe addition to housekeeping list is correct per MCP spec — these are subscription bookkeeping, not side-effect methods.
  • All 3 commits DCO-signed, conventional commit format, clean history.
  • CI: all 17 checks passing.

Re CodeRabbit's concern about "SEP-2567 / 2026-07-28 sessionless revision" — that spec revision does not exist; Mcp-Session-Id remains current in the MCP Streamable HTTP transport spec. The scoping concern (bypass only for MCP traffic) is addressed by the body-first guard: no body means no action payload, so the bypass is inert from a threat-model perspective even if triggered on non-MCP traffic.

LGTM.

@huang195 huang195 merged commit e0b9417 into kagenti:main Jun 1, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this from New /:ToDo to Done in Kagenti Issue Prioritization Jun 1, 2026
@huang195 huang195 deleted the fix/ibac-mcp-session-terminate branch June 1, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants