fix(responses-bridge): decrypt proxy-managed previous_response_id for session lookup - #36360
Conversation
Greptile SummaryThe PR restores stateful Responses API chaining when proxy-managed response-ID protection is enabled and extends the ownership check to response compaction.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/proxy/hooks/responses_id_security.py | Extends the existing ownership-check and decryption path to compact-response requests without an eligible residual issue. |
| litellm/responses/litellm_completion_transformation/session_handler.py | Resolves prior response IDs before spend-log lookup and introduces injectable Prisma and decoder dependencies. |
| litellm/responses/utils.py | Adds proxy-layer decryption ahead of managed response-ID decoding while preserving fallback behavior for unencrypted IDs. |
| tests/test_litellm/responses/litellm_completion_transformation/test_session_handler.py | Adds mocked coverage for decoded lookup keys, the Prisma accessor seam, and the unavailable-client path. |
| tests/test_litellm/responses/test_responses_utils.py | Covers layered ID decoding, injected security-hook behavior, lazy hook resolution, and plain-ID fallback. |
| tests/test_litellm/test_responses_id_security.py | Verifies compact-response requests apply the existing encrypted previous-response ownership path. |
Reviews (3): Last reviewed commit: "fix(responses-bridge): check compaction ..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
63ccc40 to
c9b0c9e
Compare
c9b0c9e to
173124f
Compare
|
Note: the |
TLDR
Problem this solves:
/v1/responseschaining through the chat-completions bridge loses all prior context when Responses ID security is enabledresp_...ID is encrypted, while spend logs are keyed by the internal chat-completion request IDprevious_response_idtherefore resolves to no session and the follow-up starts a fresh conversationHow it solves it:
/v1/responses/compactbefore the shared decoder runsUser Flow
Before: a developer sends a two-turn stateful conversation and the second turn forgets the first
POST https://litellm-domain/v1/responseswith"input": "My favorite color is blue."and receiveresp_...and HTTP 200POST https://litellm-domain/v1/responsesagain withprevious_response_idset to that ID and"input": "What is my favorite color?"After: the same two-turn flow keeps the conversation
POST https://litellm-domain/v1/responsesprevious_response_id"Blue"Relevant issues
No GitHub issue filed yet.
Linear ticket
Pre-Submission checklist
Screenshots / Proof of Fix
Live proof against the running proxy (real DeepSeek V4 calls).
Before (stock container, no fix):
{ "status": "incomplete", "output": [ {"type": "reasoning", "content": [{"type": "output_text", "text": "We need answer user asks favorite color, but we don't know..."}]}, {"type": "message", "content": [{"type": "output_text", "text": ""}]} ], "usage": {"input_tokens": 95, "output_tokens": 256} }After (original capture at commit
73384cce13; same behavior rebased ontolitellm_internal_stagingas173124f8a9with the compaction ownership and DI fixes, spend-log row flushed before the follow-up):{ "status": "completed", "output": [ {"type": "reasoning", "content": [{"type": "output_text", "text": "We previously said favorite color is blue. So answer just \"Blue\"."}]}, {"type": "message", "content": [{"type": "output_text", "text": "Blue"}]} ], "usage": {"input_tokens": 135, "output_tokens": 45} }Note: the session handler reads the prior turn from the spend-log row, so the follow-up must be sent after the spend-log write has landed.
Security coverage:
ResponsesIDSecurity.async_pre_call_hooknow treatsacompact_responseslikearesponses, checking ownership of an encryptedprevious_response_idbefore the shared decoder runs.Local suites:
67 passedfor the targeted responses/security tests;ruff checkandruff format --checkclean on the changed files; all three repo gates pass locally.Type
🐛 Bug Fix
Changes
ResponsesAPIRequestUtils.decode_previous_response_id_to_original_previous_response_iddecrypts the proxy-encrypted layer before the LiteLLM-managed decodeResponsesSessionHandler.get_all_spend_logs_for_previous_response_iduses the decoder so spend-log lookups receive the original request ID, with injectable Prisma client/decoder seams for testsResponsesIDSecurity.async_pre_call_hooknow checks ownership onacompact_responsesrequestsQA runbook
N/A — this PR adds mocked unit tests, not e2e tests. The live proof above was captured manually.
Final Attestation