Commit graph

9203 commits

Author SHA1 Message Date
Cursor Agent
1cbe744114
feat(spend-logs): opt-in suppression of stack traces in spend-tracking error logs
Adds LITELLM_SUPPRESS_SPEND_LOG_TRACEBACKS env var. When set to true and the
proxy log level is INFO or above, spend-tracking error paths emit a single
ERROR line without the full traceback. Stack traces are preserved at DEBUG
and the Sentry / proxy_logging_obj.failure_handler path is unchanged.

The new spend_log_error helper is wired through the spend write hot path:
  - DBSpendUpdateWriter (update_database, _update_*_db, batch upsert,
    redis-commit fallbacks)
  - _ProxyDBLogger._PROXY_track_cost_callback
  - get_logging_payload exception path
  - update_spend / update_daily_tag_spend / spend logs queue monitor

Resolves LIT-2704.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-02 00:44:34 +00:00
yuneng-jiang
c7c7c8f07a
Merge pull request #27028 from BerriAI/litellm_policy_does_not_work
Fix runtime policy attachment initialization
2026-05-01 17:39:56 -07:00
yuneng-jiang
c154b0df24
Merge pull request #27016 from stuxf/fix/mcp-openapi-tool-auth-bypass
fix(mcp): run pre_call_tool_check on OpenAPI/local-registry path (VERIA-7)
2026-05-01 17:38:40 -07:00
stuxf
e8818d69e0
fix(proxy): re-validate user_id after /user/info re-parses query (#27009)
* fix(proxy): re-validate user_id ownership after /user/info re-parses query

The route-level access check in `RouteChecks.non_proxy_admin_allowed_routes_check`
reads `request.query_params.get("user_id")`, which decodes literal `+` to
spaces. The endpoint then re-parses the raw query string with `urllib.unquote`
in `get_user_id_from_request` to preserve `+` characters (so plus-addressed
emails work as user_ids). Those two paths produce different ids: a caller
who registered a user_id containing a literal space could pass the route
check and then read another user's row by sending the encoded `+` form.

Add `_enforce_user_info_access` and call it after `_normalize_user_info_user_id`
returns the final id. Proxy admin / view-only admin still bypass; everyone
else must match the resolved user_id (or have no user_id, which falls back
to the caller's own id later in the handler).

Tests cover the admin bypass, owner-match path, and the cross-user lookup
that this change blocks.

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

* fix(proxy): apply user_info ownership check to PROXY_ADMIN_VIEW_ONLY

`_enforce_user_info_access` was bypassing both PROXY_ADMIN and
PROXY_ADMIN_VIEW_ONLY, but the upstream route check in
`RouteChecks.non_proxy_admin_allowed_routes_check` only treats
PROXY_ADMIN as a true admin for the `/user/info` route — view-only
admins go through the `user_id == valid_token.user_id` enforcement
along with regular users. Mirroring that asymmetry left the same
encoded-`+` bypass open for view-only admins whose user_id contains a
literal space.

Drop the PROXY_ADMIN_VIEW_ONLY exemption so the post-decode re-check
matches the upstream rule. Update tests: a view-only admin must now
be blocked from cross-user lookups but still allowed to read their
own row.

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 17:38:14 -07:00
stuxf
b80246971b
fix(batches): count non-chat tokens, validate batch-file model access (VERIA-39) (#27015)
* fix(batches): count non-chat tokens and validate every model in batch file

Two security control bypasses on POST /v1/batches:

1. `_get_batch_job_input_file_usage` only summed tokens for
   `body.messages` (chat completions). Embedding (`input`) and text
   completion (`prompt`) batches reported zero, letting massive
   non-chat workloads slip past TPM rate limits. Extend the counter
   to handle string and list shapes for both fields.

2. The batch input file was forwarded to the upstream provider
   without inspecting the models named inside the JSONL — only the
   outer `model` query parameter was checked against the caller's
   allowlist. A caller restricted to gpt-3.5 could submit a batch
   targeting gpt-4o and the upstream would execute it under the
   proxy's shared API key.

Add `_get_models_from_batch_input_file_content` (returns the
distinct `body.model` values) and call it from
`_enforce_batch_file_model_access` in the pre-call hook, which runs
each model through `can_key_call_model` so the same allowlist
semantics (wildcards, access groups, all-proxy-models, team aliases)
the proxy enforces on `/chat/completions` apply here too. Any
unauthorized model raises a 403 before the file is forwarded.

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

* fix(batches): count pre-tokenized prompt/input shapes, classify 403 logs

Two follow-ups from the Greptile review on the batch validation PR:

1. P1 TPM bypass via integer token arrays. The OpenAI batch schema
   accepts ``prompt`` and ``input`` as ``list[int]`` (a single
   pre-tokenized prompt) or ``list[list[int]]`` (multiple) in addition
   to the string and ``list[str]`` shapes. Pre-fix only the string
   shapes were counted, so a caller could submit a batch with hundreds
   of millions of pre-tokenized tokens and the rate limiter would
   record zero. Extract the per-field logic into
   ``_count_prompt_or_input_tokens`` and count each int as one token.

2. P2 access-denial logs were indistinguishable from I/O failures.
   ``count_input_file_usage`` caught every exception under a generic
   "Error counting input file usage" message, so an intentional 403
   from ``_enforce_batch_file_model_access`` looked the same in the
   logs as a missing file or a Prisma timeout. Catch ``HTTPException``
   separately and log 403s at WARNING level with a security-relevant
   message before re-raising.

Tests cover the new shapes: single ``list[int]``, ``list[list[int]]``
(the worst-case bypass vector), and embeddings ``input`` with
pre-tokenized arrays.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:36:12 -07:00
shivam
1f3d9a32f9
Fix policy registry teardown in tests
Reset the policy ID index during policy engine test cleanup so stale policy versions cannot leak between tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-01 17:25:38 -07:00
yuneng-jiang
aa2cace304
Merge pull request #27026 from BerriAI/litellm_/flamboyant-mendeleev-481c14
[Fix] Proxy/Key Management: Honor team_member_permissions /key/list In /key/list Endpoint
2026-05-01 17:20:57 -07:00
shivam
536a24c5ca
Fix runtime policy attachment initialization
Mark runtime-created policies and attachments initialized so global policy attachments created from the policy builder apply immediately without requiring a restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-01 17:11:57 -07:00
yuneng-jiang
12c9a477f1
Merge pull request #27025 from BerriAI/litellm_fix-redis-key-generation-d88e
Fix Redis key generation to be stable across working directories
2026-05-01 16:41:12 -07:00
Yuneng Jiang
b8cf48a102
[Fix] Proxy/Key Management: Honor team_member_permissions /key/list In /key/list Endpoint
When a team grants /key/list via team_member_permissions, non-admin members
should see all keys for that team — same as a team admin. Previously the
classification in list_keys() only checked admin status, so permitted
members fell into the service-account-only path and could not see other
members' personal keys. Routes those members into the full-visibility set.
2026-05-01 16:37:22 -07:00
yuneng-jiang
8ed6c0cdea
Merge pull request #26846 from BerriAI/litellm_/pensive-bartik-e24048
[Fix] RBAC: Restore Admin Viewer Read Parity for Logs + Settings Pages
2026-05-01 16:36:36 -07:00
yuneng-jiang
57dd3891fb
Merge pull request #27024 from BerriAI/litellm_yj_may1
[Infra] Merge dev branch
2026-05-01 16:36:24 -07:00
Claude
6be7b77513
fix(tests): anchor VCR redis cassette key to repo root
`os.path.relpath` with no `start` arg uses the current working
directory, so running pytest from a subdirectory produced a
different Redis key than running from the repo root. CI-recorded
cassettes and locally-replayed runs would silently miss each
other's cache.

Anchor the path to the repo root (derived from `__file__`) so the
key is stable regardless of CWD.

https://claude.ai/code/session_018uCx7pcrkdUJZrCVMaTdPx
2026-05-01 23:27:18 +00:00
Yuneng Jiang
6499fa76de
[Fix] RBAC: Drop management_routes Write Fallback for Admin Viewer
Greptile P1: the unsafe-method branch of `_check_proxy_admin_viewer_access`
ended with a blanket `if route in management_routes: return`. That set is a
mix of reads (info/list — handled via the safe-method GET branch above) and
writes. The fallback let Admin Viewer POST to write endpoints not enumerated
in `_ADMIN_VIEWER_BLOCKED_WRITE_ROUTES`, including:
  - /team/block, /team/unblock, /team/permissions_update
  - /jwt/key/mapping/{new,update,delete}
  - /key/bulk_update
  - /key/{key_id}/reset_spend

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

Tests:
  - 8 new parametrized cases pinning each previously-leaking management write
    endpoint to 403 on POST for PROXY_ADMIN_VIEW_ONLY.
2026-05-01 16:15:21 -07:00
Mateo Wang
628ce9d5ef
Merge pull request #25856 from BerriAI/litellm_clean_litellm_oss_staging_04_01_2026
Litellm clean litellm oss staging 04 01 2026
2026-05-01 16:10:15 -07:00
yuneng-jiang
fba38d0e94
Merge pull request #26929 from BerriAI/litellm_fix_service_account_user_id_bypass
fix(proxy): reject user_id=None on non-admin analytics endpoints (cross-tenant disclosure)
2026-05-01 16:08:35 -07:00
Yuneng Jiang
c78144ccf0
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/pensive-bartik-e24048
# Conflicts:
#	ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx
2026-05-01 16:04:09 -07:00
Yuneng Jiang
a12b4249bd
[Fix] Proxy: Skip Personal Budget Hook When Reservation Covers Counter
The reservation path (PR #26845) atomically pre-fills `spend:user:{user_id}`
and admits at the strict-`<` boundary. The legacy `_PROXY_MaxBudgetLimiter`
pre-call hook re-reads the same counter with `>=`, so a reservation that
fills the counter to exactly `max_budget` (e.g. a request without a
`max_tokens` cap that falls back to reserving the smallest remaining
headroom) is rejected by the hook even though the reservation already
admitted it.

Skip the hook when the request's active `budget_reservation` covers
`spend:user:{user_id}`. The reservation is the source of truth for that
counter cross-pod; the legacy `>=` path remains in place for requests
without a reservation (e.g. paths that bypass the reservation entirely).

Reproduces as `tests/otel_tests/test_prometheus.py::test_user_budget_metrics`
on a fresh user with `max_budget=10` calling `fake-openai-endpoint` without
`max_tokens`. Adds focused unit coverage in
`tests/test_litellm/proxy/hooks/test_max_budget_limiter.py`.
2026-05-01 15:57:42 -07:00
mateo-berri
04e96a9bdc Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_clean_litellm_oss_staging_04_01_2026 2026-05-01 15:54:10 -07:00
Mateo Wang
6458fe15fd
Merge pull request #26077 from BerriAI/litellm_vertex_cached_content_mutual_exclusion
fix(vertex_ai): omit system_instruction/tools/toolConfig when cachedContent set
2026-05-01 15:52:47 -07:00
ryan-crabbe-berri
4503dd18bb
Merge pull request #27012 from BerriAI/litellm_fix-post-call-guardrail
fix(guardrails): post-call guardrail must only fire once
2026-05-01 15:51:55 -07:00
Mateo Wang
0b87bdff3d
Merge pull request #26976 from BerriAI/litellm_default_embedding_encoding_format
feat(embedding): default OpenAI-path encoding_format to float
2026-05-01 15:37:59 -07:00
user
8ee599aa7d
fix(mcp): use canonical proxy_logging_obj, deny when MCP server is unresolvable
Greptile flagged two follow-ups on the OpenAPI/local-registry pre-call
check:

1. **P1 runtime crash via None proxy_logging_obj.**
   `kwargs.get("proxy_logging_obj")` is `None` on the MCP entry path,
   and `pre_call_tool_check` calls `proxy_logging_obj._create_mcp_request_object_from_kwargs`
   unconditionally after the security checks, which would have crashed
   every legitimate call with `AttributeError`. Source the logging
   object from `litellm.proxy.proxy_server` the same way
   `_handle_managed_mcp_tool` already does.

2. **P2 authorization-bypass window when mcp_server is None.**
   Previously the new check was guarded by `if mcp_server is not None`,
   so any local tool whose registry entry had no resolvable server (a
   startup-race window before `_initialize_tool_name_to_mcp_server_name_mapping`
   completes, or an orphaned registry entry) ran without the security
   check. Tools registered via openapi_to_mcp_generator are always tied
   to a server, so a missing one is a configuration/timing fault — fail
   the call with 503 instead of dispatching unguarded.

Tests: existing two pass with an added assertion that
`proxy_logging_obj` is non-None at the call site, plus a new test that
covers the 503 deny branch when the tool→server mapping is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:28:46 +00:00
Yuneng Jiang
8eca93470c
[Fix] Tests: Align Bedrock count-tokens endpoint assertions with URL-encoded model id
The endpoint builder in BedrockCountTokensConfig.get_bedrock_count_tokens_endpoint
percent-encodes the model id as a single path segment (d4dd865b1a, path-traversal
hardening). Update the four endpoint-URL assertions in TestBedrockCountTokensEndpoint
to expect `amazon.nova-lite-v1%3A0` instead of the literal `:0`, matching production
behavior already covered by test_count_tokens_endpoint_encodes_model_id.
2026-05-01 15:22:40 -07:00
yuneng-jiang
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-jiang
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
user
5daf0168a8
fix(mcp): run pre_call_tool_check on OpenAPI/local-registry path (VERIA-7)
`execute_mcp_tool` dispatches in two ways: managed MCP servers go
through `_handle_managed_mcp_tool`, which calls
`MCPServerManager.pre_call_tool_check` to enforce allowed/banned tool
lists, key/team `object_permission` tool grants, and parameter
validation. OpenAPI-backed tools, however, were resolved via
`global_mcp_tool_registry` and dispatched directly to
`_handle_local_mcp_tool` — entirely skipping `pre_call_tool_check`.

A caller could invoke any registered OpenAPI tool regardless of their
key/team permissions, including administrative or destructive
operations on the upstream API.

Run `pre_call_tool_check` before the local-registry dispatch whenever
the resolved server is set (the same condition used to surface server
context to the managed path). Honor any guardrail-modified arguments
the hook returns. Errors raised by the hook propagate up before
`_handle_local_mcp_tool` runs.

Tests cover both directions: the pre-call hook fires when the local
tool resolves alongside a server, and a hook-raised HTTPException
prevents the local handler from being invoked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:02:47 +00:00
yuneng-jiang
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-jiang
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
yuneng-jiang
eca6feb6a6
Merge branch 'litellm_yj_may1' into codex/integration-host-credential-guard 2026-05-01 14:42:23 -07:00
Mateo Wang
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-jiang
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
yuneng-jiang
c2cea58567
Merge branch 'litellm_yj_may1' into codex/budget-race-enforcement 2026-05-01 14:32:18 -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
a1fd150b98
style: black format test_deferred_guardrail_logging.py
Strip a trailing-whitespace line introduced by PR #26109. Black-only
change, no behavior impact — unblocks the lint check on this branch.
2026-05-01 14:28:20 -07:00
ryan-crabbe-berri
5e96120553
Merge pull request #26109 from mubashir1osmani/kaboom
fix: post call guardrail must be called once
2026-05-01 14:27:08 -07:00
yuneng-jiang
df17ffc386
Merge pull request #26930 from stuxf/codex/vector-store-tenant-guard
chore(vector stores): tighten managed store access
2026-05-01 14:25:51 -07:00
Yuneng Jiang
4825d94a9d
[Fix] Tests: Move Misplaced Import in Lazy OpenAPI Snapshot Test
The GitHub merge conflict resolver concatenated both test sets but left
`from litellm.proxy._lazy_openapi_snapshot import _normalize_operation_ids`
stranded between functions instead of at the top of the file.
2026-05-01 14:24:53 -07:00
yuneng-jiang
38fae659b5
Merge branch 'litellm_yj_may1' into codex/vector-store-tenant-guard 2026-05-01 14:24:48 -07:00
ryan-crabbe-berri
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-jiang
9e501edece
Merge branch 'litellm_yj_may1' into codex/file-endpoint-model-auth 2026-05-01 14:22:18 -07:00
yuneng-jiang
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-jiang
f34a2752f6
Merge pull request #26996 from stuxf/chore/ssrf-polling-and-nested-config
chore(security): close two unaddressed SSRF cases
2026-05-01 14:16:39 -07:00
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