You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(proxy): run policy checks on config pass-through entries that omit auth - #43250
A config pass-through entry that omits auth skips every policy check after key auth
A key past its team budget still reaches the upstream through such an entry
Only auth: true runs the checks, and it also demands an allowed_passthrough_routes grant
Nothing at boot tells the admin an entry omitted auth
How it solves it:
Omitted auth now means any valid key, with the full policy checks
auth: true still needs a route grant, auth: false stays public
Config and DB entries read auth with the same bool parsing (yes, 0, "true")
With a DB, config entries no longer come back as auth: true copies
Boot warns once per process for every entry that omits auth
Intentional product change: an entry that omits auth now runs team, org, and end-user budgets, guardrails, model access, and team blocks on every caller, so a key those checks refuse on /v1/chat/completions is refused here with the same error. A key scoped to allowed_routes: ["llm_api_routes"] now reaches such an entry instead of getting a 403 about allowed_passthrough_routes. There is no longer a way to require a key and skip the checks: auth: false serves the entry without a key
User Flow
Before: a key on an over-budget team is refused on chat completions but sails through a config pass-through entry that omits auth
The proxy admin adds /echo -> https://postman-echo.com/get under general_settings.pass_through_endpoints with no auth key and restarts the proxy
The same developer sends GET https://litellm-domain/echo with the same key and gets 422 Budget has been exceeded! Team=..., the chat completions error
The boot log prints pass_through_endpoints entry '/echo' sets no \auth`: any valid LiteLLM key may call it ...once per worker, namingauth: trueandauth: false`
Keys those checks refuse on chat completions are refused on /echo the same way, and a key scoped to allowed_routes: ["llm_api_routes"] gets 200 on /echo instead of a 403
Relevant issues
Related to #36508: an entry that omits auth now counts as an LLM API route, so a key scoped to llm_api_routes reaches it instead of getting a 403
Linear ticket
Resolves LIT-8631
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
I have added meaningful tests
The handful of test files covering my change pass locally, e.g. 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
My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
My PR's scope is as isolated as possible; it only solves 1 specific problem
I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)
Screenshots / Proof of Fix
Shared setup for both legs. Each leg boots two proxy instances with --num_workers 2 from the same config, sharing one Postgres, and the cases alternate between the two instances (the port is in every command). The chat completions calls hit the real openai/gpt-5-mini and cost real money; they are how the team's spend gets past its budget
Keys and the team, created once with the master key and reused by both legs (the DB is shared):
# <key-plain>: a normal key with no route grants
curl -s http://127.0.0.1:$PORT/key/generate -H "Authorization: Bearer $MASTER" -H 'Content-Type: application/json' -d '{"key_alias":"lit8631-plain"}'# a team one chat call pushes past its budget, and <key-team-over-budget> on it, granted /echo-auth-true
curl -s http://127.0.0.1:$PORT/team/new -H "Authorization: Bearer $MASTER" -H 'Content-Type: application/json' -d '{"max_budget":0.000001}'
curl -s http://127.0.0.1:$PORT/key/generate -H "Authorization: Bearer $MASTER" -H 'Content-Type: application/json' -d '{"team_id":"<team-id>","key_alias":"teamtiny-key","allowed_passthrough_routes":["/echo-auth-true"]}'# one chat completion on that key, then ~20s for the spend write: team spend 3.4e-05 > max_budget 1e-06# <key-llm-api-routes>: scoped to the llm_api_routes group only
curl -s http://127.0.0.1:$PORT/key/generate -H "Authorization: Bearer $MASTER" -H 'Content-Type: application/json' -d '{"key_alias":"lit8631-llm-api-routes","allowed_routes":["llm_api_routes"]}'
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
{"error":{"message":"Key/team not allowed to access passthrough route /echo-auth-true. Configure `allowed_passthrough_routes` on the team or key.","type":"auth_error","param":"None","code":"403"}}
HTTP 403
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
team-over-budget key: GET /echo (auth omitted), other instance
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
team-over-budget key, granted the route: GET /echo-auth-true (auth: true)
{"error":{"message":"Budget has been exceeded! Team=0cceec4d-a1cf-4fd1-bea5-57d9e4c9c9d1 Current cost: 3.4e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"422"}}
HTTP 422
{"error":{"message":"Key/team not allowed to access passthrough route /echo-auth-true. Configure `allowed_passthrough_routes` on the team or key.","type":"auth_error","param":"None","code":"403"}}
HTTP 403
3
pass_through_endpoints entry '/echo' sets no `auth`: any valid LiteLLM key may call it. Set `auth: true` to restrict it to keys granted `allowed_passthrough_routes`, or `auth: false` to serve it without a key. A future release will treat a missing `auth` as `auth: true`.
(3 lines on each instance: the CLI pre-load plus its two workers)
auth: true still refuses keys without a route grant, unchanged
Grant-denied 403 body differs by key shape, pre-existing, left alone
Boot warning prints three times per 2-worker instance, this PR
Docs still label auth Enterprise, separate docs PR follows
Type
🐛 Bug Fix
Caveats (if any)
Medium
Subpaths of an omitted-auth include_subpath entry now take any valid key
Before, they were admin-only without a DB and grant-gated with one
Keys scoped to allowed_routes: ["openai_routes"] and team default allowlists reach them now
auth: 1, "true", "yes", or "on" was public and now needs a key plus a grant
Only the team-budget check ran live; the other checks share its code path
Admin UI pass-through page should list config entries read-only now, not re-checked
Low
The boot warning repeats per worker process plus the CLI pre-load
Flipping the default to auth: true is follow-up work, the warning names it
auth: false is now the only way to skip the checks, and it drops the key too
The listing still shows auth: true for an omitted-auth config entry, pre-existing, folds into the default flip
Config entries in the listing carry a per-worker random id no lookup or delete accepts, pre-existing
Omitted-auth paths now count as LLM routes for DISABLE_LLM_API_ENDPOINTS, OAuth2 routing, and the OpenAPI filter, not driven live
A config entry with no target lands in the LLM route list though no route registers
Docs PR litellm-docs#1739 lands right after this one
CircleCI local_testing_part1 at the tip fails only test_get_model_info_bedrock_cross_region_capability_parity, red on main too (pipelines 90441 and 90403), untouched here
CircleCI using_litellm_on_windows at the tip timed out on Install Dependencies with zero tests, as on main pipelines 90442 and 90403
CircleCI langfuse_logging_unit_tests at the tip timed out in Run tests with zero tests, as on main pipelines 90441 and 90403
CircleCI unit at the tip has 35 reds, 31 of them the same tests main pipeline 90441 fails, three test_mcp_client_unit tests that pass locally at the tip and fail on main 90403 too, and test_the_repo_as_it_stands_has_every_shard_child_assigned, which fails at the merge base and is fixed on main by test(zerobus): move tests into active CI selection #43235, so it clears on merge
CircleCI integration-accounting at the tip first failed test_rotated_keys_users_and_model_groups_preserve_success_failure_cache_ledger with the same owned-HTTP-peer GET-instead-of-POST assertion main pipeline 90389 hit, and passed on the rerun from failed
CircleCI proxy_store_model_in_db_tests at the tip fails only test_e2e_langfuse_callbacks_in_db (request not found in Langfuse traces), the same single red as main pipelines 90441, 90403, and 90394
CircleCI build_and_test at the tip fails only the two rust-python-harness namespace import cases (ocr sdk parity modules), the same pair main pipelines 90441, 90403, and 90394 fail
CircleCI integration-extensions at the tip hit the 60 minute step cap at 86% and reported no junit, the same cap main pipelines 90441, 90403, 90394, and 90381 hit; the first run's log shows every pass-through visibility and chaos test PASSED and only test_straiker_v3_platform.py::test_burst_survives_one_worker_kill FAILED (a /v1/chat/completions worker-kill burst, nothing pass-through), and the rerun from failed hit the same cap with the straiker test PASSED and only test_passthrough_worker_sigkill_leaves_sibling_serving_and_logging FAILED, which main 90441 fails and 90403 passes and which drives the built-in /gemini provider route this diff does not touch
Final 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
[Critical risk] Changes how pass-through endpoints enforce authentication.
The PR appears safe to merge based on the current review.
Summary
This PR runs policy checks for config pass-through entries that omit auth, preserves explicit public and route-granted modes, aligns auth-value parsing, avoids registering config-owned entries twice, and adds configuration warnings and tests.
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
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:
authskips every policy check after key authauth: trueruns the checks, and it also demands anallowed_passthrough_routesgrantauthHow it solves it:
authnow means any valid key, with the full policy checksauth: truestill needs a route grant,auth: falsestays publicauthwith the same bool parsing (yes,0,"true")auth: truecopiesauthIntentional product change: an entry that omits
authnow runs team, org, and end-user budgets, guardrails, model access, and team blocks on every caller, so a key those checks refuse on/v1/chat/completionsis refused here with the same error. A key scoped toallowed_routes: ["llm_api_routes"]now reaches such an entry instead of getting a 403 aboutallowed_passthrough_routes. There is no longer a way to require a key and skip the checks:auth: falseserves the entry without a keyUser Flow
Before: a key on an over-budget team is refused on chat completions but sails through a config pass-through entry that omits
auth/echo->https://postman-echo.com/getundergeneral_settings.pass_through_endpointswith noauthkey and restarts the proxyBudget has been exceeded! Team=.../echountil its own key budget runs outAfter: the same key gets the same 422 on
/echo, and the boot log names the entry/echo->https://postman-echo.com/getundergeneral_settings.pass_through_endpointswith noauthkey and restarts the proxyBudget has been exceeded! Team=...Budget has been exceeded! Team=..., the chat completions errorpass_through_endpoints entry '/echo' sets no \auth`: any valid LiteLLM key may call it ...once per worker, namingauth: trueandauth: false`/echothe same way, and a key scoped toallowed_routes: ["llm_api_routes"]gets 200 on/echoinstead of a 403Relevant issues
Related to #36508: an entry that omits
authnow counts as an LLM API route, so a key scoped tollm_api_routesreaches it instead of getting a 403Linear ticket
Resolves LIT-8631
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)Screenshots / Proof of Fix
Shared setup for both legs. Each leg boots two proxy instances with
--num_workers 2from the same config, sharing one Postgres, and the cases alternate between the two instances (the port is in every command). The chat completions calls hit the realopenai/gpt-5-miniand cost real money; they are how the team's spend gets past its budgetKeys and the team, created once with the master key and reused by both legs (the DB is shared):
Before (cd1107a)
plain key: POST /v1/chat/completions (real provider call)
Run
Observed
no key: GET /echo (auth omitted)
Run
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:32759/echoObserved
plain key: GET /echo (auth omitted)
Run
Observed
team-over-budget key: POST /v1/chat/completions control
Run
Observed
team-over-budget key: GET /echo (auth omitted)
Run
Observed
team-over-budget key: GET /echo (auth omitted), other instance
Run
Observed
team-over-budget key, granted the route: GET /echo-auth-true (auth: true)
Run
Observed
no key: GET /echo-auth-false (auth: false)
Run
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:32759/echo-auth-falseObserved
llm_api_routes-only key: GET /echo (auth omitted)
Run
Observed
llm_api_routes-only key: GET /echo-auth-true (auth: true)
Run
Observed
plain key, no grant: GET /echo-auth-true (auth: true)
Run
Observed
boot log: warning for the entry that omits
authRun, on each instance's boot log
Observed
After (31bbe05)
plain key: POST /v1/chat/completions (real provider call)
Run
Observed
no key: GET /echo (auth omitted)
Run
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:57122/echoObserved
plain key: GET /echo (auth omitted)
Run
Observed
team-over-budget key: POST /v1/chat/completions control
Run
Observed
team-over-budget key: GET /echo (auth omitted)
Run
Observed
team-over-budget key: GET /echo (auth omitted), other instance
Run
Observed
team-over-budget key, granted the route: GET /echo-auth-true (auth: true)
Run
Observed
no key: GET /echo-auth-false (auth: false)
Run
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:57122/echo-auth-falseObserved
llm_api_routes-only key: GET /echo (auth omitted)
Run
Observed
llm_api_routes-only key: GET /echo-auth-true (auth: true)
Run
Observed
plain key, no grant: GET /echo-auth-true (auth: true)
Run
Observed
boot log: warning for the entry that omits
authRun, on each instance's boot log
Observed
auth: truestill refuses keys without a route grant, unchangedauthEnterprise, separate docs PR followsType
🐛 Bug Fix
Caveats (if any)
Medium
include_subpathentry now take any valid keyallowed_routes: ["openai_routes"]and team default allowlists reach them nowauth: 1,"true","yes", or"on"was public and now needs a key plus a grantLow
auth: trueis follow-up work, the warning names itauth: falseis now the only way to skip the checks, and it drops the key tooauth: truefor an omitted-auth config entry, pre-existing, folds into the default flipDISABLE_LLM_API_ENDPOINTS, OAuth2 routing, and the OpenAPI filter, not driven livetargetlands in the LLM route list though no route registersFinal Attestation