Skip to content

proxy: an allow decision can observe the response body - #799

Open
thomaslwang wants to merge 1 commit into
anthropics:mainfrom
thomaslwang:response-body-observer
Open

thomaslwang wants to merge 1 commit into
anthropics:mainfrom
thomaslwang:response-body-observer

Conversation

@thomaslwang

Copy link
Copy Markdown

filterRequest can read a request's body and onResponse reports a response's status and headers, but nothing exposes the response body. A consumer watching an agent's model traffic therefore cannot see the tool calls, which only appear in the reply. A client that resends its conversation shows them in its next request, but Claude Code 2.1 was measured sending only the new tool_result.

This adds two observers to an allow decision:

  • onResponseChunk(chunk) gets a copy of each body chunk as the upstream sent it. A compressed body stays compressed.
  • onResponseEnd(complete) is called once. complete is false when the upstream or client went away part-way, or when the proxy replaced an unrelayable response with its 502.

Scope. They observe only, like onResponse: they cannot change or hold the response, and a throw or rejection is logged and ignored. They cover the plain HTTP, parent proxy, MITM socket and TLS-terminated paths. With neither set, no listener is attached. The proxy buffers nothing for them, so what a consumer keeps is its own to bound.

Not included. Holding or rewriting a response before the client sees it. That is a different security surface and I'd rather discuss it separately if there is interest.

Testing.

  • 7 new cases in test/sandbox/request-rewriting.test.ts, on the plain and TLS-terminated paths where both apply.
  • Full suite on macOS with Bun 1.4.2: 3412 pass and 7 fail, against 3405 pass and 7 fail on main without this change. I compared the counts, not the names of the failures.
  • tsc, eslint and prettier are clean.
  • The same cases by hand on Node 22 through the plain path: whole, streamed, 204, cut off and unrelayable responses.
  • Real Claude Code 2.1 through a sandbox with tlsTerminate: the reply body showed the tool_use that the next request did not contain.

The parent proxy and MITM socket paths have the same one-line change but no dedicated new test.

🤖 Generated with Claude Code

onResponse reports a response's status and headers, and a request's body is
already readable through filterRequest. The response body had no such hook, so
a consumer could not see what came back: for an agent's model traffic, that is
where the tool calls are.

onResponseChunk gets a copy of each chunk as the upstream sent it, and
onResponseEnd is called once and says whether the whole body arrived. Both are
observers, like onResponse: they cannot change or hold the response, and their
failures are logged and ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant