Commit Graph

252 Commits

Author SHA1 Message Date
yuneng-jiang
c3f7158b2b
Merge pull request #27008 from stuxf/fix/jwt-audience-and-issuer-verification
fix(auth): support JWT issuer verification + warn when unscoped
2026-05-01 19:58:52 -07:00
shin-berri
38ddcdabdb
Merge pull request #27032 from BerriAI/litellm_yj_may1_2
[Infra] Merge dev branch
2026-05-01 19:39:42 -07:00
yuneng-jiang
5614469f22
Merge pull request #26825 from stuxf/fix/oauth2-proxy-header-forgery
chore(auth): require trusted proxy for header identity auth
2026-05-01 18:47:58 -07:00
Yuneng Jiang
b484c51a1c
[Fix] Proxy: Repair Merge Fallout In Router-Override Fallback Auth
Conflict resolution for #26968 dropped the `Iterator` typing import
(NameError at module load), left a dead `fallback_models = cast(...)`
block, and the new tests called `_enforce_key_and_fallback_model_access`
without the now-required `request` kwarg.
2026-05-01 17:48:51 -07:00
yuneng-jiang
8fc31a7b3a
Merge branch 'litellm_yj_may1_2' into chore/router-override-trust 2026-05-01 17:26:04 -07:00
yuneng-jiang
8ed6c0cdea
Merge pull request #26846 from BerriAI/litellm_/pensive-bartik-e24048
[Fix] RBAC: Restore Admin Viewer Read Parity for Logs + Settings Pages
2026-05-01 16:36:36 -07:00
Yuneng Jiang
6499fa76de
[Fix] RBAC: Drop management_routes Write Fallback for Admin Viewer
Greptile P1: the unsafe-method branch of `_check_proxy_admin_viewer_access`
ended with a blanket `if route in management_routes: return`. That set is a
mix of reads (info/list — handled via the safe-method GET branch above) and
writes. The fallback let Admin Viewer POST to write endpoints not enumerated
in `_ADMIN_VIEWER_BLOCKED_WRITE_ROUTES`, including:
  - /team/block, /team/unblock, /team/permissions_update
  - /jwt/key/mapping/{new,update,delete}
  - /key/bulk_update
  - /key/{key_id}/reset_spend

Remove the fallback. The two remaining allow sets (admin_viewer_routes and
global_spend_tracking_routes) are both read-only, so removal does not affect
the legitimate POST-as-read cases (e.g. /spend/calculate, which is in
spend_tracking_routes ⊂ admin_viewer_routes).

Tests:
  - 8 new parametrized cases pinning each previously-leaking management write
    endpoint to 403 on POST for PROXY_ADMIN_VIEW_ONLY.
2026-05-01 16:15:21 -07:00
Yuneng Jiang
c78144ccf0
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/pensive-bartik-e24048
# Conflicts:
#	ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx
2026-05-01 16:04:09 -07:00
yuneng-jiang
c2cea58567
Merge branch 'litellm_yj_may1' into codex/budget-race-enforcement 2026-05-01 14:32:18 -07:00
yuneng-jiang
9e501edece
Merge branch 'litellm_yj_may1' into codex/file-endpoint-model-auth 2026-05-01 14:22:18 -07:00
yuneng-jiang
f34a2752f6
Merge pull request #26996 from stuxf/chore/ssrf-polling-and-nested-config
chore(security): close two unaddressed SSRF cases
2026-05-01 14:16:39 -07:00
user
e55401e39c
fix(auth): support JWT issuer verification, scope-warning when unscoped
When JWT auth is enabled but `JWT_AUDIENCE` is unset, `auth_jwt`
disabled audience verification entirely. Tokens minted by any other
application that shared the same IdP signing keys (Azure AD, Okta,
etc.) were accepted as long as their signature checked out, even
though their `aud` and `iss` claims pointed at unrelated apps. The
proxy then fell into the no-team / no-user branch where access checks
default-allow.

This change:

1. Adds support for the `JWT_ISSUER` env var. When set, PyJWT verifies
   the token's `iss` claim — turning on the same defense for tokens
   that share an audience but come from a different IdP tenant.
2. Refactors the duplicated `jwt.decode` calls (RSA/EC/OKP path and
   x509 path) into a single `_build_decode_kwargs` helper that
   computes audience, issuer, and the corresponding `verify_*` opt-outs
   once per call.
3. Logs a single startup-time warning when JWT auth is enabled but
   neither `JWT_AUDIENCE` nor `JWT_ISSUER` is configured, so operators
   running the insecure default see a flag in their logs without
   getting spammed per-request.

Default behavior (no env vars) is preserved for backward compatibility.
Setting `JWT_AUDIENCE` and/or `JWT_ISSUER` opts into the verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:10:19 +00:00
user
90fd791e0d
fix(security): close P1 recursion-DoS + P2 hostname leak in SSRF fixes
Greptile follow-ups on the prior commit:

- (P1) ``is_request_body_safe`` recursed into ``litellm_embedding_config``
  with no depth bound, so a request body 1000 levels deep could exhaust
  Python's call stack and surface a 500 ``RecursionError``. Refactored
  the check to be iterative (single-level descent into a fixed list of
  nested-config keys) and extracted the per-dict banned-param scan into
  a helper that's shared between the root and the nested call sites.
  Also fixes the ``recursive_detector`` CI job that was triggered by
  the recursive-by-name pattern.

- (P2) ``assert_same_origin`` error messages identified the mismatching
  component but echoed the ``expected`` host and the candidate
  hostname back to the caller. In the SSRF threat model the caller is
  the attacker, so reflecting that information was a secondary leak of
  operator infrastructure. Messages now identify only *which*
  component mismatched (scheme / host / port) without naming names.

- (P2) ``_NESTED_CONFIG_KEYS`` was defined after the function that used
  it. Hoisted the constant (and the new ``_BANNED_REQUEST_BODY_PARAMS``
  tuple) above the function for readability.

Adds a 1000-level-deep nested config test that asserts no
``RecursionError`` and a hostname-leak test that asserts no operator
host appears in the rejection message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:52:55 +00:00
user
0d4875dec9
chore(security): close two unaddressed SSRF cases
Two SSRF findings were OPEN with no in-flight fix; both are closed
now using narrow defenses that key off existing trust boundaries.

VERIA-6 (Milvus ``litellm_embedding_config``):
``is_request_body_safe`` already blocks ``api_base`` / ``api_key`` /
``langfuse_host`` / ``s3_endpoint_url`` / etc. at the *root* of the
request body, gated by an admin opt-in (``allow_client_side_credentials``
or per-deployment ``configurable_clientside_auth_params``). The bug is
that the Milvus vector-store transformer unpacks
``litellm_embedding_config`` into ``litellm.embedding(**embedding_config)``,
so a caller can smuggle the same banned params in via nesting and bypass
the check. Fix: ``is_request_body_safe`` now recurses into a known list
of nested-config dicts (``litellm_embedding_config`` for now) and applies
the same banned-param check with the same admin opt-in. Admin-side
vector-store config flows through ``litellm_params`` rather than the
request body, so it's unaffected.

VERIA-51 (polling URLs returned by upstream APIs):
Azure DALL-E 2, Azure Document Intelligence, and Black Forest Labs
all blindly fetched a polling URL returned by the upstream and
attached the operator's API key to the request. A compromised upstream
or a future API contract change could redirect credentials anywhere.
New ``url_utils.assert_same_origin(candidate, expected)`` helper checks
scheme, host (case-insensitive), and port (with default-port
normalization). Applied at all five polling sites: Azure DALL-E
sync+async, Azure DI sync+async, BFL image generation sync+async, BFL
image edit sync+async. Cross-origin polling URLs now raise rather than
forward credentials. The Azure DALL-E ``Expected 'status' in response``
exception no longer reflects the raw response body — that path turned
Blind SSRF into Full-Read SSRF for the limited window before the
origin check fully closed it.

Tests: 7 ``assert_same_origin`` unit tests, 6 ``is_request_body_safe``
nested-config tests, 5 polling-site rejection tests + 1 same-origin
sanity check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:43:47 +00:00
user
2ed4b5968d Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD
# Conflicts:
#	litellm/proxy/auth/auth_checks.py
2026-05-01 11:37:43 -07:00
user
cc9700f1da
Merge remote-tracking branch 'upstream/main' into fix/router-override-trust
# Conflicts:
#	tests/test_litellm/proxy/test_route_llm_request.py
2026-05-01 07:55:44 +00:00
user
a5b7eeebdc
chore(proxy): close router-settings-override fallback smuggling path
Two changes that together prevent a caller from smuggling unauthorized
models past the API key's allowlist via per-request router overrides.

1. ``_enforce_key_and_fallback_model_access``: also walk fallback models
   nested inside ``router_settings_override.fallbacks`` /
   ``context_window_fallbacks`` / ``content_policy_fallbacks``.
   ``route_llm_request.py`` promotes those to per-request kwargs after
   auth, so without this they bypassed the model allowlist entirely.
   New ``iter_router_fallback_model_names`` helper extracts leaf names
   from both the simple top-level shape (str | {"model": str}) and the
   nested router-config shape ({primary: [fallbacks]}). The two fallback
   validation loops are unified — every name (top-level + override) is
   deduplicated and validated once via ``can_key_call_model`` +
   ``is_valid_fallback_model``.

2. ``route_request``: strip router-internal ``mock_testing_*`` flags
   from user-supplied data. These are testing-only flags that
   deterministically force the router into fallback logic by raising a
   synthetic ``InternalServerError`` etc. Combined with override
   fallbacks they made the smuggling path trivially exploitable. Test
   code that calls the router directly bypasses the strip and is
   unaffected. The strip list is derived from ``MockRouterTestingParams``
   so a new ``mock_testing_*`` flag added to that dataclass is
   automatically covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 07:49:32 +00:00
user
6ef26945fa test(proxy): narrow media resource decoding 2026-04-30 22:55:00 -07:00
user
0704f672c5 test(proxy): cover resource model extraction fallbacks 2026-04-30 22:21:57 -07:00
user
336fe8276f chore(proxy): align resource model auth checks 2026-04-30 21:59:56 -07:00
user
ce17639cf7 remove budget reservation disable flag 2026-04-30 20:57:20 -07:00
user
f30bfcf36a add budget reservation disable flag 2026-04-30 20:36:14 -07:00
user
64fadc3b8e Merge remote-tracking branch 'origin/litellm_internal_staging' into codex/budget-race-enforcement-greptile-fix
# Conflicts:
#	litellm/proxy/db/spend_counter_reseed.py
#	litellm/proxy/proxy_server.py
2026-04-30 18:25:48 -07:00
user
694fadd175 fix budget reservation review findings 2026-04-30 17:38:18 -07:00
harish-berri
7c8fe86fd9
Merge branch 'litellm_internal_staging' into litellm_token_verification_query_opt 2026-04-30 17:25:12 -07:00
user
9db8ecac12 update budget reservation auth test expectation 2026-04-30 17:13:49 -07:00
yuneng-jiang
bdcc23853c
Merge pull request #26835 from stuxf/codex/cli-sso-flow-binding
chore(cli): tighten CLI SSO session flow
2026-04-30 17:10:27 -07:00
user
46183e6dc9 Merge remote-tracking branch 'origin/litellm_internal_staging' into codex/budget-race-enforcement-snapshot 2026-04-30 13:56:16 -07:00
user
ca50868b75 harden end-user and tag budget reservations 2026-04-30 13:37:10 -07:00
harish-berri
8df24b5413
Merge branch 'litellm_internal_staging' into litellm_token_verification_query_opt 2026-04-30 12:04:08 -07:00
yuneng-jiang
a9db887bdd
Merge pull request #26843 from stuxf/codex/fix-onboarding-invite-token
chore(auth): harden invite-link onboarding token flow
2026-04-30 11:56:26 -07:00
Yuneng Jiang
00145f91a8
[Fix] RBAC: Default-Allow GET for Admin Viewer + Models Tab Alignment
Root cause: admin_viewer_routes was an explicit allowlist, so every newly-added
GET endpoint anywhere in the codebase silently 403'd for admin viewer until
someone remembered to add it. We had whacked /spend/logs/ui, /customer/list,
/guardrails/list, /policies/attachments/list, /invitation/info, and several
others in serial — but the next round still surfaced /in_product_nudges,
/health/latest, /credentials, /v1/mcp/network/client-ip, /claude-code/plugins,
/policy/templates. This pattern keeps repeating because the model is wrong.

Structural fix in `_check_proxy_admin_viewer_access`:
  - Default-allow safe HTTP methods (GET / HEAD / OPTIONS) on any
    non-inference route. Admin Viewer's principle is read parity with
    Proxy Admin; HTTP semantics already mark GET as side-effect-free, so
    using the method as the allow signal is the correct primitive.
  - Unsafe methods (POST/PUT/PATCH/DELETE) still go through the existing
    explicit allowlists + the hard-blocked write set
    (/user/new, /team/new, /key/generate, …).
  - LLM/inference routes still 403 (cost-incurring).

The existing admin_viewer_routes list is retained as a backstop for the
small set of routes implemented as POST but semantically read (e.g.
/spend/calculate). Adding new GET endpoints no longer requires touching
this list.

Models page tab/panel off-by-one (UI bug for Admin Viewer):
  Tremor's TabList filters falsy children but TabPanels does not, so
  conditionally hiding "Add Model" with `{!shouldHideAddModelTab && ...}`
  left a phantom panel slot — clicking "LLM Credentials" showed nothing,
  and clicking "Pass-Through Endpoints" showed the credentials panel.
  Refactor to a single source-of-truth `visibleTabs` array; tab and
  panel indices now can never desync.

Tests:
  - 12 parametrized tests covering the 6 user-reported endpoints + 4
    hypothetical-future endpoints + 2 already-fixed ones, all asserting
    Admin Viewer GET succeeds via the default-allow path (no allowlist
    entry needed).
  - 5 parametrized tests for POST writes still 403'ing
    (random-future-write, /user/new, /team/new, /key/generate, /model/new).
  - All 207 existing route_checks tests still pass — backward-compatible.
2026-04-29 23:19:45 -07:00
Yuneng Jiang
2fa6c60124
[Fix] RBAC: Unblock Guardrails / Policies / MCP-filter reads + Keys / Models page hard-blocks
User reported six more 403s and "still restricts access to keys + models" after
the first round. Root causes:

1. Six read endpoints were missing from admin_viewer_routes:
   - /guardrails/list, /v2/guardrails/list (Guardrails page)
   - /guardrails/submissions, /guardrails/submissions/{guardrail_id}
   - /guardrails/usage/overview (Guardrails Monitor page)
   - /policies/attachments/list (Policies page)
   - /get/mcp_semantic_filter_settings (Settings page)

2. /guardrails/submissions handler treated admin viewer as non-admin, filtering
   them to only their team submissions. Switch to _user_has_admin_view() so
   admin viewer sees all submissions (read parity with Proxy Admin).

3. UI Keys page (user_dashboard.tsx) and Models page (ModelsAndEndpointsView.tsx)
   each had a hard "Access Denied" block specifically for "Admin Viewer" — a
   leftover from the pre-parity era. Remove the blocks; gate the "Create Key"
   button on the Keys page so admin viewer can read keys but not mint them.
   Also drop the post-login redirect that forced admin viewers to /usage on
   sign-in (page.tsx).

Tests:
- Extend ADMIN_VIEWER_SETTINGS_ROUTES parametrize list to cover all 7 new
  routes (route-checks layer is now the layer production traffic actually
  hits, vs. the dependency-override-bypass that was masking the gap).
2026-04-29 22:58:38 -07:00
user
2f4641752b chore(auth): require trusted proxy for header identity auth 2026-04-29 21:20:21 -07:00
user
09503ebb8f harden budget reservation recovery 2026-04-29 21:06:30 -07:00
user
5a619cf879 tighten budget spend admission 2026-04-29 20:30:09 -07:00
ryan-crabbe-berri
b1b98c3fc7
Merge pull request #26821 from BerriAI/litellm_fix-guardrail-auth-check
fix(proxy/auth): tighten guardrail modification permission check
2026-04-29 19:48:34 -07:00
yuneng-jiang
383bf12001
Merge pull request #26809 from BerriAI/litellm_teamMemberNullBudgetFallback
[Fix] Team member null budget fallback
2026-04-29 19:44:41 -07:00
yuneng-jiang
c6c546ba86
Merge pull request #26484 from stuxf/fix/master-key-pass-the-hash
chore(auth): substitute alias for master key on UserAPIKeyAuth
2026-04-29 19:30:09 -07:00
Yuneng Jiang
f81fbdabe6
[Fix] RBAC: Add /invitation/info to admin_viewer_routes
Greptile review caught that the /invitation/info handler relaxation was
dead code: the route_checks layer rejects admin viewers before the handler
runs because /invitation/info was never added to admin_viewer_routes.

Add /invitation/info to admin_viewer_routes and extend the route-level
parametrized test to cover it.

The handler-level integration test passed previously because
`app.dependency_overrides[user_api_key_auth]` bypasses route_checks; this
new route-level test exercises the layer that production traffic hits.
2026-04-29 19:27:25 -07:00
Yuneng Jiang
cfda5e17ac
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/pensive-bartik-e24048
# Conflicts:
#	ui/litellm-dashboard/src/components/leftnav.tsx
2026-04-29 19:25:20 -07:00
Yuneng Jiang
b1ee9c4fc5
fix(rbac): restore admin-viewer read parity for Logs page + settings reads
Admin Viewer (proxy_admin_viewer) was being blocked from endpoints it should
be able to read. Most visibly the UI Logs page rendered empty because every
filter and detail call (/spend/logs/ui, /spend/logs/ui/{id},
/spend/logs/session/ui, /customer/list) was rejected at the route_checks
layer even though the underlying handlers permit admin-viewer.

Backend:
- Extend admin_viewer_routes to include spend_tracking_routes,
  /customer/{list,info}, /spend/logs/* detail routes, callback / config /
  budget / alerting reads, and model cost map status/source.
- Replace bare `user_role != PROXY_ADMIN` checks in read-only handlers
  (/budget/list, /budget/settings, /alerting/settings, /invitation/info,
  /config/field/info, /config/list, /schedule/model_cost_map_reload/status,
  /model/cost_map/source) with `_user_has_admin_view()`.

UI:
- Add `rolesAllowedToViewWriteScopedPages` (rolesWithWriteAccess + Admin
  Viewer) and use it for the "Models + Endpoints" and "Agents" sidebar
  items so admin viewers see them read-only. Playground stays gated by
  rolesWithWriteAccess (cost-incurring).
- Hide Add / Edit / Delete buttons in the LLM Credentials panel for
  non-proxy-admin viewers.

Tests:
- 31 parametrized route_checks cases for the Logs + settings endpoints,
  with internal-user negative coverage to ensure the gate isn't widened.
- 9 handler-level integration tests (FastAPI TestClient) verifying
  admin viewer is no longer blocked at the handler layer.
- New leftnav cases asserting Playground hidden / Models + Agents / Logs
  visible to Admin Viewer.
- New roles + credentials test cases for the UI write-gate.
2026-04-29 19:21:41 -07:00
user
20b937dcbe chore(auth): address onboarding review follow-ups 2026-04-29 19:10:30 -07:00
user
8205e248a6 chore(auth): harden invite-link onboarding token flow 2026-04-29 18:55:11 -07:00
stuxf
dedaf74a5e
chore(auth): tighten clientside api_base handling (#26518)
* chore(auth): validate clientside api_base against SSRF guard; clear admin secrets on base override

Two related issues with how the proxy handles client-supplied
``api_base`` / ``base_url`` overrides on chat-completion requests:

1. **SSRF gate bypass** — ``check_complete_credentials()`` returned
   ``True`` for any non-empty ``api_key``, allowing the
   ``is_request_body_safe`` ``banned_params`` loop to admit ``api_base``
   / ``base_url`` values that point at private (RFC 1918), loopback,
   link-local, or cloud-metadata addresses. Now: when the gate sees a
   client-supplied ``api_base`` / ``base_url``, it runs the URL through
   ``litellm_core_utils.url_utils.validate_url`` (DNS-resolves, blocks
   internal/IMDS/LL networks, defends against rebinding). Rejection
   raises with a clear message.

2. **Admin-config leak on base override** —
   ``get_dynamic_litellm_params`` only carried the three clientside keys
   (``api_key``, ``api_base``, ``base_url``) from request to upstream
   call. Other admin-configured fields on ``litellm_params`` —
   ``organization``, ``extra_body``, ``extra_headers``, ``api_version``,
   ``azure_ad_token``, AWS / Vertex creds, etc. — flowed through
   unchanged. With base redirected to a client-controlled server, those
   admin secrets were sent to the attacker. Now: when ``api_base`` /
   ``base_url`` is in ``request_kwargs``, drop those admin-config
   fields from ``litellm_params`` unless the caller re-supplied them.

Tests cover the SSRF-target rejection per URL field, the admin-secret
clearing on base override, the don't-clear case when only ``api_key``
is overridden (BYOK pattern), and the don't-overwrite case when the
caller resupplies fields like ``organization`` themselves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(vertex-batches): wrap api_base GET in safe_get for defense-in-depth

The vertex batches status-poll fetches an attacker-influenceable
``api_base`` URL with a raw ``sync_handler.get()``. The proxy auth gate
already validates clientside ``api_base`` before reaching this sink, so
the proxy flow is covered. This adds the per-sink wrap so SDK callers
and any future code path that bypasses the proxy gate pick up the same
SSRF defense from ``url_utils.safe_get``.

Operators with a legitimate private Vertex base can either allowlist
the host via ``litellm.user_url_allowed_hosts`` or disable validation
with ``litellm.user_url_validation = False``.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(auth): hoist url_utils import; derive admin-config field list from CredentialLiteLLMParams

/simplify pass:
- Move ``from litellm.litellm_core_utils.url_utils import SSRFError, validate_url``
  to module top in ``proxy/auth/auth_utils.py``. CLAUDE.md prefers
  module-level imports unless avoiding a circular dependency, and
  there's no cycle here (``url_utils`` doesn't depend on ``proxy.auth``).
- Replace the hardcoded ``_ADMIN_CONFIG_FIELDS_TO_CLEAR_ON_BASE_OVERRIDE``
  literal with ``_admin_config_fields_to_clear_on_base_override()`` that
  derives the typed-field portion from
  ``CredentialLiteLLMParams.model_fields``. Adds three fields the
  hardcoded list missed (``aws_bedrock_runtime_endpoint``,
  ``watsonx_region_name``, ``region_name``) and stays in sync as new
  provider fields are declared on the model. The kwargs-only set
  (``organization``, ``extra_body``, ``azure_ad_token``, ``aws_session_token``,
  ``aws_sts_endpoint``, ``aws_web_identity_token``, ``aws_role_name``, …)
  remains explicit since those fields aren't on the typed model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(auth): close field-echo bypass; gate URL check on toggle; cover async batch path

Three issues from review:

1. ``get_dynamic_litellm_params`` used ``if field not in request_kwargs:
   pop`` to clear admin-set provider config when the caller redirected
   ``api_base``. A caller could *echo* any clear-list field name (with any
   value, including an empty string) to skip the pop, leaving the admin's
   value in ``litellm_params`` to be forwarded to the redirected upstream.
   Fix: always pop, then write the caller's value back if they resupplied
   the field.

2. ``check_complete_credentials`` called ``validate_url`` directly. That
   helper doesn't itself consult ``litellm.user_url_validation``; the
   toggle is honoured by ``safe_get`` / ``async_safe_get``. Mirror that
   here so admins who explicitly disabled URL validation aren't blocked
   at the proxy boundary.

3. ``VertexAIBatchesHandler._async_retrieve_batch`` still used a bare
   ``await client.get(api_base, ...)`` while the sync sibling was wrapped
   in ``safe_get``. Wrap the async call in ``async_safe_get`` so SDK
   callers on the async path get the same DNS-rebind / private /
   cloud-metadata defenses as the sync path.

Tests:

- ``TestCheckCompleteCredentialsBlocksSSRF`` is now mock-only; an autouse
  fixture flips the toggle on, ``validate_url`` is patched in the
  parametrized blocking tests, and the positive path no longer makes a
  real DNS call to api.openai.com.
- ``test_skips_url_validation_when_toggle_is_off`` documents the new
  toggle-off behaviour and asserts ``validate_url`` is not called.
- ``test_caller_resupplied_value_overrides_admin_value_on_base_override``
  replaces the prior test that asserted the buggy
  preserve-admin-value-on-echo behaviour.
- ``test_field_echo_does_not_preserve_admin_value`` is a focused
  regression test for the empty-string echo vector.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(auth): close provider-confusion credential exfil; expand banned-params; cover OCI

Three additions on top of the entry-point URL gate so the cluster is
fully closed against caller-supplied ``api_base`` redirection:

1. ``get_llm_provider_logic.py`` matched registered openai-compatible
   endpoints against ``api_base`` with an unanchored substring search
   (``if endpoint in api_base:``). A caller could pass an api_base like
   ``https://attacker.com/api.groq.com/openai/v1`` to coerce the proxy
   into reading ``GROQ_API_KEY`` from the environment and forwarding it
   as a Bearer credential to the attacker's host. Replaced with parsed-
   URL semantics (hostname exact-match plus segment-bounded path-prefix)
   in a new ``_endpoint_matches_api_base`` helper.

2. ``is_request_body_safe`` rejects ``api_base`` / ``base_url`` /
   ``user_config`` / a handful of AWS / vertex fields, but the list
   omitted three other endpoint-targeting fields:
   * ``aws_bedrock_runtime_endpoint`` — Bedrock endpoint redirect
   * ``langsmith_base_url`` / ``langfuse_host`` — observability callback
     hostnames; attacker-controlled values exfiltrate the entire request
     payload (incl. message content) via the logging hook.
   Added all three to the blocklist.

3. ``_admin_config_fields_to_clear_on_base_override`` derives its typed-
   field list from ``CredentialLiteLLMParams.model_fields``, which does
   not declare any of the OCI provider's auth fields. Added
   ``oci_signer``, ``oci_user``, ``oci_fingerprint``, ``oci_tenancy``,
   ``oci_key``, and ``oci_key_file`` to the kwargs-only fixed list so
   they are cleared on caller-redirected ``api_base`` like the AWS /
   Azure / Vertex equivalents.

Tests:

- ``TestEndpointMatchesApiBase`` — direct unit tests on the new
  matcher: legitimate provider URLs (5 shapes) match; attacker
  smuggling via path injection, suffix label, prefix label, userinfo
  ``@`` injection, and path-segment lookalikes (7 shapes) do not.
- ``TestGetLlmProviderRejectsAttackerSmuggledApiBase`` — end-to-end
  invariant that ``GROQ_API_KEY`` is never read against an attacker-
  controlled host while the legitimate ``api.groq.com`` path still
  resolves the provider correctly.
- ``TestIsRequestBodySafeBlocksEndpointTargetingFields`` — parametrized
  coverage that each of the three new banned-params raises a clear
  rejection naming the offending field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(auth): remove implicit api-key bypass + add posthog/braintrust/slack to blocklist

The historical ``check_complete_credentials`` clause inside
``is_request_body_safe`` was a third, *implicit*, *caller-controlled*
BYOK path: any caller that supplied a non-empty ``api_key`` caused the
entire banned-params blocklist to be skipped. That turned every missing
entry on the blocklist into an exploitable SSRF / credential-exfil hole
and is the root cause of the chain of api_base advisories that have
been re-discovered with each new integration:

* GHSA-jh89-88fc-qrfp (critical, triage) — env-var exfil via api_base
* GHSA-3frq-6r6h-7j64 (high, triage) — admin org / extra_body leak
* veria-admin Dv_m860l, b_yRJeQ5, stN90yjP, LBlyOAc8, U2TD78kg —
  variations on "list X is missing field Y"

Two explicit, admin-controlled BYOK paths already exist and remain:
``general_settings.allow_client_side_credentials = true`` (proxy-wide)
and ``configurable_clientside_auth_params: [...]`` per deployment.
Removing the implicit bypass converts the failure mode of a missing
blocklist entry from "live credential leak" to "predictable 400 with
a clear remediation message," which is the structural fix.

Also adds the three remaining endpoint-targeting fields the dynamic
callback layer reads from request body: ``posthog_host``,
``braintrust_host``, ``slack_webhook_url``. ``slack_webhook_url`` in
particular was a direct exfil channel (caller-set webhook → proxy
mirrors every request to attacker's Slack).

Tests:

- ``test_api_key_does_not_bypass_blocklist`` — parametrized regression
  asserting api_key=anything no longer skips the gate for any of the
  five highest-risk fields.
- ``test_admin_opt_in_proxy_wide_still_allows`` — confirms the
  documented BYOK opt-in still works.
- Extends ``test_endpoint_targeting_field_in_request_body_is_rejected``
  to cover posthog / braintrust / slack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(auth): block sagemaker_base_url, s3_endpoint_url, deployment_url

Provider-specific endpoint overrides surfaced by a wider audit of
``optional_params`` consumers in ``litellm/llms/``. Same threat as
``api_base``: a caller-supplied value redirects the outbound request
to an attacker host.

* ``s3_endpoint_url`` — read in ``litellm/llms/bedrock/files/transformation.py``
  to build the S3 upload URL for Bedrock files. Caller redirects file
  uploads to attacker-controlled S3.
* ``sagemaker_base_url`` — read in ``litellm/llms/sagemaker/{chat,completion}/*``.
  Caller redirects SageMaker traffic. This is the primary vector
  described in veria-admin mNqEBBtG.
* ``deployment_url`` — popped in ``litellm/llms/sap/chat/transformation.py``.
  Caller redirects SAP deployment requests.

Tests parametrize ``test_endpoint_targeting_field_in_request_body_is_rejected``
to cover the three new fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:27:22 -07:00
user
19ca420056 cover cli sso start validation 2026-04-29 17:25:32 -07:00
user
2612187c57 fix cli auth test expectations 2026-04-29 17:24:17 -07:00
user
722bc63e37
chore(oauth2-proxy): drop unused patch import + tighten docstring
Greptile flagged the unused ``from unittest.mock import patch``
left over from before the ``configure_proxy`` fixture refactor (the
fixture uses ``monkeypatch``, no ``patch`` calls remain). Also pruned
the now-stale "premium gate" paragraph from the module docstring
since that gate was removed in fbcfd59b1a.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:47:29 +00:00
user
fbcfd59b1a
fix(oauth2-proxy): drop premium gate; identity-only allowlist is the security fix
Greptile flagged the ``premium_user is not True`` check as a hard
backwards-incompatible break for OSS users currently running
``enable_oauth2_proxy_auth=True``. They were right: unlike the
api_base case (where the docs already required admin opt-in), this
path was documented as available to OSS users. Adding the gate would
have closed a documented feature, not fixed a vuln.

Reframed the change:

* The **identity-only allowlist** (``ALLOWED_OAUTH2_PROXY_FIELDS`` =
  ``{user_id, user_email, team_id, team_alias, org_id, models}``) is
  the actual security fix — it closes the privesc by rejecting any
  mapping to a non-identity field at request time. This is unchanged.
* The **premium gate** was parity-with-siblings (a product decision,
  not a security one). Removed. BerriAI can re-add it on their own
  schedule with a proper deprecation cycle if they want enterprise-
  only gating.

Tests: removed ``test_rejects_when_not_premium``; everything else
(allowlist enforcement, identity passthrough, attack-shape
regression) still passes — 14 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:04:05 +00:00
user
b35287a062
fix(oauth2-proxy): switch privileged-field denylist to identity-only allowlist
Greptile flagged that the denylist was incomplete: ``user_max_budget``,
``user_tpm_limit``, ``user_rpm_limit``, and ``user_spend`` were not on
it. Inspection of the auth model showed dozens more privileged fields
across the ``LiteLLM_VerificationTokenView`` hierarchy (team / org /
end-user / region budget / spend / limit fields, plus
``allowed_model_region``, ``rpm_limit_per_model``, etc.) — a denylist
of "privileged fields" is unmaintainable here.

Inverted the model. ``ALLOWED_OAUTH2_PROXY_FIELDS`` is now an
identity-only allowlist: ``user_id``, ``user_email``, ``team_id``,
``team_alias``, ``org_id``, ``models``. Any mapping to a non-identity
field is rejected at request time. Default-secure: a future field
added to ``UserAPIKeyAuth`` is automatically blocked from
header-trust.

Use case for OAuth2-proxy auth is identity assertion from a trusted
upstream. Anything beyond that (privileges, budgets, rate limits) is
policy and should be authenticated with a signature, not a header —
operators who need this should switch to JWT auth.

Tests:

- ``test_refuses_to_map_non_identity_fields`` parametrized over 22
  fields including all four ``user_*`` Greptile flagged, plus
  team/org/end-user budget/limit fields, plus a fabricated field name
  to confirm "anything not on the allowlist" is the rule.
- ``test_allowlist_is_identity_only`` locks in the allowlist's intent
  so future additions of budget / role / permission entries are caught
  in review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:28:57 +00:00