Commit graph

10183 commits

Author SHA1 Message Date
user
148485c2a2
fix(passthrough): default auth=True; drop enterprise gate on the safe option
Pass-through endpoints configured in
``general_settings.pass_through_endpoints`` defaulted to ``auth: false``
and the safe ``auth: true`` setting was rejected at startup unless the
operator had a LiteLLM Enterprise license. Net result: OSS deployments
had **no safe configuration** — every pass-through admins added without
remembering ``auth: true`` shipped an unauthenticated forwarder, and
remembering ``auth: true`` raised a hard "enterprise-only" error.

Three changes:

* ``litellm/proxy/_types.py`` — flip
  ``PassThroughGenericEndpoint.auth`` default to ``True``. Operators
  who add a pass-through with no explicit ``auth`` value now get a
  safe, authenticated forwarder by default. Setting ``auth: false``
  remains supported for genuine public-forwarder use cases (e.g.
  webhook receivers).

* ``litellm/proxy/pass_through_endpoints/pass_through_endpoints.py``
  — drop the ``premium_user`` gate around ``auth: true``. An
  unauthenticated forwarder is a deployment choice operators should
  be allowed to make explicitly, but the safe option must always be
  free. The product-tier decision (which features sit behind the
  enterprise license) is separate from "OSS users must always have a
  safe option."

* ``litellm/proxy/auth/user_api_key_auth.py`` — the runtime dispatch
  pulls pass-through endpoints from ``general_settings`` as raw
  dicts, so the Pydantic default doesn't apply. Switched
  ``endpoint.get("auth")`` to ``endpoint.get("auth", True)`` so a
  config dict without an explicit ``auth`` key still requires
  authentication at request time.

Tests:

- ``test_passthrough_auth_defaults_to_true`` — Pydantic default is
  now safe.
- ``test_passthrough_auth_can_still_be_explicitly_disabled``
  — opt-in to ``auth=False`` still works for legitimate
  public-forwarder use cases.
- ``test_register_passthrough_with_auth_true_works_for_oss``
  — ``premium_user=False`` no longer rejects ``auth=true``.
- ``test_runtime_check_treats_missing_auth_key_as_authenticated``
  — raw dict without an ``auth`` key now requires auth (the
  previously-unauthenticated forwarder).
- ``test_runtime_check_explicit_auth_false_still_skips_validation``
  — explicit opt-in still works.

Closes GHSA-7h34-mmrh-6g58.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:10:59 +00:00
yuneng-jiang
fc0cc9c581
Merge pull request #26225 from BerriAI/litellm_dbReconnectNonBlocking
[Fix] Proxy: reconnect Prisma DB without blocking the event loop
2026-04-29 16:09:22 -07: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
a9bc5549b2
fix(team): also gate organization-scoped endpoints behind _verify_org_access
Variant analysis on the team-callback IDOR (GHSA-xxv2-fprq-9x93)
surfaced the same shape on organization-scoped endpoints. Each takes
``organization_id`` from the request body, looks up the org, and
performs reads or writes — without checking whether the caller can
manage that org.

Affected endpoints:

* ``PATCH /organization/update`` — any authenticated key holder could
  rewrite any org's metadata, budgets, and object permissions.
* ``POST /organization/member_add`` — docstring promises "Only
  proxy_admin or org_admin allowed" but the code never enforced it;
  any caller could add members to any org.
* ``POST /organization/member_update`` — only the
  ``modify-PROXY_ADMIN-target-only`` defense was in place; non-admin
  members in any org could be re-roled by any caller.
* ``POST /organization/member_delete`` — no access check at all; any
  caller could remove any user from any org.

Each handler now runs the existing ``_verify_org_access`` helper
(proxy-admin / org-admin hierarchy already used by ``GET
/organization/info`` and ``POST /organization/info``) before the read
or write.

Tests:

- ``test_organization_member_add_rejects_unauthorized_caller`` —
  internal user not on the org gets 403; DB write never happens.
- ``test_organization_member_update_rejects_unauthorized_caller`` —
  same.
- ``test_organization_member_delete_rejects_unauthorized_caller`` —
  same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:51:59 +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
user
e6867c143a
chore(oauth2-proxy): /simplify pass — drop dead max_budget branch + DRY tests
Two cleanups from the /simplify review pass:

* The header-mapping loop had a special-case ``if key == "max_budget":
  auth_data[key] = float(value)`` branch. Since ``max_budget`` is now
  in ``PRIVILEGED_OAUTH2_PROXY_FIELDS``, the denylist check rejects
  the configuration before the loop runs — the float-conversion
  branch is unreachable. Removed.

* Four tests independently called
  ``monkeypatch.setattr(proxy_server, "premium_user", ...)`` and
  ``monkeypatch.setattr(proxy_server, "general_settings", ...)`` with
  almost-identical bodies. Replaced with a ``configure_proxy`` fixture
  that yields a single callable —
  ``configure_proxy(premium=False)`` /
  ``configure_proxy(mappings={...})`` — so each test's setup is one
  line. The previously-unused ``premium_proxy_settings`` fixture is
  removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:23:29 +00:00
user
3c9a8690d1
fix(auth): gate oauth2-proxy header trust on premium + privileged-field denylist
``handle_oauth2_proxy_request`` reads HTTP request headers per the
admin-set ``oauth2_config_mappings`` and constructs a
``UserAPIKeyAuth`` from the values. Two failure modes:

1. **Premium parity.** Sibling auth paths
   (``enable_oauth2_auth``, ``enable_jwt_auth``) require
   ``premium_user``; this path did not, so any open-source deployment
   could turn the feature on without realising it requires a hardened
   reverse-proxy topology. Added the ``premium_user`` gate.

2. **Privileged-field denylist.** Without a denylist, an admin who
   maps the wrong header to ``user_role`` (or whose reverse proxy
   leaks the header from upstream user input) lets any caller send
   ``X-User-Role: proxy_admin`` and gain full admin access — Pydantic
   coerces the string into the ``LitellmUserRoles.PROXY_ADMIN`` enum.
   Mapping any field in ``PRIVILEGED_OAUTH2_PROXY_FIELDS``
   (``user_role``, ``api_key``, ``token``, ``permissions``,
   ``allowed_routes``, budget/limit fields, ``metadata``) raises at
   request time so the misconfiguration surfaces loudly rather than
   as a silent privesc.

Operators who genuinely need a trusted upstream to assert one of
these privileged fields should switch to JWT auth (signature-validated)
rather than header-trust.

Tests:

- ``test_returns_auth_for_simple_user_id_mapping``: legitimate
  identity-only mapping still works.
- ``test_rejects_when_not_premium``: open-source deployments get a
  clear enterprise-feature error.
- ``test_refuses_to_map_privileged_fields``: parametrized over every
  entry in the denylist — each is rejected at request time.
- ``test_user_role_header_forgery_attack_is_blocked``: end-to-end
  shape of the GHSA-5c3m-qffq-4r9m attack; rejected before auth
  object construction.
- ``test_safe_fields_still_pass_through``: documented usage
  (``user_id``, ``user_email``, ``team_id``, ``models``) is
  unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:18:18 +00:00
user
c112bdf2c1
chore(static-assets): /simplify pass — top-level Response import + cleaner test fixture
Two cleanups from the /simplify review pass:

* ``Response`` was imported inside the ``except OSError`` branch in
  ``/get_image`` and at the top of ``/get_favicon``. Per the project's
  no-inline-imports rule (CLAUDE.md), hoisted to the existing
  ``from fastapi.responses import (...)`` block at the top of
  ``proxy_server.py``.

* The test class's ``_patches()`` helper returned a 2-element list of
  patch context managers and tests indexed into them via
  ``self._patches(...)[0], self._patches()[1]`` — two distinct calls
  with confusing aliasing semantics. Restructured to:
    - module-level ``_patch_async_safe_get(...)`` that returns a single
      patch context manager
    - autouse fixture that patches ``get_async_httpx_client`` for every
      test in the file (it's the same patch in every case)
    - small ``_image_response(...)`` factory to deduplicate Mock setup

  Tests now read as ``with _patch_async_safe_get(return_value=...):``
  with no list-indexing or duplicate Mock construction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:01:57 +00:00
user
75d1a0116e
fix(static-assets): use async_safe_get; drop SVG; serve bytes inline on cache miss
Three review items addressed:

* **Veria (Medium): SSRF via redirect.** ``fetch_validated_image_bytes``
  was calling ``validate_url(url)`` once and then fetching with the
  default httpx client, so a 3xx to an internal IP would have been
  followed unvalidated. Switched to ``async_safe_get`` (the existing
  SSRF primitive used elsewhere in the codebase) which walks each
  redirect hop, re-validates, and rejects redirects to blocked
  networks. Default ``litellm.user_url_validation`` is True so
  protection is on out of the box.

* **Greptile (P2): SVG can embed JS.** Removed ``image/svg+xml`` from
  the allowed-Content-Type set. The hardcoded response media type
  (``image/jpeg`` / ``image/x-icon``) means a real SVG body wouldn't
  render as SVG anyway in modern browsers — the allowlist entry was
  giving up XSS surface for no actual SVG-rendering benefit. If real
  SVG support is wanted later, that's a deliberate feature PR with CSP
  / nosniff bundled.

* **Greptile (P2): cache-write OSError drops validated bytes.** When
  the upstream fetch succeeded but ``open(cache_path, "wb")`` raised
  (read-only assets dir), the bytes were discarded and the default
  logo was served — a silent regression for that deployment. Now
  serve the validated bytes inline via ``Response(...)`` as a fallback
  before falling back to default.

Tests:

- Replaced low-level mocks of ``validate_url`` with mocks of
  ``async_safe_get`` directly, exercising the helper's contract
  rather than the SSRF primitive's internals.
- New ``test_rejects_svg_content_type`` confirms SVG is blocked.
- ``test_get_image_cache_logic`` fixture now sets
  ``mock_response.is_redirect = False`` so ``async_safe_get`` doesn't
  treat the Mock's truthy attribute as a redirect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:57:22 +00:00
Ryan Crabbe
2ccb4b94e5
fix(proxy/auth): gate guardrail modification check on key presence
Use key-in-dict membership instead of truthy value lookup so explicitly
supplied empty/falsy payloads still trigger the permission check.
Adds parametrized regression coverage across all gated keys.
2026-04-29 14:56:47 -07:00
user
578846e57d
fix(team): don't log legitimate 403s at error level on /callback endpoints
Greptile flagged that ``disable_team_logging`` and ``get_team_callbacks``
re-wrap any ``HTTPException`` (including the 403 from the access guard)
through a catch-all that logs at ``.error()`` before re-raising — so
every legitimate access-denied response would pollute alerting
dashboards as a "server error".

Add explicit ``except HTTPException: raise`` and
``except ProxyException: raise`` branches before the catch-all (matching
the pattern already used in ``add_team_callbacks``). 4xx now propagates
quietly; only genuinely unexpected exceptions still hit the
error-level log.

Tests assert ``HTTPException`` is now the surfaced shape (instead of
the previous ``ProxyException`` re-wrap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:56:04 +00:00
user
55d393d77d
fix(static-assets): unblock CI — pass headers explicitly + harden + update legacy tests
Three CI failures from the previous push, all addressed:

* ``lint`` (mypy): ``async_client.get(url, **request_kwargs)`` confused
  mypy because ``AsyncHTTPHandler.get``'s second positional arg is typed
  ``bool | None``. Switched to an explicit branch:
  ``await async_client.get(rewritten_url, headers={"host": host_header})``
  for the HTTP-rewritten case, plain ``get(rewritten_url)`` otherwise.

* ``proxy-infra`` /
  ``test_get_image_custom_local_logo_bypasses_cache``: the existing
  test set ``UI_LOGO_PATH=/app/custom_logo.jpg`` with no
  ``LITELLM_ASSETS_PATH``, asserting the path was served verbatim. That
  was the LFI behaviour the new path-containment guard closes. Updated
  the test to set ``LITELLM_ASSETS_PATH=/app`` so the path is inside an
  allowed root, and patched the helper's ``realpath`` / ``isfile`` to
  go along with the mocked filesystem. Test intent (bypass cache when
  ``UI_LOGO_PATH`` is local) is preserved.

* ``auth-and-jwt`` / ``test_get_image_cache_logic``: existing test
  built a ``Mock`` response without ``headers``, so the new
  Content-Type check tripped on ``Mock().split(";")[0]``. Two fixes:

    1. Set ``mock_response.headers = {"content-type": "image/jpeg"}``
       on the test (matches the real upstream contract — a logo CDN
       always sets a Content-Type).
    2. Make ``fetch_validated_image_bytes`` defensive: if the
       Content-Type header is missing or non-string, treat as non-image
       and fall back to default. Closes a subtle hole — pre-fix, an
       upstream that omits Content-Type entirely would have served
       arbitrary bytes under the ``image/jpeg`` wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:47:41 +00:00
user
140628063c
fix(team): gate /team/{id}/callback endpoints behind _verify_team_access
Three endpoints in ``team_callback_endpoints.py`` accept a ``team_id``
from the URL but never check whether the authenticated caller can
manage that team:

* ``POST /team/{team_id}/callback`` — write Langfuse / Langsmith / GCS
  credentials to any team
* ``POST /team/{team_id}/disable_logging`` — silence audit logging
  for any team
* ``GET /team/{team_id}/callback`` — read back another team's stored
  third-party API credentials

Each handler now runs the existing ``_verify_team_access`` helper
(proxy-admin / org-admin / team-admin hierarchy already used by sibling
endpoints in ``team_endpoints.py``) on the resolved team row before
the read or write.

Tests:

- ``test_add_team_callbacks_rejects_unauthorized_caller`` — internal
  user not on the team gets 403; DB write never happens.
- ``test_disable_team_logging_rejects_unauthorized_caller`` — same.
- ``test_get_team_callbacks_rejects_unauthorized_caller`` — same on the
  read path; victim team's callback data stays inaccessible.
- ``test_proxy_admin_can_add_team_callbacks`` — proxy admin still
  passes through to the DB write (sanity that the guard didn't
  over-rotate).
- ``test_team_admin_of_target_team_can_add_callbacks`` — team admin of
  the target team still passes through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:39:39 +00:00
Cursor Agent
1b6ab9facf
Merge branch 'litellm_internal_staging' into litellm_oss_staging
Resolves merge conflict in tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py
by keeping both the new bedrock tool-result file/document tests and the
transform_response body-leak regression test.

Also addresses Greptile P2 comment: when BedrockImageProcessor returns a
block with neither 'image' nor 'document' keys on the tool-result path
(image_url and file content types), log a warning instead of silently
dropping the block.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 21:34:52 +00:00
Mateo Wang
9bc317b4d0
Merge pull request #26584 from BerriAI/litellm_mcp-oauth-azure-entra-discovery2
[Feat]Add support for azure entra discovery endpoint
2026-04-29 14:28:41 -07:00
Michael-RZ-Berri
08d35f6b42
Merge pull request #26662 from BerriAI/litellm_spendLogsErrorRedaction
[Fix] Redact spend logs error message
2026-04-29 14:26:48 -07:00
user
9ef8572d67
fix(proxy): /get_logo_url no longer discloses local UI_LOGO_PATH
The unauthenticated ``/get_logo_url`` endpoint returned the
``UI_LOGO_PATH`` env var verbatim. For HTTP(S) URLs this is intended —
the dashboard loads the logo directly from a public/internal CDN. For
local filesystem paths it was an information disclosure: any caller
could fetch ``/get_logo_url`` and read admin-only filesystem details
like ``UI_LOGO_PATH=/etc/litellm/secret-config.json``.

Now the endpoint returns the URL only when it begins with
``http://`` or ``https://``. For local paths (or unset) it returns an
empty string — the dashboard falls back to ``/get_image`` which
serves the file via the path-containment guard added in the previous
commit.

Tests parametrize the disclosure-blocked cases (``/etc/...``,
``/proc/self/environ``, relative paths) and confirm HTTP / HTTPS URLs
still pass through unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:15:21 +00:00
user
0166992f6b
fix(proxy): contain UI_LOGO_PATH and LITELLM_FAVICON_URL to allowed asset roots
The unauthenticated ``/get_image`` and ``/get_favicon`` endpoints accept
the admin-set env vars ``UI_LOGO_PATH`` and ``LITELLM_FAVICON_URL`` and
return whatever bytes they resolve to, with a hard-coded ``image/jpeg``
or ``image/x-icon`` content-type. Two attack shapes:

* ``UI_LOGO_PATH=/etc/passwd`` (or any other readable file path) — any
  unauthenticated caller exfiltrates the file via ``GET /get_image``.
  The previous gate was ``os.path.exists(logo_path)`` which fires on
  every readable file. Same shape for the favicon endpoint.
* ``UI_LOGO_PATH=http://169.254.169.254/iam`` (or any internal HTTP
  service the admin pointed at) — the proxy fetches it server-side
  and streams the response body to the unauthenticated caller. No
  URL validation, no Content-Type validation; ``application/json``
  AWS metadata gets tunneled out under the ``image/jpeg`` wrapper.

New helper module ``litellm/proxy/common_utils/static_asset_utils.py``:

* ``resolve_local_asset_path(candidate, allowed_roots)`` — returns the
  resolved absolute path only if it lives within one of the allowed
  asset roots. Uses ``realpath`` so symlinks pointing outside the roots
  are caught.
* ``fetch_validated_image_bytes(url)`` — runs the URL through
  ``validate_url`` (rejecting private / cloud-metadata / loopback
  targets) and only returns the response body if the upstream
  Content-Type is in a small allowlist of image MIME types.

Both ``/get_image`` and ``/get_favicon`` are wired through the helpers.
The SSRF gate is enforced unconditionally — these endpoints are
unauthenticated, so the admin-facing ``litellm.user_url_validation``
toggle does not apply (an admin who opted out of URL validation for
LLM provider paths shouldn't also expose ``/get_image`` to SSRF).

Tests:

- ``TestResolveLocalAssetPath``: 10 cases covering legitimate paths,
  ``/etc/passwd``, ``/proc/self/environ``, symlink-out, ``..``
  traversal, directories, missing files, and root list edge cases.
- ``TestFetchValidatedImageBytes``: 7 cases covering SSRF block, non-
  image content-type rejection, valid image passthrough, non-200
  response, fetch exception, empty URL, and parametrized coverage of
  every allowed image MIME type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:09:37 +00:00
Yuneng Jiang
4f6192a49e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_dbReconnectNonBlocking_local
# Conflicts:
#	tests/test_litellm/proxy/db/test_prisma_self_heal.py
2026-04-29 13:57:35 -07:00
yuneng-jiang
602a6cff81
Merge pull request #26756 from BerriAI/litellm_prisma_reconnect_hardening
fix(proxy): self-heal Prisma read paths + harden reconnect state machine
2026-04-29 13:49:40 -07:00
sruthi-sixt-26
55c3129e5f fix(proxy/batches): forward model to retrieve_batch for bedrock
- Proxy decoded `model` from the encoded batch_id but never passed it
  to `litellm.aretrieve_batch`.
- Without `model` in kwargs, litellm cannot load `BedrockBatchesConfig`
  and falls into the legacy provider switch, which 400s for bedrock.
- Fix: set `data["model"] = model_from_id` before the litellm call in
  the SCENARIO 1 (encoded batch_id) branch.
- Also corrects the error string in
  `_handle_retrieve_batch_providers_without_provider_config` (said
  `'create_batch'` despite being raised from the retrieve path).
- Adds tests covering retrieve + file_content round-trip for bedrock-
  encoded IDs.
2026-04-29 22:48:03 +02:00
Mateo Wang
97a3bd5ff4
Merge pull request #26733 from BerriAI/litellm_mcp-short-prefix-id-0e42
feat(mcp): opt-in short-ID tool prefix to keep MCP tool names under the 60-char limit
2026-04-29 13:48:01 -07:00
harish-berri
25cf097da8 add test(tag-routing): prevent header regex bypass for strict plain tags.
Add tests to validate the condition

 improve the conditional readability by naming the plain-tag check explicitly.
2026-04-29 20:14:30 +00:00
Mateo Wang
295a36aa69
Merge pull request #26685 from BerriAI/litellm_bedrock_retrievalconfig_passthrough2
feat(vector-stores): support Bedrock retrievalConfiguration passthrough
2026-04-29 12:36:14 -07:00
Sameer Kankute
4cecfec9f9
feat(proxy): LiteLLM headers on Google native generateContent routes (#25500)
* feat(proxy): return LiteLLM headers on Google native generateContent routes

Wire build_litellm_proxy_success_headers_from_llm_response for :generateContent
and :streamGenerateContent so x-litellm-*, rate limit, and provider headers
match the OpenAI-style proxy path. Add unit test.

Annotate httpx.HTTPStatusError branch so pyright accepts .response after optional
exception transform. Remove unused variable in streaming tracer test (Ruff F841).

Made-with: Cursor

* fix(proxy): prefill Google GenAI stream _hidden_params for proxy headers

- Pass model_id, api_base, and process_response_headers output into streaming
  iterators so streamGenerateContent gets the same x-litellm-* headers as
  non-streaming paths.
- Drop request_data deployment mutation from build_litellm_proxy_success_headers_from_llm_response.
- Avoid logging raw request key names in oversized debug payload (code scanning).
- Extend tests for streaming iterator shape, metadata fallback, and helper.

Made-with: Cursor

* Update litellm/proxy/common_request_processing.py

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* remove unused key count

* Fix greptile review

* Update litellm/proxy/common_request_processing.py

Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
2026-04-29 12:34:14 -07:00
Cursor Agent
3f5c589255
fix(bedrock): add 1-hour cache write tier for Claude 4.5/4.6/4.7 (Global, US)
AWS Bedrock pricing publishes a separate 1-hour prompt-cache write rate for
Claude 4.5 / 4.6 / 4.7 (1.6x the 5-minute rate). Without
`cache_creation_input_token_cost_above_1hr`, cost tracking for 1-hour-TTL
prompt caching on Bedrock falls back to the 5-minute rate and undercounts
spend by ~60%.

Adds the field to the spot-checked Global and US-region entries:

- anthropic.claude-opus-4-7         (Global $10.00 / MTok)
- anthropic.claude-opus-4-6-v1      (Global $10.00 / MTok)
- anthropic.claude-opus-4-5-...     (Global $10.00 / MTok)
- anthropic.claude-sonnet-4-6       (Global $6.00 / MTok)
- anthropic.claude-sonnet-4-5-...   (Global $6.00 / MTok regular,
                                     $12.00 / MTok long-context >200K)
- anthropic.claude-haiku-4-5-...    (Global $2.00 / MTok)
- global.anthropic.* mirrors of the above
- us.anthropic.* mirrors at the US +10% premium

Also updates the long-context (>200K) variants of Sonnet 4.5 with
`cache_creation_input_token_cost_above_1hr_above_200k_tokens`.

The mirrored entries in `litellm/model_prices_and_context_window_backup.json`
are updated in lockstep.

EU / AU / APAC / JP / us-gov regional variants are out of scope for this
change pending separate verification against AWS Bedrock pricing for those
regions.

Adds tests/test_litellm/test_bedrock_anthropic_1hr_cache_pricing.py to lock
in the expected values and the 1.6x ratio invariant.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 19:21:57 +00:00
user
4d92bc8b86
fix(vector-stores): re-raise HTTPException from get_vector_store_info; allowlist recursion
Two issues from the previous push's review:

1. **Greptile P1**: ``get_vector_store_info`` had the same catch-all
   ``except Exception`` pattern as ``update_vector_store``, so the
   HTTPException(403/404) raised by both the in-memory access check and
   the new ``_fetch_and_authorize_vector_store`` helper was rewritten as
   500. Mirror the ``except HTTPException: raise`` guard from
   ``update_vector_store``.

2. **code-quality CI** (``tests/code_coverage_tests/recursive_detector.py``)
   flagged ``_redact_sensitive_litellm_params`` as an unallowlisted
   recursive function. Match the convention of other allowlisted
   helpers ("max depth set"): bound recursion at depth 10 (well above
   any plausible nesting level for real ``litellm_params`` payloads),
   return the redaction sentinel on overflow, and add the function
   name to ``IGNORE_FUNCTIONS``.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:56:55 +00:00
user
294ac8383e
fix(vector-stores): recurse into nested litellm_params; handle JSON-string shape
Three issues surfaced in review of the previous commit:

1. **Veria — Medium**: ``litellm_params`` carries a nested
   ``litellm_embedding_config`` dict (auto-resolved from the model
   registry on create / update) which itself holds ``api_key`` /
   ``aws_*`` / ``vertex_credentials``. The previous redactor only
   inspected top-level keys, so the nested values passed through
   unredacted. Recurse into nested dicts.

2. **Greptile — P2**: when ``litellm_params`` is a JSON-serialized
   string (the in-memory registry occasionally stores it that way), the
   previous redactor silently no-op'd via the ``isinstance(..., dict)``
   guard and echoed the raw payload back. Now: parse, redact, re-serialize.
   If the string is not valid JSON, replace it with the redaction
   sentinel rather than echo it.

3. **mypy** flagged ``_redact_sensitive_litellm_params``'s
   ``Optional[Dict[str, Any]]`` signature as incompatible with the
   ``object``-typed call site. Widened to ``Any -> Any`` to reflect the
   actual contract (the function now handles dict / str / None / other).

Also fixes a related test regression in
``test_remove_sensitive_info_from_deployment_with_excluded_keys``: the
``"credentials"`` plural addition to ``SensitiveDataMasker`` defaults
caused the first call (without ``excluded_keys``) to mutate the input
dict's ``litellm_credentials_name`` to a masked value. The second call
(with ``excluded_keys``) then saw the already-masked value rather than
the original. Construct fresh input for each call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:56:40 +00:00
user
51d560ba2e
chore(vector-stores): also gate /vector_store/update; upstream credentials plural in masker
Two architectural extensions to the credential-redaction in the previous
commit:

1. ``/vector_store/update`` had two gaps:
   - No per-store access control. Any authenticated principal that
     passed the premium-feature gate could mutate *any* vector store,
     including stores belonging to other teams.
   - The response returned the full DB row including ``litellm_params``,
     so the caller could read another team's persisted provider
     credentials by submitting a no-op metadata change.
   Mirror the access-control check ``/vector_store/info`` already
   performs (``_check_vector_store_access`` against the existing row),
   redact ``litellm_params`` in the response, and add an
   ``except HTTPException: raise`` guard so the 403/404 responses don't
   get rewritten as 500 by the catch-all.

2. ``SensitiveDataMasker``'s default ``sensitive_patterns`` set used
   segment-exact matching, so ``credential`` matched ``vertex_credential``
   but not ``vertex_credentials`` (the actual Vertex field name). The
   previous commit worked around this with a per-call extension; this
   commit puts the plural in the upstream defaults so every caller
   (Redis config dump, MCP debug headers, cache routes, ...) gets the
   correct behavior. The local override in
   ``vector_store_endpoints/management_endpoints.py`` is removed.

Also updates ``test_excluded_keys_exact_match`` which relied on
``credentials`` *not* being a sensitive pattern to demonstrate
case-sensitive ``excluded_keys`` matching. The intent of the test
(case-sensitive match) is preserved; the assertion now reflects that
when ``excluded_keys`` fails to apply (wrong case), the field falls
through to standard pattern-based masking instead of being passed
through unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:56:09 +00:00
user
a99943ec49
test+style: drop _redact_vector_store wrapper; inherit masker defaults
/simplify pass:
- Remove the single-call-site ``_redact_vector_store`` wrapper. Inline
  the two-line redaction at its only caller in ``list_vector_stores``;
  ``get_vector_store_info`` was already calling the inner helper directly.
- Inherit ``SensitiveDataMasker``'s default sensitive-key set instead of
  duplicating the 12-element list, then add only the plural
  ``credentials`` extension. Won't drift if upstream defaults change.
- Trim the over-explained docstring on ``_redact_sensitive_litellm_params``
  to a one-paragraph summary; the WHY (credential-leakage class) belongs
  in the commit message, not in every consumer's IDE tooltip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:55:40 +00:00
user
0806cca340
chore(vector-stores): redact credentials from list/info responses
``LiteLLM_ManagedVectorStore.litellm_params`` carries the upstream provider
credential — OpenAI ``api_key``, AWS ``aws_access_key_id`` /
``aws_secret_access_key``, GCP ``vertex_credentials``, etc. ``GET
/vector_store/list`` and ``POST /vector_store/info`` return these
verbatim to any authenticated principal. Because both routes are in
``openai_routes``, ``RouteChecks.is_llm_api_route`` short-circuits the
standard role gate, so even read-only users and narrowly-scoped keys can
read every stored credential.

Replace credential-bearing values with the ``REDACTED_BY_LITELM``
sentinel in both responses while preserving non-secret keys
(``api_base``, ``region``, ``model``, ``api_version``) so callers can
still see *which* upstream is configured. Detection reuses
``SensitiveDataMasker.is_sensitive_key`` with the default heuristics
plus the plural ``credentials`` pattern (covers Vertex's
``vertex_credentials`` field, which the singular ``credential`` pattern
misses on segment-exact matching).

Applied at:
- ``list_vector_stores`` (``GET /vector_store/list``,
  ``GET /v1/vector_store/list``)
- ``get_vector_store_info`` (``POST /vector_store/info``), both the
  in-memory-registry path and the prisma-DB fallback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:55:40 +00:00
user
bdb00c43cf
fix(spend-tracking): drop orphaned imports; align tests with alias contract
CI surfaced two issues from the previous commit:

1. ``general_settings`` and ``master_key`` were still imported at the top
   of ``get_logging_payload`` but had no remaining users after the
   master-key hash-detection blocks were removed. Drop the import.

2. ``tests/proxy_unit_tests/test_user_api_key_auth.py::test_x_litellm_api_key``
   and ``tests/proxy_unit_tests/test_key_generate_prisma.py::test_master_key_hashing``
   asserted ``valid_token.token == hash_token(master_key)`` — the
   pre-alias behavior. The new contract is
   ``valid_token.token == LITELLM_PROXY_MASTER_KEY_ALIAS`` (and !=
   ``hash_token(master_key)``), since the master key (and its hash)
   must not propagate to the verification-token column or any other
   downstream consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:53:12 +00:00
user
9d9f09934e
chore(auth): substitute alias for master key on UserAPIKeyAuth
Two related changes to how the master-key auth path interacts with
downstream consumers of UserAPIKeyAuth.api_key:

1. The master-key auth branch in user_api_key_auth.py now sets
   `valid_token.api_key` to a stable alias
   (`LITELLM_PROXY_MASTER_KEY_ALIAS = "litellm_proxy_master_key"`) instead
   of the raw master key. Downstream consumers — spend logging,
   Prometheus metrics, audit trails, rate limiting, cost tracking — now
   receive the alias instead of the master key (which they would
   previously hash and propagate). Neither the raw master key nor its
   hash flows past the auth layer.

2. `_is_master_key` in spend_tracking_utils.py is reduced to a strict
   raw-only constant-time comparison. The hashed form is no longer
   considered equivalent.

Side effects:

- The two hash-detection blocks in `get_logging_payload` are removed.
  They were re-detecting the master key per spend-log write to swap in
  the alias; that detection happens once at the auth layer now.

- The `disable_adding_master_key_hash_to_db` general setting becomes a
  no-op. Operators can remove it from their config; existing config is
  still accepted.

- Operator dashboards that filter Prometheus metrics by the master-key
  hash will need to switch to the `api_key="litellm_proxy_master_key"`
  label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:53:12 +00:00
Michael Riad Zaky
04687ba48e Trim verbose comments and docstrings 2026-04-29 11:07:35 -07:00
Michael Riad Zaky
2b9ad4d4eb Fall through to team default when per-member budget max_budget is NULL 2026-04-29 10:14:09 -07:00
Yassin Kortam
9b3cd5ca25
Merge pull request #26730 from yassinkortam/fix/http-handler-keepalive
fix: add optional TCP SO_KEEPALIVE support to aiohttp's TCPConnector
2026-04-29 10:10:59 -07:00
Yassin Kortam
848b79acb5 fix: added keepalive args for aiohttp tcpconnector 2026-04-29 09:14:57 -07:00
Sameer Kankute
d8d1444da4
Merge pull request #26710 from minznerjosh/fix/bedrock-anthropic-tool-result-pdf-content
fix(bedrock, anthropic): translate OpenAI file content on tool-result path
2026-04-29 12:59:08 +05:30
Sameer Kankute
8af338a202
Merge pull request #26757 from BerriAI/litellm_internal_staging
merge main
2026-04-29 12:44:09 +05:30
Sameer Kankute
4b03cb68a2
feat(proxy): move search tool access to object permissions
Store search tool allowlists only on object permissions, wire auth/management/UI flows to object_permission.search_tools, and remove legacy team-metadata search credential code and tests.

Made-with: Cursor
2026-04-29 12:29:20 +05:30
Yuneng Jiang
aa2ef41200 fix(proxy): preserve original transport error if reconnect itself raises
Greptile review on #26756 (P2): if `attempt_db_reconnect` itself raises
(e.g. lock cancellation, timer error, unexpected internal failure), the
original `httpx.ReadError` / transport error was lost — `failure_handler`
and `db_exceptions` alerts then logged the reconnect exception instead of
the actual DB transport problem, masking the root cause.

Wrap the reconnect call in a try/except. On reconnect failure, re-raise
the *original* `first_exc` and chain the reconnect error as `__cause__`
so it remains visible for debuggability without becoming the primary
exception observers see.

Adds `test_call_with_db_reconnect_retry_preserves_original_error_when_reconnect_raises`
asserting (a) the propagated exception is the original transport error
and (b) the reconnect exception is attached as `__cause__`.
2026-04-28 23:55:46 -07:00
Yuneng Jiang
1c9c219a74 fix(proxy): self-heal Prisma read paths + harden reconnect state machine
Two related fixes layered on top of the existing reconnect plumbing:

1. Restore reconnect-and-retry on `PrismaClient.get_generic_data` (issue
   #25143). 1.83.x lost the transport-reconnect-and-retry-once branch that
   1.82.6 had on this method, so transient `httpx.ReadError` flaps now
   surface immediately as `db_exceptions` alerts. `_update_config_from_db`
   fans out four concurrent `get_generic_data` reads, so a single transport
   blip used to mark four alerts and a stale config window.

   Adds `call_with_db_reconnect_retry` to `litellm/proxy/db/exception_handler.py`
   — a single canonical "try DB read, on transport error reconnect once and
   retry once" wrapper. Mirrors the inline pattern in
   `auth_checks._fetch_key_object_from_db_with_reconnect` so we have one
   implementation rather than three drifting copies, and gives future read
   paths a clean opt-in.

2. Fix the `_engine_confirmed_dead` flag-reset bug in
   `_run_reconnect_cycle`. The flag was cleared before `_do_heavy_reconnect()`
   ran, so any failure inside the heavy reconnect (timeout, missing
   DATABASE_URL, recreate failure) left the flag False — and the next
   attempt could silently demote to the lightweight path even though the
   engine was genuinely dead. Move the reset into the success branch so the
   flag stays True across heavy-reconnect failures and the next attempt
   re-enters the heavy branch.

Tests:

- `tests/test_litellm/proxy/db/test_exception_handler_reconnect_retry.py`
  (new) — 9 tests covering the helper's contract: happy path, retry on
  transport error, no retry on data-layer errors, propagation when reconnect
  fails, propagation after second transport error, `hasattr` guard for
  partial mocks, fresh-coroutine-per-call invariant, explicit timeout
  override, default timeouts read off the prisma_client.
- `tests/test_litellm/proxy/db/test_prisma_self_heal.py` — adds:
  - `test_get_generic_data_retries_on_transport_error_for_config_table`
  - `test_get_generic_data_propagates_when_reconnect_fails`
  - `test_engine_confirmed_dead_persists_across_failed_heavy_reconnect`
    (regression test for the flag-reset bug).

All 16 self-heal tests + 9 helper tests + 535 auth/exception-handler tests
pass locally.
2026-04-28 23:44:34 -07:00
Yuneng Jiang
8c91c8b2c4 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_dbReconnectNonBlocking 2026-04-28 23:29:36 -07:00
Cursor Agent
3fb5056305
fix(mcp): address greptile review on short tool prefix
- server.py: drop the redundant server_id append in
  _get_filtered_mcp_servers_from_mcp_server_names. iter_known_server_prefixes
  already yields server_id unconditionally, so the manual append (and its
  misleading comment) was a no-op duplicate.
- utils.py: rewrite the SHORT_MCP_TOOL_PREFIX docstring to accurately
  describe the collision behaviour. The previous wording said collisions
  were 'cosmetic only', but a natural-hash collision IS a routing-correctness
  issue, which is precisely why we already added _assign_unique_short_prefix
  to rehash deterministically. The new comment cross-references that path.
- utils.py: restrict the first character of the short prefix to [A-Za-z]
  via a 52-char alphabet for position 0 only. The remaining two positions
  still use the full base62 alphabet. This keeps prefixes valid identifiers
  on every backend and gives 52*62*62 = 199_888 distinct prefixes (still
  comfortably more than any realistic deployment).
- tests: add coverage proving the first character of the prefix is always
  alphabetic across many server_ids and rehash attempts.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 03:59:40 +00:00
Sameer Kankute
d7862e7fe8
Merge pull request #26745 from BerriAI/litellm_internal_staging
Some checks failed
Unit Tests: Caching (Redis) / caching-redis (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
merge mian
2026-04-29 09:25:42 +05:30
Sameer Kankute
af5b7be51d
Merge pull request #26742 from BerriAI/litellm_internal_staging
merge main
2026-04-29 09:20:12 +05:30
Cursor Agent
3215874e40
fix(test): scope ERROR log assertion to LiteLLM logger in test_model_alias_map
The test was flaking on unrelated asyncio ERROR records (e.g. "Unclosed
client session" from background tasks in other tests). Restrict the
assertion to records emitted by LiteLLM loggers so the test only fails
on errors actually produced by the code under test.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 03:48:41 +00:00
Cursor Agent
df3dbd18d6
feat(mcp): rehash short tool prefix on collision and cache per server
Two MCP servers can natural-hash to the same three-character base62
prefix. With 62**3 = 238_328 slots the birthday bound is ~488 servers
for 50% collision probability, so a single proxy hosting more than
~100 MCP servers has a non-trivial chance of seeing a collision in
practice — and a collision means tool names from two different servers
share a routing key, causing silent mis-routing.

Mitigation:

- compute_short_server_prefix(server_id, attempt=N) folds an attempt
  counter into the SHA-256 seed, so rehashes are deterministic and
  produce a fresh three-char prefix space per attempt.
- New MCPServer.short_prefix field caches the resolved (post-dedup)
  prefix on the model so it stays stable across the process lifetime.
- MCPServerManager._assign_unique_short_prefix walks attempts 0..N
  until it finds a prefix not already used by another server in the
  combined registry. Logs an INFO line when a rehash happens so
  operators have a breadcrumb if it ever does.
- Wired into every registration path: load_servers_from_config,
  add_server, update_server, reload_servers_from_database. The
  database reload path also carries the previously-resolved prefix
  forward so reloads don't churn it.
- get_server_prefix prefers the cached short_prefix when set, so the
  resolved value (not the raw natural hash) is used everywhere.
- iter_known_server_prefixes yields the cached short_prefix too, so
  reverse-lookup tolerance covers the rehashed form.

No-op when LITELLM_USE_SHORT_MCP_TOOL_PREFIX is disabled — the field
stays None and behaviour is unchanged.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 03:43:34 +00:00
Sameer Kankute
b516120036
Merge pull request #26737 from BerriAI/litellm_internal_staging
merge internal staging
2026-04-29 08:50:12 +05:30
ishaan-berri
4ae2996f08
Add gpt-image-2 support (#26644) (#26705)
* Add gpt-image-2 support

* Address gpt-image-2 PR feedback

Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
2026-04-28 20:10:42 -07:00