fix(passthrough): record ownership of streamed responses under managed ids - #38320
Merged
mateo-berri merged 2 commits intoAug 26, 2026
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
Greptile SummaryThis follow-up adds streamed-response ownership hardening and completes the requested CR-only SSE boundary handling and comment cleanup.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/proxy/common_utils/sse_keepalive.py | The shared splitter now recognizes CR-only frame boundaries, resolving the previously reported incomplete SSE handling. |
| litellm/proxy/pass_through_endpoints/managed_id_rewriter.py | Adds buffered streamed-response ID minting and rewriting; the previously reported decorative heading has been removed. |
| litellm/proxy/pass_through_endpoints/pass_through_endpoints.py | Integrates managed-ID ownership into eligible passthrough streaming response paths behind the existing feature gate. |
| litellm/proxy/pass_through_endpoints/streaming_handler.py | Reuses the centralized SSE frame splitter without changing the surrounding logging lifecycle. |
| tests/test_litellm/proxy/common_utils/test_sse_keepalive.py | Adds coverage for all supported SSE delimiters and unterminated tails. |
| tests/test_litellm/proxy/pass_through_endpoints/test_managed_id_rewriter.py | Covers streamed ownership, arbitrary chunking, CR-only framing, unaffected routes, and persistence-failure fallback. |
| tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py | Adds an integration-level mocked passthrough test confirming ownership persistence and consistent streamed ID replacement. |
Reviews (3): Last reviewed commit: "fix(passthrough): recognize CR-only SSE ..." | Re-trigger Greptile
Contributor
Author
… streamed managed ids
Contributor
Author
Contributor
Author
|
bugbot run |
Contributor
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6a9662a. Configure here.
mateo-berri
enabled auto-merge
August 26, 2026 09:11
yucheng-berri
approved these changes
Aug 26, 2026
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Problem this solves:
resp_idHow it solves it:
response.createdeventresponse.idinside every SSE frame as it is relayedUser Flow
Before: a developer on key A streams a response through the OpenAI passthrough and gets OpenAI's own
resp_...id back, so any other key on the gateway can read and delete it, and the first one to read it becomes its owner{"model": "gpt-5.1", "input": "Reply with the single word mango.", "stream": true, "store": true}"id": "resp_04bd26a8...", OpenAI's raw id, instead of the long scrambled id the same call returns withoutstream: true"deleted": trueManaged resource not found.After: the same streamed response comes back under a scrambled gateway id owned by key A, so other keys are refused
{"model": "gpt-5.1", "input": "Reply with the single word mango.", "stream": true, "store": true}bGl0ZWxsbV9wcm94eTpwYXNz...), the same shape as a non-streamed responseAccess denied to managed resource.Access denied to managed resource."deleted": trueRelevant issues
Linear ticket
Resolves LIT-6165
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Each leg ran two separate proxy processes, each with
--num_workers 2, on random ports sharing one Postgres. Both virtual keys were minted through instance 1, key A creates through instance 1, and key B's requests plus key A's own-read go through instance 2, so every ownership check crossed processes. Config (passthrough_managed_object_idson, real OpenAI key);KEY_Ahasuser_id: user-a,KEY_Bhasuser_id: user-b. Long managed ids are shortened tobGl0ZWxsbV9wcm94eTpwYXNz...below; full ids and untruncated bodies are in the QA logs (lit6165_qa_before.out/lit6165_qa_after.out)Before (e52f055, instances on P1=46566 and P2=47062)
Streamed response: key B reads, continues from, and deletes key A's response through the other instance
Non-streamed response (control, already protected)
streamthrough instance 1; the id is already a managed idAfter (6a9662a, instances on P1=38401 and P2=52717)
Streamed response: key B is refused through the other instance
response.createdon (10 data lines, 1 distinct id, 0 lines with a rawresp_id)Non-streamed response (control, unchanged)
Closing observations from the run:
[DONE]preserved: unaffected"type": "None": pre-existing, left aloneType
🐛 Bug Fix
Caveats (if any)
Medium
/openai_passthrough/*and/azure/openai/*prefixes are covered/openai/v1/responsesis the native Responses route, and streams there still skip the ownership check (separate ticket)Low
passthrough_managed_object_ids: trueFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
6a9662a passes /live-pr-risk
Note
High Risk
Changes authorization and multi-tenant isolation for streamed Responses passthrough (auth/access control on a security-sensitive path), though behavior is gated behind
passthrough_managed_object_idsand limited to Responses SSE routes.Overview
Fixes a cross-tenant gap for streamed OpenAI Responses passthrough: SSE bodies used upstream raw
resp_ids with no ownership row, so other API keys could read, continue, or delete those responses (and sometimes become the owner on first read).Streaming path now mirrors non-streamed managed-id behavior. When
passthrough_managed_object_idsis on and the route is POST/v1/responses, the proxy reassembles complete SSE frames (sharedsplit_complete_sse_frames, including CR-only delimiters), mints or reuses a managed object id from the firstresponse.createdevent, persists the caller as owner, and byte-replaces the raw id in every relayed frame. Other routes and disabled flags pass the stream through unchanged; if persistence fails, the stream stays raw.Passthrough streaming responses wire this via
_own_streamed_managed_idsahead of SSE keepalive wrapping; cost-injection streaming reuses the same frame splitter instead of a local duplicate.Reviewed by Cursor Bugbot for commit 6a9662a. Bugbot is set up for automated code reviews on this repo. Configure here.