Commit graph

40072 commits

Author SHA1 Message Date
Michael Riad Zaky
adcab435a4 swagger: stub-inject unloaded lazy features and warm on dropdown expand 2026-04-29 17:20:56 -07:00
Michael Riad Zaky
0f8dd28542 lazy-load optional feature routers on first request 2026-04-29 17:20:55 -07:00
shivam
181e99b996
Fix health pagination review issues
Made-with: Cursor
2026-04-29 17:16:32 -07:00
user
88d8a80761 tighten cli sso session flow 2026-04-29 17:13:25 -07:00
ishaan-berri
4a7af1ff68
feat(proxy): durable agent workflow run tracking via /v1/workflows/runs (#26793)
* feat(schema): add workflow run tracking tables (LiteLLM_WorkflowRun, LiteLLM_WorkflowEvent, LiteLLM_WorkflowMessage)

* feat(proxy): add /v1/workflows/runs endpoints for durable agent workflow tracking

* feat(proxy): register workflow management router in proxy_server

* docs(workflows): add README for workflow run tracking API

* test(workflows): add unit tests for /v1/workflows/runs endpoints

* fix(workflows): atomic event+status update via tx(), run_id 404 guard, sequence retry on collision

* test(workflows): add tx mock, 404 on unknown run_id, retry-on-collision tests

* fix(workflows): constrain status to Literal enum, rename total→count in list responses

* add tenant isolation and bounded limits to workflow endpoints

* add created_by column and index to LiteLLM_WorkflowRun

* add ownership and bounded-limit tests for workflow endpoints

* Fix workflow run ownership for null owners

* guard prisma import in workflow_management_endpoints

* sync schema.prisma copies with workflow run models

* black: format workflow_management_endpoints.py

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-04-29 17:12:18 -07:00
shivam
a7a3f0a19d
Address health pagination review feedback
Made-with: Cursor
2026-04-29 16:49:25 -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
shivam
0b9d06a509
Fix model e2e result count locator
Made-with: Cursor
2026-04-29 16:25:57 -07:00
Ryan Crabbe
2461139593
fix(proxy): inherit caller identity in passthrough batch managed-object
Read user_id and team_id from the request's litellm_params metadata when
fabricating the UserAPIKeyAuth handed to the managed_files hook, so
batches created via passthrough are attributed to the real requester
instead of a hardcoded fallback. Adds parametrized regression coverage
for both the populated-metadata and empty-kwargs cases.
2026-04-29 16:25:13 -07:00
Yuneng Jiang
1fd38eb5a5 fix(proxy): /config/update normalize existing success_callback before dedup
When a litellm_settings row already holds mixed-case names (e.g.
["Langfuse"]) — written by another code path or by hand — the
union-on-update path was running set([...]) over the raw existing list
plus the lowercase-normalized incoming list, so "Langfuse" and
"langfuse" survived as duplicates. delete_callback uses a lowercase
lookup, leaving the mixed-case entry unreachable.

Normalize the existing list with normalize_callback_names before the
union so the merged list converges to lowercase. Adds a regression test
covering the case where the DB starts with ["Langfuse", "SQS"] and the
caller submits ["langfuse"].
2026-04-29 16:21:51 -07:00
user
7c4ef97239
fix(passthrough): drop now-unused CommonProxyErrors top-level import
The previous commit removed the only top-level use of
``CommonProxyErrors`` (the enterprise-gate ``raise ValueError``).
Ruff F401 flagged the import as unused; the function-local import at
line 2601 in a separate handler is the only remaining caller.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:18:43 +00:00
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
14473ed8f9
fix(static-assets): ruff F811 — drop duplicate Response import
``Response`` is already imported from the top-level ``fastapi``
package via the multi-line ``from fastapi import (...)`` block at the
top of the file (along with ``Depends``, ``HTTPException``, etc.) —
``fastapi.Response`` is the same class that ``fastapi.responses``
re-exports. The earlier ``from fastapi.responses import Response``
addition triggered ruff F811 for redefinition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:04:34 +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
6c386af9c7
fix(team): mypy — guard organization_id None before _verify_org_access call
``LiteLLM_OrganizationTableUpdate.organization_id`` is typed
``Optional[str]`` to allow update payloads that don't change the id.
``_verify_org_access`` expects ``str``. Add an explicit None check
that raises 400 before the access guard fires — previously this would
have crashed at runtime on a malformed update payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:00:33 +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
shivam
1277cbe454
Add health status pagination controls
Made-with: Cursor
2026-04-29 15:50:55 -07:00
user
89aa13fdf3
fix(static-assets): also wrap admin-only Vault token verification in async_safe_get
Variant analysis on the unauthenticated /get_image SSRF surfaced one
related sink in an admin-only endpoint:
``test_hashicorp_vault_connection`` in
``config_override_endpoints.py:402`` calls
``async_client.get(f"{vault_addr}/v1/auth/token/lookup-self")`` with
no SSRF guard. ``vault_addr`` is admin-set, so the threat model is
"admin misconfig (or attacker with admin creds) pivots Vault calls
to cloud metadata or another internal IP."

Same fix shape as the unauthenticated endpoints: wrap in
``async_safe_get`` so each redirect hop is re-validated and private
networks are rejected. Admins running against a legitimate internal
Vault should add the host to ``litellm.user_url_allowed_hosts`` —
the existing escape hatch already used elsewhere in the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:47:43 +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
Ryan Crabbe
f3fd79bf23
fix: trim caller-supplied dicts from compile_prompt error message
Drop prompt_variables and client_messages from the re-raised error so
callers cannot leak secrets, tokens, or PII embedded in those payloads
through HTTP error responses. Both sync and async variants.
2026-04-29 15:16:01 -07:00
Ryan Crabbe
a291cc60cf
fix: drop sensitive locals from re-raised error messages
Remove parameters that may contain credentials from the messages built
inside broad except handlers. These messages can surface in HTTP error
responses, so caller-supplied secrets and integration tokens shouldn't
be interpolated into them.
2026-04-29 15:11:29 -07: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
4f751fdcc6
fix(proxy): also accept LITELLM_ASSETS_PATH for /get_favicon local path
Align ``/get_favicon``'s allowed-root list with ``/get_image``'s. Both
endpoints now accept paths under any of:

* ``LITELLM_ASSETS_PATH`` (or its default — ``/var/lib/litellm/assets``
  for non-root, the package dir otherwise)
* the package's bundled-asset dir (``proxy/_experimental/out`` for the
  default favicon, ``proxy/`` for the default logo)
* the proxy package dir (``current_dir``) as a final fallback

Without this, an admin who put a custom favicon under
``LITELLM_ASSETS_PATH`` (e.g. mounted into the container at
``/var/lib/litellm/assets/favicon.ico``) would have the favicon
endpoint silently fall back to the default after the previous commit's
path-containment guard. The logo endpoint already accepted this root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:18:07 +00: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
Michael Riad Zaky
b5b07089dd Update get_team_member_default_budget docstring for NULL fallback 2026-04-29 14:08:28 -07:00
Cursor Agent
4b9505bb9f
fix: address Cursor Bugbot findings on PR #26691
- Remove unused search_provider parameter from
  SearchAPIRouter._resolve_search_provider_credentials. The function
  only reads tool_litellm_params; the docstring already omitted
  search_provider, confirming it was unintentional dead code.
- Drop redundant hasAgents/hasSearchTools conditions from the outer
  object_permission guard in OldTeams.tsx. Both agent and search-tool
  handling already run independently below this block with their own
  object_permission initialization, so including them in the outer
  guard caused an empty object_permission to be created prematurely
  and never populated within that block.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 21:08:19 +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
78d12ee888
refactor(vector-stores): extract _fetch_and_authorize_vector_store helper
/simplify pass:
- ``update_vector_store`` (newly added) and ``get_vector_store_info``'s
  DB-fallback path duplicated the same shape: ``find_unique`` →
  ``model_dump`` → ``LiteLLM_ManagedVectorStore(**)`` →
  ``_check_vector_store_access`` → raise 404/403. Extract into
  ``_fetch_and_authorize_vector_store`` so the pattern lives in one
  place; future endpoints that need the same gate get it via one call.
- The ``except HTTPException: raise`` guard added in the prior commit is
  retained — the helper raises HTTPException(403/404) and the catch-all
  ``except Exception`` would otherwise rewrite them as 500.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:56:09 +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