Commit Graph
38463 Commits
Author SHA1 Message Date
user c84ae97899 Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD
# Conflicts:
#	litellm/litellm_core_utils/url_utils.py
#	litellm/llms/gemini/files/transformation.py
#	litellm/proxy/_lazy_openapi_snapshot.py
#	tests/test_litellm/litellm_core_utils/test_url_utils.py
#	tests/test_litellm/llms/gemini/files/test_gemini_files_transformation.py
#	tests/test_litellm/proxy/test_lazy_openapi_snapshot.py
2026-05-01 15:23:40 -07:00
user 7784b7f4ad chore(providers): allowlist URL model destinations 2026-05-01 15:18:31 -07:00
yuneng-jiangandGitHub d07cdd4481 Merge pull request #26838 from BerriAI/litellm_vcr-cassette-llm-tests-af37
tests(vcr): redis-backed vcrpy cache for offline LLM e2e replay
2026-05-01 15:16:52 -07:00
yuneng-jiangandGitHub 8363fe00e1 Merge pull request #26841 from stuxf/fix/mcp-xff-trust-gate
chore(mcp): require trusted-proxy gate before honouring X-Forwarded-* on OAuth discovery
2026-05-01 15:08:40 -07:00
yuneng-jiangandGitHub 3583ac1159 Merge pull request #26860 from stuxf/fix/provider-path-traversal
chore(security): encode upstream URL path identifiers
2026-05-01 14:47:52 -07:00
yuneng-jiangandGitHub dc681b9eb2 Merge pull request #26954 from BerriAI/claude/lucid-margulis-e99b6b
refactor(rate-limit): consolidate batch + dynamic limiter check/increment
2026-05-01 14:46:25 -07:00
Mateo WangandGitHub 05439530c2 Merge branch 'litellm_internal_staging' into litellm_vcr-cassette-llm-tests-af37 2026-05-01 14:37:48 -07:00
mateo-berri 80415b472e tests(vcr): drop redundant comments and docstrings
Remove explanatory comments that restated what the code already says.
Kept only those that document non-obvious external contracts (the aiohttp
record-path patch's reason for re-feeding the body, and the warning
messages inside save_cassette that reach the user).
2026-05-01 14:36:48 -07:00
yuneng-jiangandGitHub ebd335da67 Merge pull request #27000 from BerriAI/litellm_fix_pdf_file_id_test
[Test] Anthropic: Use jsDelivr CDN For PDF Fixture URL
2026-05-01 14:36:25 -07:00
mateo-berri 53f71fbf4d tests(vcr): emit per-test verdicts via xdist controller's terminalreporter
Previous attempt wrote to sys.__stderr__ from the test fixture. Under
xdist, fixtures run inside worker subprocesses whose stderr is captured
by the controller and only released to the live log on test failure —
so passing tests' verdicts were silently swallowed.

Round-trip via report.user_properties: the worker-side fixture stashes
the verdict on user_properties, xdist serializes it onto the report,
and a controller-side pytest_runtest_logreport hook writes it via the
TerminalReporter (the same plugin that emits PASSED/FAILED markers).
TerminalReporter is resolved lazily on first hook call because it's
not yet registered when conftest's pytest_configure runs.

Verified locally in both serial and xdist modes.
2026-05-01 14:29:06 -07:00
ryan-crabbe-berriandGitHub 610f79dc03 Merge pull request #27003 from BerriAI/litellm_health-endpoint-non200-on-failure
fix(health): return 503 when targeted model is unhealthy or DB is disconnected
2026-05-01 14:23:37 -07:00
yuneng-jiangandGitHub 42122b83f5 Merge pull request #26969 from stuxf/codex/tool-permission-guardrail-fix
chore(guardrails): tighten tool permission checks
2026-05-01 14:17:55 -07:00
mateo-berri 965185c106 fix(tests): host PDF fixture via jsDelivr with proper application/pdf MIME
Raw github serves application/octet-stream which OpenAI/Gemini reject
when LiteLLM fetches the URL client-side. jsDelivr serves the same
file with content-type: application/pdf. Pin to a commit SHA so the
asset is immutable and jsDelivr can cache it for a year.
2026-05-01 14:16:57 -07:00
yuneng-jiangandGitHub 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
mateo-berri c05c865a1c tests(vcr): emit verbose verdicts to un-redirected stderr
Previously, the per-test [VCR HIT/MISS/...] line was written via
TerminalReporter.write_line from inside fixture teardown. Pytest
captures that stream by default and only surfaces it on FAILED tests
(under 'Captured stdout teardown'), so passing tests' verdicts were
invisible in CI logs and the user couldn't tell whether the cache
was working.

Write directly to sys.__stderr__ so the line bypasses pytest's
capture entirely. Under xdist each worker has its own __stderr__
which CircleCI aggregates into the live job log alongside the
PASSED/FAILED markers.
2026-05-01 14:14:44 -07:00
Ryan Crabbe 038b180315 fix(health): validate model_id against scoped model_list in cache-path resolver
A non-admin scoped to ["model-a"] could call /health?model_id=id-b
(where id-b belongs to a deployment outside their scope) and the
background-cache code path would return id-b's cached health entry. The
helper returned {model_id} unconditionally, so the cache filter was
driven by an unvalidated id and the global cache leaked the entry — the
ternary `targeted_ids if not None else allowed_model_ids` skipped any
intersection with the caller's allowed deployments.

Make _resolve_targeted_model_ids walk the supplied model_list for both
the model and model_id branches. Callers pass an already-scoped list
(filtered to allowed model_names for non-admins, full list for admins),
so an out-of-scope model_id resolves to an empty set and the cache
filter drops every entry — matching the live path's existing behavior.
2026-05-01 14:11:51 -07:00
Yuneng Jiang 5cd1272eda Revert "[Fix] Image Handling: Fall Back To URL Extension When Server Returns Octet-Stream"
This reverts commit 019f5eeed7.
2026-05-01 14:09:47 -07:00
Yuneng Jiang 019f5eeed7 [Fix] Image Handling: Fall Back To URL Extension When Server Returns Octet-Stream
GitHub serves PDFs from raw.githubusercontent.com, github.com/.../raw/..., LFS, and Releases as application/octet-stream by deliberate anti-hotlinking policy. Anyone who passes a GitHub-hosted PDF URL as an OpenAI / Gemini / Bedrock file_id hits "unsupported MIME type 'application/octet-stream'" because _process_image_response inlines the URL with whatever Content-Type the server sent.

When the server-provided Content-Type is application/octet-stream or binary/octet-stream and the URL extension maps to a known MIME type (.pdf, .png, .jpg, etc.), trust the extension instead. Specific Content-Types (image/png, application/pdf) still win over the extension; the override only applies to generic binary types.

Also restores the Greptile SHA-pinned raw.githubusercontent.com URL on the file_id integration test so we test against the same hosting real users hit, no third-party CDN.
2026-05-01 14:08:15 -07:00
mateo-berri 225d01cb4f fix(tests): use github-hosted PDF fixture for Anthropic Files API test
Anthropic's URL fetcher intermittently returns 400 'Unable to download
the file' for the Wikipedia URL the test was using. Point it at the
repo's existing tests/llm_translation/fixtures/dummy.pdf via raw
GitHub instead — small, deterministic, reliably fetchable.

With a stable URL the test no longer needs to be opted out of VCR;
remove it from the incompatible list so it can replay from cassette.
2026-05-01 14:07:32 -07:00
Yuneng Jiang 42cc765593 Merge remote-tracking branch 'origin/litellm_fix_pdf_file_id_test' into litellm_fix_pdf_file_id_test
Resolve URL conflict: keep Greptile's commit-SHA pin for immutability, but route through jsDelivr (cdn.jsdelivr.net/gh/BerriAI/litellm@<sha>/...) so the response Content-Type is application/pdf instead of application/octet-stream. Without this, OpenAI / Gemini / Router PDF tests reject the inlined file_data with "unsupported MIME type 'application/octet-stream'".
2026-05-01 14:02:35 -07:00
Ryan Crabbe 21e19bf3a5 test(health): tighten happy-path 200 assertions to exact equality
Per review: `assert response.status_code != 503` is satisfied by 404,
500, or any other non-503 code, so a regression that returned the wrong
non-503 status would slip through. Switch to `== 200` so the assertions
verify the actual expected status, not just the absence of one specific
failure.
2026-05-01 13:59:10 -07:00
mateo-berri a47c4e7d1b tests(vcr): refuse to persist cassettes past 50 episodes
A test that produces non-deterministic request bodies (e.g. uuid in
the prompt) under record_mode=new_episodes never replays — every CI
run appends fresh unmatched episodes. The cassette grows unbounded
over time and silently inflates Redis (we observed one cassette at
22 episodes / ~860KB after ~5 CI runs).

Refuse the save when episode count exceeds MAX_EPISODES_PER_CASSETTE
so the pathology surfaces with a loud warning that points to the
opt-out fix instead of festering invisibly.
2026-05-01 13:56:20 -07:00
Yuneng Jiang f13b7af2fa Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_pdf_file_id_test 2026-05-01 13:54:50 -07:00
Yuneng Jiang feb238bce9 [Test] Anthropic: Use jsDelivr CDN For PDF Fixture URL To Preserve Content-Type
The previous URL switch to raw.githubusercontent.com fixed Anthropic's "Unable to download" failure but caused OpenAI / Gemini / Router PDF tests to fail with "unsupported MIME type 'application/octet-stream'": those providers download the URL and inline it as data:<Content-Type>;base64,..., and raw.githubusercontent.com serves PDFs as application/octet-stream.

jsDelivr proxies the same in-repo fixture (cdn.jsdelivr.net/gh/BerriAI/litellm@main/...) and returns the correct Content-Type: application/pdf, so all providers (Anthropic forwards the URL natively; OpenAI/Gemini/Bedrock fetch and inline) get the right MIME type without changing transformer code.
2026-05-01 13:54:44 -07:00
mateo-berri 8c01b02779 tests(vcr): opt out tests that observe live cross-call provider state
Some tests can't benefit from cassette replay because they assert on
state that only exists in the live provider between two calls (e.g.
prompt-cache propagation, intermittent provider quirks). Marking them
with @pytest.mark.vcr just wastes cycles trying to record cassettes
they will never replay against successfully.

Opt-out by nodeid suffix so subclassed/parametrized variants are
covered:

- ::test_prompt_caching — Anthropic/Bedrock prompt-cache propagation
  isn't deterministic in the 0–1s window the test gives it.
- ::test_async_pdf_handling_with_file_id — flaky upstream Wikipedia
  fetch through the Anthropic Files API.
- TestBedrockInvokeNovaJson::test_json_response_pydantic_obj —
  Bedrock Nova returns tool_call vs JSON nondeterministically (other
  providers' subclasses are healthy).
- ::test_bedrock_converse__streaming_passthrough — Bedrock streaming
  response_cost calc returns None intermittently.

These tests keep their existing @pytest.mark.flaky retry behavior.
2026-05-01 13:50:34 -07:00
Ryan Crabbe 3340533cfb fix(health): filter background-cache result by targeted model before 503 check
When use_background_health_checks is enabled, /health?model=foo returned
the full cached aggregate across every model — so an unhealthy foo
combined with any other healthy deployment kept healthy_count > 0 and
the targeted-503 path never fired.

Resolve the targeted model/model_id to a deployment-id set first
(mirroring perform_health_check's match-on-model_name-or-litellm_model
semantics) and narrow the cache to those IDs before _post_process
evaluates healthy_count, so the 503 contract holds for both the live
and cache code paths.
2026-05-01 13:50:03 -07:00
mateo-berri ff63bdb984 tests(vcr): only persist cassette on test pass to avoid poisoning cache
A test that fails (incl. all the failing retries before a passing one)
can otherwise overwrite a known-good cassette with a 'bad luck'
recording. Tests like test_prompt_caching, which assert on provider
state across two calls, can produce a 200 response that semantically
fails the assertion — the 2xx filter doesn't catch this because the
HTTP layer is fine.

- pytest_runtest_makereport hook attaches each phase report to the
  pytest item.
- _vcr_outcome_gate fixture (combining the verbose-mode reporter)
  reads the call-phase outcome at teardown and informs the persister
  via mark_test_outcome_for_cassette before vcrpy's Cassette.__exit__
  triggers save_cassette.
- save_cassette consults the per-key 'did the test pass?' flag and
  short-circuits when False, leaving any prior good recording intact.
- Defaults to passed=True when no marker is present so non-test
  usage of the persister still works.
2026-05-01 13:35:29 -07:00
Ryan Crabbe 7635955c91 fix(health): return 503 when targeted model has no healthy endpoints or DB is disconnected
/health?model=foo and /health?model_id=foo previously returned HTTP 200
even when zero endpoints were healthy, forcing monitoring systems to
parse the JSON body to detect failure. /health/readiness similarly
returned 200 even when a configured Prisma DB was unreachable, leaving
unhealthy pods in rotation.

Both endpoints now flip to HTTP 503 in the failure case while keeping
the JSON response body identical, so existing parsers continue to work
and orchestrators can rely on the HTTP status alone.
2026-05-01 13:20:43 -07:00
mateo-berri cf4c9ede61 tests(vcr): add LITELLM_VCR_VERBOSE per-test hit/miss reporting
Set LITELLM_VCR_VERBOSE=1 to print a one-line cassette verdict per
test (HIT / MISS / PARTIAL / NOOP) showing replay vs new-recording
counts. Useful for local QA to confirm which tests actually exercised
the cache and which fell through to the live provider.
2026-05-01 13:00:20 -07:00
mateo-berri a7e8189b17 tests(vcr): make redis persister resilient to transient outages
Managed Redis (e.g. Upstash) drops idle TLS connections, which surfaced
in CI as a teardown ERROR on test_gemini_image_size_limit_exceeded:

  redis.exceptions.ConnectionError: EOF occurred in violation of
  protocol (_ssl.c:2427)

Cassette persistence is a cache, not test correctness, so:

- Configure the redis client with Retry(ExponentialBackoff, retries=2)
  on ConnectionError/TimeoutError to absorb single-socket drops.
- Wrap save_cassette so a final failure logs a warning instead of
  failing teardown — the next run re-records.
- Wrap load_cassette so an outage on read becomes a cache miss
  (CassetteNotFoundError) instead of erroring in setup.
2026-05-01 12:53:36 -07:00
yuneng-jiangandGitHub b1fcdb671b Merge pull request #26643 from BerriAI/litellm_fix-config-update-targeted-upserts
[Fix] /config/update: targeted per-section writes, drop store_model_in_db gate
2026-05-01 12:50:25 -07:00
Krrish DholakiaandClaude Opus 4.7 ca3e659a3c review: separate ttl_seconds from window_size_seconds in atomic ARGV
The Lua ARGV layout for `CHECK_AND_INCREMENT_BY_N_SCRIPT` documents two
distinct slots — `ttl_seconds` (counter-key TTL) and `window_size_seconds`
(sliding-window length) — but the Python call site collapsed both to
`int(window_size)`. They happen to be equal today, so the bug was latent,
but a future descriptor carrying a custom counter TTL would have been
silently mis-applied (counter key expiring at the wrong time relative to
the window).

Make the two values separate variables (`ttl_seconds`, `window_size_seconds`)
before the `script_args.extend([...])` call so the ARGV layout matches the
documented contract by construction. The values still flow through to
`per_counter_meta` so the in-memory fallback uses the same source of truth.

Also:
- Document the COUNTER-level (not descriptor-level) parallel-indexing
  invariant on `_build_atomic_response` and `per_counter_meta`. A descriptor
  with both RPM and TPM emits two counters and two meta entries; the Lua
  script's `i` enumerates counters and the over-limit return tuple's index
  maps directly to `per_counter_meta[i - 1]`. Comment surfaces this so a
  future contributor doesn't refactor the arrays apart.
- Annotate the `increments` literal in the batch limiter directly instead
  of routing through `cast(...)`. Drops an unneeded `cast` import.
- Comment on the dynamic limiter's tracking-only branch explaining why
  `should_rate_limit` (non-atomic) is intentional there: we want to bump
  the priority counter so future enforced windows have accurate usage,
  and the OVER_LIMIT response is intentionally ignored.

Tests: 65 passed (1 skipped), 0 regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:43 -07:00
mateo-berri 4c69557621 tests(vcr): isolate cassette redis to CASSETTE_REDIS_URL
Stop falling back to REDIS_URL/REDIS_SSL_URL/REDIS_HOST for the VCR
persister. Sharing a Redis with the application cache risks cassettes
being wiped by tests that flush the app Redis.
2026-05-01 12:32:59 -07:00
yuneng-jiangGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
a7a112798d Update tests/llm_translation/base_llm_unit_tests.py
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-01 12:32:01 -07:00
Yuneng Jiang d769e81c90 [Test] Anthropic: Use Repo PDF Fixture For test_async_pdf_handling_with_file_id
The test was passing the Wikipedia URL https://upload.wikimedia.org/wikipedia/commons/2/20/Re_example.pdf as the file_id, which Anthropic's URL fetcher can no longer download (returns "Unable to download the file"). The URL is healthy for generic clients but Anthropic's fetcher fails on it deterministically, so the test has been red across PRs on litellm_internal_staging.

Switch to the in-repo fixture at tests/llm_translation/fixtures/dummy.pdf served via raw.githubusercontent.com - same fixture used elsewhere in the repo, no external CDN dependency that can block by user-agent.
2026-05-01 12:24:28 -07:00
ryan-crabbe-berriandGitHub 34b340218e Merge pull request #26998 from BerriAI/litellm_stream_timeout_midstream_fallback
Fix: trigger fallbacks on mid-stream httpx.TimeoutException
2026-05-01 12:24:04 -07:00
Krrish DholakiaandClaude Opus 4.7 eba0cdf3f5 fix(rate-limit): fail closed on unrecognized OVER_LIMIT descriptor
If atomic_check_and_increment_by_n returns overall_code=OVER_LIMIT but no
status entry matches a descriptor key the dynamic limiter dispatcher knows
how to translate into a 429 (`model_saturation_check` or `priority_model`),
the for-loop previously exited cleanly and execution fell through to the
priority-tracking increment + the data["litellm_proxy_rate_limit_response"]
write — silently admitting an over-limit request.

This is the fail-open path a future contributor would hit by wiring a new
descriptor type into enforced_descriptors without updating the dispatcher.
Refuse the request with a generic 429 carrying the offending descriptor
metadata so the operator can see what slipped past, and emit an error log
to surface the wiring gap.

Adds a regression test (test_dynamic_rate_limiter_v3_fails_closed_on_unknown_descriptor)
that drives the limiter with a synthetic OVER_LIMIT response carrying an
unrecognized descriptor_key and asserts a 429 is raised.

Tests: 65 passed (1 skipped), 0 regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:19:43 -07:00
ishaan-berriandGitHub 231c430200 fix: scope CLI stored token to base_url to prevent cross-domain credential leakage (#26945)
* fix: add expected_base_url origin check to get_litellm_gateway_api_key

* fix: scope get_stored_api_key and save base_url on login

* fix: pass base_url to get_stored_api_key in CLI entrypoint

* fix: scope ProxyClient stored key to base_url

* test: add expected_base_url coverage for get_stored_api_key

* fix: initialize self.http with resolved api_key not raw param

* fix: black formatting in client.py and test_auth_commands.py
2026-05-01 12:11:32 -07:00
ishaan-berriandGitHub c06cc560e0 fix(ui): add Your Usage view for admin users on usage page (#26746)
* feat(ui): add "Your Usage" view for admin users on usage page

Admins were forced to use the global usage view with no way to scope it
to their own activity without manually searching for themselves in the
user filter dropdown.

Adds a new "Your Usage" option (admin-only) to the usage view selector.
When selected, it locks the data to the admin's own user_id and hides
the "Filter by user" dropdown.

* feat(ui): wire my-usage view to admin's own user_id in UsagePageView

When usageView is "my-usage", effectiveUserId resolves to the logged-in
admin's own userID. The "Filter by user" dropdown is hidden in this
view (only shown for "global").

* add: screenshots for usage page Your Usage admin fix

* remove: QA screenshots from .github/screenshots
2026-05-01 12:10:47 -07:00
Krrish DholakiaandClaude Opus 4.7 6496e58417 review: address atomic limiter review feedback
- Lua script now reads time via redis.call('TIME') instead of a client-supplied
  timestamp. Prevents window-reset divergence across replicas with skewed
  wall-clocks, which could otherwise reopen the cross-replica TOCTOU window.
- Per-descriptor window_size is now plumbed through both the Lua ARGV layout
  and the in-memory fallback. Previously the in-memory path used the global
  self.window_size while Lua honored the per-descriptor override, so a
  descriptor with a custom window would be enforced inconsistently between
  Redis-available and Redis-unavailable code paths.
- Lua-failure fallback path now logs at error severity and explicitly
  documents the in-memory ↔ Redis state divergence risk so operators can
  alert on it. Prior `warning` log understated the impact.
- Coarse-granularity lock is now documented inline with the conditions under
  which a per-descriptor sharded lock would be worth introducing.
- New regression test: zero-token batch consumes RPM only and is properly
  capped by the RPM ceiling (validates the asymmetric quota path that arises
  from `inc_amount <= 0: continue`).

Tests: 64 passed (1 skipped), 0 regressions. Multi-instance Redis loadtest
re-verified: chat 20/80 success @ RPM=20, batches 3/20 @ TPM=200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:02:04 -07:00
b14e1d7d6a refactor: scope /health response to caller's models and tidy display fields (#26935)
* refactor: scope /health response to caller's models and tidy display fields

Two small consistency changes to the /health response:

1. health_endpoint() now narrows _llm_model_list to deployments whose
   model_name is in user_api_key_dict.models, matching how other model
   listing endpoints already scope their output. The same narrowing applies
   to the cached health_check_results dict when background_health_checks is
   enabled, via a new _filter_health_check_results_by_model_ids helper.

2. ILLEGAL_DISPLAY_PARAMS in health_check.py picks up api_base and
   api_version, which are provider routing fields and not part of the
   health response shape.

Tests in tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py
pin both behaviors so future changes do not widen the response shape.

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

* address greptile review feedback (greploop iteration 1)

- tests: extend background-cache test with model_id on cached entries plus
  positive assertions that model-a's deployment is the one returned, so
  the test is no longer satisfied by an empty result.
- _health_endpoints.py: add a verbose_proxy_logger.debug line when a scoped
  key has accessible model_names but the matching deployments have no
  model_info.id, so the empty cache-result case is observable.

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

* make background-cache test's non-vacuity explicit

Restructure test_health_endpoint_filters_background_cache_by_user_access
so the assertions positively pin the post-scoping result (one entry,
model_id == "id-a", api_base == https://example-a.test) and add fixture
sanity checks that confirm the source cache had two entries and every
cached entry carries a model_id.

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

* gate api_base in /health response on proxy-admin role

Replace the blanket strip of api_base / api_version with a role-aware
post-processor:

- api_base is now left in the cleaned per-deployment dict that
  _clean_endpoint_data produces (api_version stays in the denylist).
- health_endpoint() removes api_base from each endpoint entry before
  returning when the caller's user_role is not PROXY_ADMIN /
  PROXY_ADMIN_VIEW_ONLY. The strip uses a copy so the shared
  health_check_results cache still carries api_base for subsequent
  admin reads.

Net effect: a proxy admin can still see which Vertex region or Azure
resource is healthy in the /health output, while non-admin keys (and
read-only keys) only see model / model_id / status fields.

Tests:
- test_health_endpoint_admin_sees_api_base_non_admin_does_not pins both
  branches and verifies the cache is not mutated.
- test_clean_endpoint_data_strips_credentials_but_keeps_api_base
  replaces the previous mask/drop tests now that the cleaning helper
  no longer touches api_base.

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

* address review feedback: api_version symmetry, missing-id warnings, deprecation header

Three blockers raised in review:

1. api_version asymmetry — api_base was role-gated for proxy admins, but
   api_version was unconditionally stripped via ILLEGAL_DISPLAY_PARAMS.
   Move api_version out of the credential denylist and into a new
   ADMIN_ONLY_HEALTH_DISPLAY_PARAMS tuple alongside api_base, so admins
   keep both routing fields and non-admins lose both. Useful for telling
   apart Vertex regions or Azure api-versions from the /health response.

2. Silent empty results when scoped key's deployments lack model_info.id —
   raise the existing log from .debug to .warning, and add a structured
   "warnings" field to the response so the caller can distinguish "no
   deployments configured" from "deployments excluded due to missing
   model_info.id".

3. Migration signal for the api_base / api_version removal — when a
   non-admin caller hits /health, set a "Litellm-Health-Field-Notice"
   response header so existing dashboards or scripts that parsed those
   fields can detect the change programmatically rather than silently
   seeing absent keys.

Tests adjusted: existing background-cache test injects a Response stub,
admin-vs-non-admin test now asserts both api_base and api_version are
gated and asserts the notice header. New test covers the warnings field
when a scoped key's deployments are missing model_info.id.

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

* defensive copies + clarifying comments in /health filter

- _filter_health_check_results_by_model_ids now shallow-copies each
  retained endpoint dict before returning. The shared module-level
  health_check_results cache should never be mutated by downstream
  transforms, even though _strip_admin_only_fields_from_health_result
  already builds new dicts today.
- Document the live (model_name) vs cache (model_id) scoping asymmetry
  so future readers do not have to derive it from the warnings field.
- Document why _PROXY_ADMIN_ROLES includes PROXY_ADMIN_VIEW_ONLY (read-
  only operators need routing fields to diagnose health).

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

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:01:27 -07:00
yuneng-jiangandGitHub 02582466c4 Merge pull request #24340 from BerriAI/litellm_staging_03_21_2026
Litellm staging 03 21 2026
2026-05-01 11:57:44 -07:00
userandClaude Opus 4.7 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
Ryan Crabbe 35133e3550 Fix: trigger fallbacks on mid-stream httpx.TimeoutException
The async CustomStreamWrapper.__anext__ caught httpx.TimeoutException
and re-raised it raw, bypassing _handle_stream_fallback_error. The
Router's FallbackStreamWrapper only reacts to MidStreamFallbackError,
so stream_timeout firing mid-stream never triggered fallbacks the way
connection-phase timeout does.

Route timeouts through _handle_stream_fallback_error like every other
exception so they wrap into MidStreamFallbackError and the Router can
switch to a fallback model.
2026-05-01 11:46:17 -07:00
ishaan-berriandGitHub fdd9f3d129 fix: block path traversal SSRF in BitBucket, Arize Phoenix, and AssemblyAI clients (#26943)
* fix: sanitize BitBucket file path to block path traversal SSRF

* fix: sanitize Arize Phoenix prompt_version_id to block SSRF

* fix: sanitize AssemblyAI transcript_id to block SSRF

* test: add path traversal SSRF security tests for BitBucket client

* test: add SSRF security tests for Arize Phoenix client

* style: black format arize_phoenix_client.py

* style: black format assembly_passthrough_logging_handler.py

* test: add SSRF security tests for AssemblyAI transcript_id validation

* fix: move AssemblyAI transcript_id validation before try/except so ValueError propagates
2026-05-01 11:45:12 -07:00
userandClaude Opus 4.7 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
ishaan-berriandGitHub 32704ff7b2 fix(projects): project dropdown empty for internal_user (3 bugs) (#26664)
* fix(projects): fire useProjects hook for all authenticated users, not just admins

* fix(routes): add /project/list and /project/info to internal_user_routes allowlist

* fix(projects): use members_with_roles + LiteLLM_UserTable.teams for membership checks

* feat(ui): add "Your Usage" view for admin users on usage page

Admins were forced to use the global usage view with no way to scope it
to their own activity without manually searching for themselves in the
user filter dropdown.

Adds a new "Your Usage" option (admin-only) to the usage view selector.
When selected, it locks the data to the admin's own user_id and hides
the "Filter by user" dropdown.

* feat(ui): wire my-usage view to admin's own user_id in UsagePageView

When usageView is "my-usage", effectiveUserId resolves to the logged-in
admin's own userID. The "Filter by user" dropdown is hidden in this
view (only shown for "global").

* add: screenshots for usage page Your Usage admin fix

* fix(ui): gate useProjects on admin roles to fix failing unit test

* feat(proxy): add /project/list and /project/info to internal user routes

* fix(enterprise): use members_with_roles and litellm_usertable.teams for project access checks

* remove .github screenshots and workflow file from PR
2026-05-01 11:42:22 -07:00
harish-berriandGitHub c8fb77f119 Merge pull request #25980 from BerriAI/litellm_metrics_auth
Fix /metrics hang when require_auth_for_metrics_endpoint is true and auth succeeds
2026-05-01 11:01:39 -07:00
Yuneng Jiang 650821b538 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix-config-update-targeted-upserts
# Conflicts:
#	tests/test_litellm/proxy/test_proxy_server.py
2026-05-01 10:38:34 -07:00
Sameer KankuteandGitHub 8300657af9 fix(mcp): preserve oauth2 m2m auth for tools routes (#26871)
* Fix tool/list M2M creds issue

* Fix tool call creds issue

* Fix greptile review

* Fix lint

* Fix lint

* Fix lint

* Fix lint
2026-05-01 10:26:10 -07:00