/v3/login now returns a single-use opaque code (60s TTL) instead of the
JWT directly. A new /v3/login/exchange endpoint redeems the code for the
actual JWT. This prevents the JWT from appearing in the initial login
response, reducing exposure if the response is logged or intercepted.
- /v3/login now returns 404 unless control_plane_url is configured
- _validate_return_to uses urlparse().hostname for case-insensitive comparison
- Add rejection test for /v3/login without control_plane_url
- Add case-insensitive hostname test for _validate_return_to
Add origin validation for the return_to parameter in SSO flow to prevent
JWT theft via crafted redirect URLs. Workers must configure control_plane_url
in general_settings.
- Add return_to param to /sso/key/generate for cross-origin redirect
- Store return_to in httpOnly cookie during SSO redirect
- Read cookie in /sso/callback and pass to OpenID handler
- Redirect to control plane with token in URL, delete cookie after use
/v2/login is battle-tested; control-plane token-in-body behavior belongs
only in /v3/login. Removes the conditional worker_registry check from v2
and its associated test.
Workers don't have worker_registry, so /v2/login omits the token
from the response body. The control plane UI needs the token in the
body to set the cookie cross-origin (document.cookie) when
authenticating against a worker. /v3/login always includes it.
Explicitly mock proxy_config.worker_registry in the existing login test
to isolate it from global state. Add positive test verifying the token
is returned in the response body when workers are configured.
Only return JWT token in /v2/login response body when workers are
configured (control plane mode). Non-CP instances continue to set
the cookie only, avoiding unnecessary token exposure. Also make
workers list in discovery endpoint explicitly conditional on
is_control_plane flag.
Add backend support for a control plane mode where one LiteLLM instance
serves as a worker directory. Parses `worker_registry` from YAML config,
exposes `is_control_plane` flag and worker list via the existing
`/.well-known/litellm-ui-config` endpoint, and returns JWT in /v2/login
JSON body for cross-origin auth.
- WorkerRegistryEntry pydantic model with HTTP URL validation
- Config parsing in ProxyConfig._init_non_llm_configs
- is_control_plane + workers fields on UiDiscoveryEndpoints response
- Example control_plane_config.yaml
- Tests for discovery endpoint and /v2/login response shape
Instead of hardcoding SPEND_PER_REQUEST (which broke when the model
changed from gpt-3.5-turbo-0301 to gpt-3.5-turbo), make a single
calibration request first, poll for its spend, and use that as the
per-request cost. Fails fast with pytest.fail() after 5 retries if
calibration cannot determine the cost.
Also fixes a bug in test_basic_spend_accuracy where the user spend
assertion error message referenced user_info['info'] instead of
user_info['user_info'].
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same deprecated model fix as proxy_server_config.yaml — these two CI
configs also referenced gpt-3.5-turbo-0301 which has no pricing data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase wait timeout to 90s and pytest.fail() instead of silently
continuing, so the failure message points at the real cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gpt-3.5-turbo-0301 was removed from the model cost map, so every call
had response_cost=0 and team member spend never increased. The wait
helper also returned True after 3s regardless of whether spend updated.
- Switch fake-openai-endpoint to gpt-3.5-turbo (has pricing in cost map)
- Remove premature early-return in wait_for_team_member_spend_update
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase max_retries from 6 to 9 and retry_delay from 10s to 20s
(180s total wait, up from 60s) to give batch cost tracking more time
to finish before cleanup attempts file deletion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The terminal-state DB shortcut in retrieve_batch returned a LiteLLMBatch
with empty _hidden_params, causing the managed_files hook to skip encoding
output_file_id into a unified ID. This adds the same model_id extraction
from unified_batch_id that the non-terminal path already has.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- realtime_api/main.py: Revert param types to Dict, explicitly construct
RealtimeSessionConfig/RealtimeExpiresAfter before passing to
RealtimeClientSecretRequest
- presidio.py: Move type:ignore[override] to def line where mypy reports it
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test_e2e_managed_batch test intermittently fails during cleanup
when deleting the input file — the batch cost tracking hasn't finished
processing yet (batch_processed=true not set), causing a 400 error.
This is a timing race condition unrelated to batch retrieval logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Union[RealtimeClientSecretResponse, Response] annotation breaks
FastAPI's response model generation. Revert to the original annotation
and suppress mypy on the error-path return instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The terminal-state DB shortcut in retrieve_batch returned a LiteLLMBatch
with empty _hidden_params, causing the managed_files hook to skip encoding
output_file_id into a unified ID. This adds the same model_id extraction
from unified_batch_id that the non-terminal path already has.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- realtime_api/main.py: Widen param types to accept both Dict and Pydantic models
- proxy/realtime_endpoints/endpoints.py: Widen return type to Union[..., Response]
- proxy/guardrails/guardrail_hooks/presidio.py: Add type:ignore[override] for bytes in streaming return
- proxy/_experimental/mcp_server/rest_endpoints.py: Annotate _oauth2_flow with Literal type
- proxy/management_endpoints/ui_sso.py: Add httpx import under TYPE_CHECKING, remove invalid timeout kwarg from AsyncHTTPHandler.get()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(proxy): cap managed-object poll size + expire stale rows + kill-switch flag to prevent OOM/Prisma connection loss
* fix(constants): simplify PROXY_BATCH_POLLING_ENABLED readability
* docs+test: document new polling env vars, add pagination+stale-cleanup tests
* fix: exclude stale_expired from batch poll queries; fix update_many assertions in tests
* fix: scope stale cleanup to file_purpose, fix file_object mocks, add CheckBatchCost tests
* fix: avoid duplicate cost logging in fallback path; guard integer constants against zero/negative values
* fix: cache _has_batch_processed_column; guard cleanup from aborting poll; narrow fallback except
* fix: add complete/completed to primary query not_in; fix vacuous test assertion
- Primary find_many was missing "complete" and "completed" in its not_in
filter, creating asymmetry with the fallback query. A job whose status
was set to "complete" but whose batch_processed flag update failed would
be silently re-fetched and re-processed every cycle, emitting duplicate
cost logs.
- test_fallback_completion_update_omits_batch_processed patched
_is_base64_encoded_unified_file_id to return None, causing an immediate
continue — so update() was never called and the assertion looped over an
empty list (vacuously true). Rewrote the test to mock the full
completion pipeline, verify update() is called exactly once, and assert
batch_processed is absent from the update data.
- Added symmetric test (primary path) proving batch_processed IS included
when the column exists.
Made-with: Cursor
The recursive implementation was flagged by the recursive function detector
lint check. Converted to an iterative approach using an explicit stack and
seen set, with depth capped at DEFAULT_MAX_RECURSE_DEPTH.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fix (124b44ec) only updated 3 tests but missed 10 more
that still patched the old `ui_sso.httpx.AsyncClient` path. Also
updated credential assertions to check Authorization header instead
of httpx.BasicAuth kwargs, matching the production code change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test used fallbacks=[{"gpt-3.5-turbo": ["123"]}] where "123" is a
model_id, but the fallback mechanism treats values as model group names.
This caused a ValueError since no model group "123" exists. Additionally,
mock_response propagates to fallback calls, making mock-based fallback
tests unreliable.
Simplified the test to verify that a RateLimitError doesn't permanently
cool down a deployment for subsequent requests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test was creating a real AsyncHTTPHandler instance and patching its
post method, but the internal code creates its own handler, bypassing
the mock. This caused real API calls to Vertex AI, resulting in 401
auth errors in CI. Switched to patching AsyncHTTPHandler at the class
level, matching the pattern used by the passing GPT-OSS test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test fails with InvalidIdentityToken because the OIDC provider is
no longer configured in the third-party AWS account (ai.moda). This
matches the existing quarantine on test_oidc_circleci_with_azure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>