Skip to content

Pass Validated Client Responses to Transport - #6521

Closed
jstar0 wants to merge 1 commit into
stacklok:mainfrom
jstar0:fix/5009-authz-accepts-client-responses
Closed

jstar0 wants to merge 1 commit into
stacklok:mainfrom
jstar0:fix/5009-authz-accepts-client-responses

Conversation

@jstar0

@jstar0 jstar0 commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ParsingMiddleware validates each POST with mcp.DecodeMessage, but only stores parsed method-bearing requests. A valid JSON-RPC response therefore looked like an unparsed body to pkg/authz.Middleware and received HTTP 400 before transport session handling.

  • Preserve the response type from the already validated message; do not decode the body again.
  • Pass validated responses to downstream transport handling. Method-bearing requests continue through Cedar authorization.
  • Keep malformed envelopes, including responses with neither or both result and error, rejected by DecodeMessage.

Related to #5009; this PR does not close #5009. The current vMCP New/Serve path uses core admission rather than pkg/authz.Middleware, and its real Legacy elicitation round-trip is covered by TestForwarding_Elicitation_RealBackend. This change is scoped to the HTTP authorization middleware used with the streamable proxy.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test was run; see full-suite note below)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Focused race tests passed for pkg/authz, pkg/mcp, and pkg/transport/proxy/streamable. task lint-fix passed with 0 issues.

The full task test suite was run but did not complete cleanly in this environment. Remaining failures were unrelated to the changed packages: some tests require a container runtime, and network-error tests received HTTP 503 responses from the network proxy. No failures were reported in the three focused packages.

Does this introduce a user-facing change?

Yes. With authorization enabled on the streamable proxy path, validated JSON-RPC client responses reach transport session handling instead of being rejected as malformed. Requests still receive normal Cedar decisions, and invalid envelopes remain rejected.

Special notes for reviewers

  • Current ParsingMiddleware validates POST bodies independent of Content-Type; this change follows that admission model and adds no MIME-type gate.
  • The streamable proxy integration test checks session creation, Cedar denial of a protected tool call, and forwarding of a validated response to the runner channel. It does not add response-correlation state to authorization middleware.

@codecov

codecov Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.28%. Comparing base (fc57a22) to head (90c5c25).

Files with missing lines Patch % Lines
pkg/authz/middleware.go 57.14% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6521   +/-   ##
=======================================
  Coverage   79.28%   79.28%           
=======================================
  Files         802      802           
  Lines       81245    81253    +8     
=======================================
+ Hits        64415    64422    +7     
- Misses      16825    16826    +1     
  Partials        5        5           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jstar0

jstar0 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for pointing this out. I agree the health-probe regression is separate from #5009. I removed it from #6521; it remains covered by the separate PR #6520. The current #6521 diff is limited to the parser/authz handling and the client-response regression test.

@JAORMX JAORMX left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry for taking so long to review this, and thanks for sticking with it! Separating client responses from requests that failed to parse makes sense for the proxy authorization middleware.

There are two things we need to address before merging: the response check accepts invalid JSON-RPC envelopes, and the regression test doesn't establish that we're fixing the vMCP path described in #5009. I've left the details inline.

We've also changed the parser on main since this was written, and both production files now conflict. Please keep the newer admission checks when rebasing. We should be able to use the already validated message to identify responses rather than decode the body again.

I don't think we should add a pending-request table to the authorization middleware. Correlation belongs to the transport/session implementation. The 202 requirement is conditional on accepting the response, though, so please adjust the comment that says the transport is required to accept it.

A few contribution details to tidy up as well: commits 24049c75d, f2f74710f, and 327f0d372 are missing their Signed-off-by trailers. Please remove the fix(authz): title prefix and fill in the change-type and test-plan sections from the PR template. The green CI run is useful verification evidence; there's no need to repeat the same suite locally for this review.

Once the parser check and the affected-path coverage are sorted out, we can reassess whether this closes the original issue or should be scoped to the proxy behavior it fixes.

Comment thread pkg/mcp/parser.go Outdated
if err != nil {
return false
}
_, ok := msg.(*jsonrpc2.Response)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we base this on a validated response envelope? The pinned jsonrpc2.DecodeMessage returns *jsonrpc2.Response for {"jsonrpc":"2.0","id":1} even though it has neither result nor error. It also accepts an envelope containing both. That means these invalid messages get the response marker and pass through authorization. The truncated-JSON test doesn't catch this because the JSON itself is valid.

This isn't evidence of a method-bearing request bypass, but it does broaden admission beyond the valid responses we're trying to allow. Current main already has the stricter mcp.DecodeMessage in ParsingMiddleware. After rebasing, please derive the marker from that already validated message and drop this second decode. Add regression cases for missing/both result and error so we keep that boundary intact.

var handlerCalled bool
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
handlerCalled = true
w.WriteHeader(http.StatusAccepted)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This proves the response reaches the next handler, but the stub supplies the 202 we're asserting. It doesn't show that the affected transport accepts the response or completes a pending server-initiated request.

There's a scope question here too: at this revision, vMCP no longer installs pkg/authz.Middleware. pkg/vmcp/server/server.go:703-713 describes the move to core admission and the SDK call gate, and pkg/vmcp/cli/serve.go:387-390 notes that the legacy authz middleware is ignored. So this is a useful proxy regression, but it doesn't establish closure of the vMCP issue.

Could you identify the currently affected deployment path and cover its actual transport/session flow? If the original vMCP behavior has already changed, let's say that in the PR and narrow the closure claim. For the middleware-level coverage, please also use a denying policy to show that valid responses pass while protected requests still fail.

Valid JSON-RPC responses are not authorization requests, but the middleware rejected them when no ParsedMCPRequest was available. Reuse the message already validated by ParsingMiddleware to identify responses, then leave session and response handling to the transport. Keep malformed envelopes rejected and retain Cedar authorization for method-bearing requests.

Signed-off-by: King Star <mcxin.y@gmail.com>
@jstar0
jstar0 force-pushed the fix/5009-authz-accepts-client-responses branch from 4eb7c9c to 90c5c25 Compare October 3, 2026 18:34
@jstar0 jstar0 changed the title fix(authz): accept JSON-RPC responses to server-initiated requests Pass Validated Client Responses to Transport Oct 3, 2026
@jstar0

jstar0 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

I traced the current supported paths after the review. The vMCP New/Serve path now enforces authorization through core admission and the SDK streamable handler; it does not apply the legacy Config.AuthzMiddleware changed here. The shared runner HTTPProxy path exercised by this PR rejects backend-originated server requests because it cannot route replies across shared sessions, so this test cannot demonstrate completion of a pending server request. It proves only that a validated response passes the Cedar middleware and reaches the downstream transport channel. The repository already has TestForwarding_Elicitation_RealBackend for a Legacy vMCP request/response round trip.

Given that, this change does not fix #5009 in the current vMCP path, and the only production HTTPProxy path I found has no supported server-request response flow for this middleware change to enable. I’m closing this PR rather than keep an unsupported scope. I’ll leave #5009 open for confirmation against a current build.

@jstar0 jstar0 closed this Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vMCP streamable-http returns 400 on valid client responses to server-initiated requests

2 participants