Commit graph

3525 commits

Author SHA1 Message Date
Krrish Dholakia
454ce5073f
fix(anthropic, mcp): sanitize tool names to match Anthropic's [a-zA-Z0-9_-]{1,128} pattern (#26788)
* fix(anthropic, mcp): sanitize tool names to match Anthropic's `^[a-zA-Z0-9_-]{1,128}$`

Tool names with characters like `/` or `.` (commonly produced by the
OpenAPI -> MCP generator from `operationId`s such as
`actions/download-job-logs-for-workflow-run`) caused Anthropic to reject
requests with `tools.N.custom.name: String should match pattern
'^[a-zA-Z0-9_-]{1,128}$'`.

Two layers of fix:

1. Anthropic transformation: build a per-request forward map (original ->
   sanitized, disambiguated by suffix on collisions) and a reverse map
   (only for names actually rewritten). Forward map is applied to tool
   defs, `tool_choice`, and historical assistant tool_calls in messages.
   Reverse map is threaded through both the non-streaming and streaming
   response paths so callers continue to see their original tool names
   in `tool_use` blocks.

2. OpenAPI -> MCP generator: sanitize `operationId` (and the
   method+path fallback) at registration time so generated MCP tools are
   valid for any strict-name provider, not just Anthropic. The dashboard
   preview endpoint applies the same sanitization for parity.

Includes unit tests covering: collision disambiguation between
`foo_bar` and `foo/bar` in the same request, reverse-map only firing
for actually-rewritten names, message rewrite for historical tool_calls,
streaming chunk_parser reverse-mapping, and sanitization of OpenAPI
operationIds plus the preview endpoint output.

Made-with: Cursor

* fix(anthropic): build tool-name maps in transform_request, not optional_params

The previous patch stashed the per-request forward and reverse tool-name
maps under ``optional_params["_anthropic_tool_name_forward_map"]`` and
``optional_params["_anthropic_tool_name_map"]``. ``optional_params`` is
the dict that becomes the JSON body via ``data = {**optional_params}``,
so those internal keys leaked over the wire and Anthropic 400'd with:

  _anthropic_tool_name_forward_map: Extra inputs are not permitted

Worse, this meant *every* request whose tool list contained any name with
an invalid character (the exact case the patch was meant to fix) regressed
into a confusing meta-error pointing at LiteLLM's internal map instead of
the offending tool.

Fix: move all tool-name sanitization into ``transform_request``, which is
the single chokepoint already shared by ``AnthropicConfig``,
``AmazonAnthropicConfig`` (Bedrock invoke), ``VertexAIAnthropicConfig``,
and ``AzureAnthropicConfig`` (all call ``super().transform_request`` /
``AnthropicConfig.transform_request(self, ...)``). New static helper
``_sanitize_tool_names_in_request`` walks the already-Anthropic-shaped
``optional_params["tools"]`` (only ``type=="custom"`` entries -- hosted
tool names are reserved by Anthropic and must not be touched), builds
the per-request forward/reverse maps, and applies the forward map in
place to ``tools[*].name`` and ``tool_choice.name``. The reverse map is
stashed exclusively on ``litellm_params`` (which is never serialized to
a provider) under ``_anthropic_tool_name_map`` for the response paths
to consume.

Side effect of this restructure: ``map_openai_params`` is now a pure
OpenAI->Anthropic param translator with no side-channel state, which
matches its contract everywhere else in the codebase.

Tests: replaced the now-incorrect "stashes maps in optional_params"
tests with regressions that assert no underscore-prefixed keys appear
in either ``optional_params`` after ``map_openai_params`` or in the
final ``transform_request`` body. Added end-to-end coverage for:
sanitization in ``transform_request``, ``tool_choice`` rewriting,
historical ``tool_calls`` rewriting in messages, and hosted-tool
passthrough.

Made-with: Cursor

* fix(anthropic): always sanitize empty text content blocks

Anthropic 400s on `{"role": "user", "content": ""}` with:
  "messages: text content blocks must be non-empty"

LiteLLM already had `_sanitize_empty_text_content` to rewrite empty text
to a placeholder, but it was gated behind `litellm.modify_params=True`.
With that flag off (default), empty content from upstream agent
frameworks (e.g. pydantic-ai) flowed straight through and tripped the
Anthropic validator.

Fix:
- Always run `_sanitize_empty_text_content` at the top of
  `anthropic_messages_pt`, independent of `modify_params`. There is no
  way to "pass through" an empty text block, so this is non-optional.
  The richer tool-call sanitizations (Cases A/B/D, which actually
  mutate conversation structure) remain gated on `modify_params`.
- Extend `_sanitize_empty_text_content` to also handle list-of-blocks
  content (`[{"type": "text", "text": ""}]`), not just string content.

Adds 3 regression tests covering string content, list-of-blocks
content, and the no-op case (non-empty messages with modify_params off).

Made-with: Cursor

* fix(anthropic): drop dead tool-name forward-map params, fix mypy + caller-mutation

- remove unused `name_forward_map` param from `_map_tool_choice`,
  `_map_tool_helper`, `_map_tools` and the `_apply_anthropic_tool_name_forward`
  helper. Production sanitization runs in `_sanitize_tool_names_in_request`
  at `transform_request`; these params were never threaded through.
- handler.py: use `ANTHROPIC_TOOL_NAME_REVERSE_MAP_KEY` constant instead of
  the hardcoded `"_anthropic_tool_name_map"` string.
- fix mypy `"object" has no attribute "__iter__"` in
  `_rewrite_tool_names_in_messages` by guarding `tool_calls` with
  `isinstance(..., list)`.
- `_sanitize_tool_names_in_request`: build a new tools list with copy-on-
  change entries (and copy `tool_choice` on rewrite) so a caller reusing
  the same tool list/dicts across requests doesn't see its inputs
  permanently rewritten.
- doc-comment `_build_request_tool_name_maps` clarifying it operates on
  OpenAI-format tools (vs `_sanitize_tool_names_in_request` which runs
  on Anthropic-format tools post-`_map_tools`).
- tests: drop 3 tests pinning the now-removed param paths; add coverage
  for tool_calls + None function_call rewrite and caller-dict immutability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(mcp): inherit stored credentials in test/tools/list for edit flow

When editing an existing MCP server, the Tool Configuration preview
calls POST /mcp-rest/test/tools/list with server_id but no credentials
(management API redacts them). The endpoint now calls
_inherit_credentials_from_existing_server() so stored bearer tokens
and OAuth2 M2M credentials are loaded from global_mcp_server_manager
automatically — tools load without re-entering credentials.

New servers (no server_id) and requests with explicit credentials are
unaffected (function is a no-op in both cases).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(mcp): show all tools in edit panel, not just allowed tools

Edit flow was passing externalTools (from GET /tools/list, filtered by
allowed_tools) to MCPToolConfiguration, disabling the internal hook.
Remove the external props so the internal hook fires via
POST /test/tools/list, which returns all tools unfiltered. Combined
with the credential inheritance fix, tools load automatically without
re-entering credentials and all tools are visible for re-configuration.

existingAllowedTools still pre-checks previously allowed tools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix order-dependent collision in _build_anthropic_tool_name_maps

Use a two-pass approach: first pre-register all already-valid tool names
in the 'used' set, then sanitize/disambiguate names that need rewriting.
This ensures valid names always have priority regardless of input order,
preventing duplicate tool names on the wire when e.g. 'foo/bar' appears
before 'foo_bar' in the tool list.

Add regression test for the reversed ordering case.

* Fix OpenAPI tool name collision: disambiguate sanitized names with numeric suffixes

sanitize_openapi_tool_name replaces all invalid chars with '_', but when
two operationIds differ only by sanitized characters (e.g. 'foo/list' and
'foo.list' both become 'foo_list'), the second registration silently
overwrites the first in the tool registry.

Add collision disambiguation in register_tools_from_openapi that appends
_2, _3, ... suffixes when a sanitized name is already taken, mirroring
the existing logic in _build_anthropic_tool_name_maps.

* Fix preview endpoint missing collision disambiguation for tool names

Add used_names tracking and _2/_3 suffix disambiguation to
_preview_openapi_tools, matching the logic in register_tools_from_openapi.
Without this, two operationIds that sanitize to the same string (e.g.
'foo/list' and 'foo.list' both becoming 'foo_list') would show duplicate
names in the preview while registration would disambiguate them.

* Align preview HTTP method order with register_tools_from_openapi

The preview endpoint and register_tools_from_openapi both use
order-dependent collision disambiguation (_2, _3 suffixes). When the
iteration order differs, two operations on the same path with sanitized
names that collide get different suffixes in preview vs registration,
so the dashboard shows names that don't match what actually got
registered.

Also adds a regression test that fails on the swapped order.

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

* Skip duplicate originals in _build_anthropic_tool_name_maps

If the same invalid tool name appeared twice in original_names (e.g.
['foo/bar', 'foo/bar']), the second occurrence overwrote the forward
map entry with a freshly-suffixed name (foo_bar_2), leaving foo_bar
orphaned in 'used' with no reverse mapping. _sanitize_tool_names_in_request
then rewrote both tool entries to foo_bar_2, and Anthropic 400'd on
duplicate tool names.

Skip the rewrite if forward already has the original mapped.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-06 00:00:36 +00:00
Sameer Kankute
b8635bbc7a
feat(realtime): OpenAI Realtime GA support and beta compatibility (#27110)
* feat(realtime): OpenAI Realtime GA support and beta compatibility

- Normalize beta-style session.update to GA for upstream OpenAI; optional GA→beta
  event translation when client sends OpenAI-Beta: realtime=v1
- Default upstream WebSocket without OpenAI-Beta; forward header when client opts in
- Extend OpenAI realtime types for GA event names and conversation item shapes
- Relax LiteLLMRealtimeStreamLoggingObject.results to List[Any] for GA events
- Update proxy client_secrets fallback to omit beta header; dashboard RealtimePlayground
- Add unit tests for remap, translation, and beta header helper

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix results

* fix greptile

* Fix mypy issues

* Remove unused class constants _GA_TEXT_DELTA_TYPES and _GA_AUDIO_DELTA_TYPES

These frozensets were defined as class-level constants in realtime_streaming.py
but never referenced anywhere in the codebase. Removing dead code.

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>

* fix(realtime): use GA-shaped session.update in guardrail injections

The guardrail VAD injection code sent a beta-style session.update with a
flat turn_detection field:

  {"session": {"turn_detection": {"create_response": false}}}

When the upstream OpenAI backend operates in GA mode (no OpenAI-Beta
header forwarded), it requires the nested GA shape:

  {"session": {"type": "realtime", "audio": {"input": {"turn_detection": {"create_response": false}}}}}

The _remap_beta_session_to_ga helper was only applied to client-
originated session.update messages in client_ack_messages. Internally-
generated session.updates (sent via _send_to_backend) in two paths:
  - _handle_raw_backend_message (raw/no provider_config path, line 518)
  - backend_to_client_send_messages provider_config path (line 481)
bypassed the remap, so GA upstreams ignored or rejected them, breaking
audio transcription guardrails for all non-beta clients.

Fix: add _make_disable_auto_response_message() helper that always emits
the correct GA-shaped session.update, and replace both injection sites
with it.

Update existing tests to assert the GA nested shape instead of the old
flat beta shape, and add a new unit test for the helper itself.

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>

* Log realtime session type

* Fix beta realtime session payloads

* Fix realtime audio format remapping edge case

* Fix Azure realtime beta session shape

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-05 16:49:20 -07:00
ryan-crabbe-berri
1b25f853ce
[Fix] Team UI: handle legacy dict shape for metadata.guardrails (#27224)
* [Fix] Team UI: handle legacy dict shape for metadata.guardrails

A team can have metadata.guardrails stored as {"modify_guardrails": bool}
(the permission-flag shape introduced in PR #4810) rather than the
expected string[]. The opt-out logic added in PR #25575 calls .filter()
on this field, which throws TypeError on a dict and crashes the team
detail page.

Add a safeGuardrailsList helper that returns [] when the field is not
an array, and route the three read sites through it.

* [Fix] Team UI: inline Array.isArray guards for guardrails metadata

Replace the safeGuardrailsList helper with inline Array.isArray checks
at each call site, and apply the same guard to opted_out_global_guardrails
for consistency. No known legacy dict rows for opted_out_global_guardrails,
but the unguarded `|| []` pattern is the same shape risk.

Six call sites now defended directly: three for metadata.guardrails
and three for metadata.opted_out_global_guardrails.
2026-05-05 15:40:44 -07:00
yuneng-jiang
fa6a82ab0f
[Fix] UI: Clear Admin Session Cookies Before Establishing Invited User's Session (#27227)
The invite-signup form was writing the new user's token via raw
`document.cookie` at `path=/`, while the rest of the auth surface uses
`storeLoginToken` (which writes at `path=/ui` and mirrors to
sessionStorage). After signup the inviter's `path=/ui` cookie kept
winning path-specificity matching, and sessionStorage still held the
inviter's token, so the dashboard rendered as the inviter rather than
the newly created user.

Treat invite signup as a principal-change boundary — clear prior
session cookies first, then store the new token via the canonical
helper.
2026-05-05 13:57:04 -07:00
yuneng-jiang
be5f217aaf
Merge pull request #26861 from BerriAI/litellm_fix_scim_virtual_key_deactivation
fix(scim): revoke virtual keys when SCIM deprovisions a user
2026-05-04 19:03:55 -07:00
Yuneng Jiang
e35cd5af76
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_yj_may4 2026-05-04 18:22:47 -07:00
Yassin Kortam
a56256e5ee feat: routing groups ui 2026-05-04 18:09:14 -07:00
yuneng-jiang
2aa4301fe7
Merge pull request #27027 from stuxf/fix/mcp-server-url-redact-non-admin
fix(proxy): redact MCP server URL and headers for non-admin viewers (VERIA-8)
2026-05-04 15:05:05 -07:00
user
1dcfc36393 chore(deps): align dashboard node engine 2026-05-04 13:21:03 -07:00
user
e96d850b84 chore(deps): address dependency review notes 2026-05-04 12:09:04 -07:00
user
bfdd786962 chore(deps): refresh dependency locks 2026-05-04 11:36:18 -07:00
Cursor Agent
1ce92da9e8
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_oss_staging_04_25_2026
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-02 06:04:39 +00:00
mateo-berri
456cb495de Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_scim_virtual_key_deactivation 2026-05-01 20:29:19 -07:00
mateo-berri
be4f46683a fix(scim): cascade FK cleanup on user delete and surface block status in UI
SCIM DELETE /Users/{id} previously called litellm_usertable.delete without
clearing rows that FK back to the user, so Postgres rejected the delete with
LiteLLM_InvitationLink_user_id_fkey and the SCIM caller saw a 500. Add a
helper to drop invitation_link, organization_membership, and team_membership
rows before the user delete (mirrors /user/delete in internal_user_endpoints).

Also add a Status column to the Virtual Keys and Internal Users tables so
admins can see at a glance which keys are blocked and which users SCIM has
deactivated. SCIM-blocked keys carry a tooltip explaining the origin.

Pin the dashboard's Node version to 20 via .nvmrc to match CI.
2026-05-01 20:20:39 -07:00
user
4ea32d13c9
fix(proxy): redact MCP server URL and headers for non-admin viewers (VERIA-8)
Many MCP integrations (Zapier, etc.) embed an upstream API key
directly in the server URL, e.g.
``https://actions.zapier.com/mcp/<api-key>/sse``. The list and
single-server endpoints were returning the full URL to any
authenticated user — `_redact_mcp_credentials` only stripped the
explicit ``credentials`` field, and `_sanitize_mcp_server_for_virtual_key`
only ran for restricted virtual keys. Non-admin internal users could
read the dashboard, click the unmask toggle, and exfiltrate the raw
token.

Add `_sanitize_mcp_server_for_non_admin` that runs on top of the
existing credential redaction and clears the credential-bearing
fields:

- ``url`` (the primary leak vector)
- ``spec_path`` (OpenAPI spec URLs that may carry tokens)
- ``static_headers`` / ``extra_headers`` (Authorization)
- ``env`` (arbitrary secrets)
- ``authorization_url`` / ``token_url`` / ``registration_url``

Identity fields (``server_id``, ``alias``, ``mcp_info``, etc.) are
preserved so the UI can still list servers a non-admin's team has
access to.

Apply the new sanitizer in `fetch_all_mcp_servers` and the per-server
fetch path right after the existing virtual-key branch. Update the
existing `test_list_mcp_servers_non_admin_user_filtered` assertions
that previously checked URL visibility.

Frontend defense-in-depth: hide the URL unmask toggle on
`mcp_server_view.tsx` unless the viewer is a proxy admin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:59:38 +00: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
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
d1fc398f44
Merge pull request #26924 from BerriAI/litellm_fix_ui_session_fixation_url_token
fix(ui): remove insecure ?token= URL handler from LoginPage to close session-fixation
2026-05-01 16:06:49 -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
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
ishaan-berri
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
Mathieu St-Vincent
49ec6aba80
feat: add Qohash Nexus guardrail hook (#24927)
* feat: added Qohash Nexus guardrail hook

* fix: ui_friendly_name of Qostodian Nexus

* Update litellm/proxy/guardrails/guardrail_hooks/qohash/qohash.py

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

* Update litellm/proxy/guardrails/guardrail_hooks/qohash/qohash.py

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

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-01 17:26:32 +05:30
yuneng-jiang
ebbe2f49ff
Merge pull request #26826 from BerriAI/litellm_health_status_pagination
Add pagination controls to model health status
2026-04-30 18:45:40 -07:00
michelligabriele
05a89b2c92
fix(ui): remove insecure ?token= URL handler from LoginPage to close session-fixation 2026-04-30 23:39:03 +02:00
Cursor Agent
8111dbe2b1
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_oss_staging_04_25_2026
Some checks failed
Unit Tests: Caching (Redis) / caching-redis (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
# Conflicts:
#	litellm/model_prices_and_context_window_backup.json
#	model_prices_and_context_window.json

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-30 19:37:57 +00:00
yuneng-jiang
a9db887bdd
Merge pull request #26843 from stuxf/codex/fix-onboarding-invite-token
chore(auth): harden invite-link onboarding token flow
2026-04-30 11:56:26 -07:00
Yuneng Jiang
0423f51e2a
[Fix] RBAC: Open Handler-Level Admin Reads + Hide UI Write Buttons for Admin Viewer
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
The default-allow-GET fix in route_checks unblocked the route layer, but a
second class of bug remained: handlers that gate on `user_role !=
PROXY_ADMIN` (or a private `_require_proxy_admin` helper) reject admin
viewer at the handler before the route's HTTP method even matters.

Backend: relax handler role checks on read endpoints to allow
PROXY_ADMIN_VIEW_ONLY (same `_user_has_admin_view` helper used elsewhere).

  - /v1/access_group GET (list) + /v1/access_group/{id} GET — split
    `_require_proxy_admin` into a parallel `_require_admin_view` for the
    two read handlers; writes (POST / PUT / DELETE) keep the strict gate.
  - /cloudzero/settings GET, /vantage/settings GET — read-only views.
  - /config_overrides/hashicorp_vault GET — read-only config view.
  - /team/permissions_list GET — let admin viewer see permissions like
    a Proxy Admin would.
  - /jwt/key/mapping/list, /jwt/key/mapping/info — JWT mapping reads.
  - /v1/mcp/discover, /v1/mcp/openapi-registry — MCP picker views.
  - /schedule/anthropic_beta_headers_reload/status — read-only status.
  - /adaptive_router/state — read-only live snapshot.

UI: hide write buttons that admin viewer should not see (button click
would fail the backend write gate, but the UX expectation is no button).

  - Internal Users: hide "Invite User" button.
  - Access Groups: hide "Create Access Group" + Delete row action.
  - Budgets: hide "+ Create Budget" + Edit/Delete row actions.
  - Prompts: hide "+ Add New Prompt" / "Upload .prompt File"; gate the
    prompt-table Edit/Delete actions on `isProxyAdminRole` (was
    `isAdminRole` which incorrectly included admin viewer).
  - Router Settings → Fallbacks: hide AddFallbacks panel + per-row Test
    + Delete actions.
  - AI Hub: hide "Select Models / Agents / MCP Servers / Skills to Make
    Public" + "Useful Links Management" (writes).

These pages remain VISIBLE for admin viewer (read parity); only the
write entry points are hidden.
2026-04-29 23:52:11 -07:00
Yuneng Jiang
00145f91a8
[Fix] RBAC: Default-Allow GET for Admin Viewer + Models Tab Alignment
Root cause: admin_viewer_routes was an explicit allowlist, so every newly-added
GET endpoint anywhere in the codebase silently 403'd for admin viewer until
someone remembered to add it. We had whacked /spend/logs/ui, /customer/list,
/guardrails/list, /policies/attachments/list, /invitation/info, and several
others in serial — but the next round still surfaced /in_product_nudges,
/health/latest, /credentials, /v1/mcp/network/client-ip, /claude-code/plugins,
/policy/templates. This pattern keeps repeating because the model is wrong.

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

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

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

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

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

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

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

Tests:
- Extend ADMIN_VIEWER_SETTINGS_ROUTES parametrize list to cover all 7 new
  routes (route-checks layer is now the layer production traffic actually
  hits, vs. the dependency-override-bypass that was masking the gap).
2026-04-29 22:58:38 -07:00
Sameer Kankute
6588564a88
Merge pull request #26691 from BerriAI/litellm_team_search_credentials_metadata
feat(proxy): add team-level search provider credentials
2026-04-30 08:35:17 +05:30
Yuneng Jiang
1005fcd592
[Fix] CI/Tooling: Correct min-release-age value in .npmrc files
npm's `min-release-age` config has type `[null, Number]`. The value `3d`
parses to NaN, which propagates into `before = new Date(NaN)` (Invalid
Date). Pacote then calls `.toISOString()` on it and throws
`RangeError: Invalid time value`, breaking every local `npm install`.

Drop the `d` suffix in all six `.npmrc` files. The `<days>` in npm's
type hint is a label, not part of the value.

This is a no-op for CI (`npm ci` ignores this setting per the comment
in the file) but unblocks local `npm install`.
2026-04-29 19:49:27 -07:00
Yuneng Jiang
cfda5e17ac
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/pensive-bartik-e24048
# Conflicts:
#	ui/litellm-dashboard/src/components/leftnav.tsx
2026-04-29 19:25:20 -07:00
Yuneng Jiang
b1ee9c4fc5
fix(rbac): restore admin-viewer read parity for Logs page + settings reads
Admin Viewer (proxy_admin_viewer) was being blocked from endpoints it should
be able to read. Most visibly the UI Logs page rendered empty because every
filter and detail call (/spend/logs/ui, /spend/logs/ui/{id},
/spend/logs/session/ui, /customer/list) was rejected at the route_checks
layer even though the underlying handlers permit admin-viewer.

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

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

Tests:
- 31 parametrized route_checks cases for the Logs + settings endpoints,
  with internal-user negative coverage to ensure the gate isn't widened.
- 9 handler-level integration tests (FastAPI TestClient) verifying
  admin viewer is no longer blocked at the handler layer.
- New leftnav cases asserting Playground hidden / Models + Agents / Logs
  visible to Admin Viewer.
- New roles + credentials test cases for the UI write-gate.
2026-04-29 19:21:41 -07:00
user
20b937dcbe chore(auth): address onboarding review follow-ups 2026-04-29 19:10:30 -07:00
user
8205e248a6 chore(auth): harden invite-link onboarding token flow 2026-04-29 18:55:11 -07:00
shivam
181e99b996
Fix health pagination review issues
Made-with: Cursor
2026-04-29 17:16:32 -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
shivam
0b9d06a509
Fix model e2e result count locator
Made-with: Cursor
2026-04-29 16:25:57 -07:00
shivam
1277cbe454
Add health status pagination controls
Made-with: Cursor
2026-04-29 15:50:55 -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
Sameer Kankute
45c22081ee
Fix ui unit test 2026-04-29 17:47:27 +05:30
Sameer Kankute
4b03cb68a2
feat(proxy): move search tool access to object permissions
Store search tool allowlists only on object permissions, wire auth/management/UI flows to object_permission.search_tools, and remove legacy team-metadata search credential code and tests.

Made-with: Cursor
2026-04-29 12:29:20 +05:30
Sameer Kankute
c4e074f277
feat(proxy): add model-like search tool access control
Treat search tools like models by adding team/key allowed_search_tools controls, enforcing search tool authorization checks, and moving credential ownership to search tool config only to avoid exposing secrets in team metadata.

Made-with: Cursor
2026-04-28 18:53:32 +05:30
Sameer Kankute
2d2f540480
feat(proxy): add team-level search provider credential resolution
Allow search requests to resolve provider credentials from request metadata, team metadata, and default team settings with clear precedence, and expose this flow in proxy docs/UI with regression tests.

Made-with: Cursor
2026-04-28 16:58:03 +05:30
Ryan Crabbe
7f48284dec
test(ui): reset mocks between LoggingSettings tests to prevent bleed-through
vi.clearAllMocks does not reset mockImplementation, so the error-notification
test was inadvertently relying on a deleteField stub set up in earlier tests
and would time out when run in isolation.
2026-04-27 16:28:48 -07:00
Ryan Crabbe
325c74548d
refactor(ui): invalidate proxyConfig query after spend-logs mutations
Previously, useStoreRequestInSpendLogs and useDeleteProxyConfigField
did not refresh the proxyConfig cache on success, so the Logging
Settings form continued to render the pre-save values until React
Query refetched on its own. Wire both hooks to invalidate
proxyConfigKeys on success so any active observer (currently the
Logging Settings page) repulls fresh data.

Export proxyConfigKeys for cross-hook reuse.
2026-04-27 15:48:27 -07:00
Ryan Crabbe
adff1c93d0
refactor(ui): simplify LoggingSettings save flow via React Query callbacks
Switch the spend-logs save flow from mutateAsync + try/catch to
mutate + callbacks. Errors now surface through a single onError path
(no more double toast on failure), and the delete-then-update sequencing
runs through onSettled instead of awaited promises. handleFormSubmit is
no longer async.

Tighten the corresponding test to assert exactly one error toast fires.
2026-04-27 14:27:11 -07:00
Ryan Crabbe
be248627b9
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix-logging-settings-admin-only 2026-04-27 12:12:13 -07:00
ryan-crabbe-berri
d120ddf678
Merge pull request #26002 from BerriAI/litellm_fix-edit-page-tools-fetch-422
fix(ui): use stored-credentials endpoint for tools fetch on MCP edit page
2026-04-27 09:03:16 -07:00