Skip to content

Detect JSON-RPC application errors in proxy audit middleware#4709

Open
gmogmzGithub wants to merge 1 commit intostacklok:mainfrom
gmogmzGithub:fix/audit-jsonrpc-error-detection
Open

Detect JSON-RPC application errors in proxy audit middleware#4709
gmogmzGithub wants to merge 1 commit intostacklok:mainfrom
gmogmzGithub:fix/audit-jsonrpc-error-detection

Conversation

@gmogmzGithub
Copy link
Copy Markdown

@gmogmzGithub gmogmzGithub commented Apr 9, 2026

Summary

  • The proxy audit middleware determined outcome solely from the HTTP status code. Because MCP servers return errors inside HTTP 200 responses per the JSON-RPC spec, application-level failures (expired tokens, API errors, invalid parameters) were logged as outcome=success, making them invisible in audit logs.
  • Adds JSON-RPC error detection: after receiving an HTTP 200, the middleware inspects a small prefix of the response body for a top-level "error" field. If found, it records outcome=application_error with the JSON-RPC error code and truncated message as metadata.
  • Adds a DetectApplicationErrors config flag (default true) that controls this behavior independently of IncludeResponseData, so error detection works without enabling full response capture.

Fixes #4678 as a BUG FIX

Changes

File Change
pkg/audit/event.go Add OutcomeApplicationError constant
pkg/audit/config.go Add DetectApplicationErrors config flag with ShouldDetectApplicationErrors() helper
pkg/audit/auditor.go Add errorDetectionBody prefix buffer to responseWriter, JSON-RPC error check in logAuditEvent(), error metadata attachment
pkg/mcp/response.go Add ParseMCPResponse() helper for detecting JSON-RPC errors in response bodies
pkg/mcp/response_test.go 9 test cases covering error detection, valid responses, truncated JSON, edge cases
pkg/audit/auditor_test.go 7 test cases covering buffer capture, config toggle, and full middleware integration

Does this introduce a user-facing change?

Yes. Audit logs now distinguish between true successes and application-level failures. Events that previously showed outcome=success for JSON-RPC errors will now show outcome=application_error with jsonrpc_error_code and jsonrpc_error_message in metadata. This is enabled by default and can be disabled via the detectApplicationErrors audit config flag.

Special notes for reviewers

  • The errorDetectionBody buffer is only allocated when IncludeResponseData is false. When response data capture is already enabled, the existing rw.body buffer is reused to avoid double-buffering.
  • A prefix[0] == '{' guard skips JSON parsing for non-JSON responses (health checks, SSE keepalives).
  • Custom jsonRPCError/jsonRPCResponseEnvelope structs are used instead of jsonrpc2.DecodeMessage because the library's wireError type is unexported, making it impossible to extract the numeric error code from outside the package.
  • This approach only covers Streamable HTTP JSON responses. Legacy SSE transport (where POST returns 202 and the response arrives on a separate GET stream) is not covered — documented as a known limitation per discussion in Proxy audit middleware records outcome=success for JSON-RPC application-level errors in HTTP 200 responses #4678.

The audit middleware previously determined outcome solely from the HTTP
status code. Because MCP servers return errors inside HTTP 200 responses
per the JSON-RPC spec, application-level failures (expired tokens, API
errors, invalid parameters) were logged as outcome=success, making them
invisible in audit logs.

This change inspects the response body for a JSON-RPC error field when
the HTTP status is 200. If found, the outcome is recorded as
application_error with the error code and truncated message in metadata.

A new DetectApplicationErrors config flag (default true) controls the
behavior independently of IncludeResponseData, avoiding the need to
enable full response capture just for error detection.

Fixes stacklok#4678

Signed-off-by: Guillermo Gomez <guillermogomezmora@gmail.com>
@gmogmzGithub gmogmzGithub force-pushed the fix/audit-jsonrpc-error-detection branch from b87bbf7 to c4c842f Compare April 9, 2026 17:01
@github-actions github-actions bot added the size/M Medium PR: 300-599 lines changed label Apr 10, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 88.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.67%. Comparing base (96bee39) to head (c4c842f).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
pkg/audit/auditor.go 79.31% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4709      +/-   ##
==========================================
+ Coverage   68.63%   68.67%   +0.04%     
==========================================
  Files         516      517       +1     
  Lines       54113    54160      +47     
==========================================
+ Hits        37138    37194      +56     
+ Misses      14111    14103       -8     
+ Partials     2864     2863       -1     

☔ View full report in Codecov by Sentry.
📢 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proxy audit middleware records outcome=success for JSON-RPC application-level errors in HTTP 200 responses

1 participant