Commit graph

24 commits

Author SHA1 Message Date
Yassin Kortam
25991fe78a
feat(auth): enforce configurable password policy and SSO-only login (#39381)
Adds a configurable password-strength policy (default: min 12 chars,
upper/lower/number/special, all individually toggleable, floored at 8
so a misconfigured minimum cannot disable the length check, and
unicode-aware so an accented letter cannot satisfy the special-
character requirement) enforced on every path that sets a local
user's password: /user/update, /user/bulk_update, and the invitation
onboarding claim flow.

Adds general_settings.disable_password_login_when_sso_enabled, which
rejects username/password login on /login, /v2/login and /v3/login
(including the UI_USERNAME/UI_PASSWORD admin fallback) once ANY
configured SSO provider is FULLY ready: every companion secret/
endpoint an OAuth provider needs, checked independently per provider
so a stray leftover client id for an unused provider can't mask a
different, fully configured one; and for SAML, the optional
python3-saml runtime being importable, checked without letting a
fully-missing package's ModuleNotFoundError take down password login
itself. SSO becomes the enforced boundary for interactive UI access
without an incomplete, mixed, or half-installed SSO setup locking
every admin out or breaking login outright. Master-key API access is
untouched, and unsetting the setting plus a restart restores password
login as the documented recovery path.
2026-09-02 14:28:13 -07:00
mateo-berri
47d8ce6d10 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_0826
# Conflicts:
#	basedpyright-code-budget.json
#	litellm/llms/soniox/common_utils.py
#	ruff-strict-budget.json
#	type-discipline-budget.json
2026-08-29 03:29:39 -07:00
ryan-crabbe-berri
bc127a0b82 feat(proxy): reject wildcard project models under enforce_project_model_quota
Project auth expands all-proxy-models, * patterns, and access-group names
to many concrete models, but the rate limiter looks quotas up by the exact
requested model name, so a quota keyed on one of those entries is never
applied. Fail loudly with a 400 instead of storing an unenforceable quota
2026-08-27 20:49:22 -07:00
ryan-crabbe-berri
2e06762fc4 Merge branch 'litellm_internal_staging' into feature/enforce-model-rpm-tpm-on-create 2026-08-27 20:49:11 -07:00
mateo-berri
b74e696615 refactor(types): replace Any with real types across 178 backend files
Types provider request and response bodies at their boundaries with
TypedDicts and Protocols instead of dict[str, Any], so the untyped-to-typed
crossing is paid once per boundary rather than once per field read.

Removes 1,700 reportAny/reportExplicitAny errors and 1,953 basedpyright
errors overall, plus 310 ruff strict-rule and 106 LIT-rule violations.
No cast, type: ignore, noqa, or new Any annotations anywhere in the diff.

Ratchets the basedpyright, ruff-strict, and type-discipline budgets to the
new counts so the cleared headroom cannot silently grow back.
2026-08-27 10:14:58 +00:00
mateo-berri
255dad6716 chore(typing): drop 1.3k basedpyright errors across 30 Any hotspot files
Replace Any-typed seams with real types in files carrying the highest
remaining reportAny/reportExplicitAny density after #34745: the proxy
server and its utils, the router, the streaming handler and chunk builder,
litellm_logging, the redis cache, the MCP db/tool-registry/spend-writer
layer, the anthropic pass-through adapters and guardrail translation, the
lasso and presidio guardrail hooks, the azure_ai agents handler, the
management endpoints (keys, users, ui_sso, model access groups, config
override, MCP, projects), the responses MCP handlers, response polling
background streaming, and the containers and vector stores mains

No casts, no type: ignore, no noqa, no new suppressions, and no Any
annotations that were not already at base. Whole-tree basedpyright:
reportAny 14,610 -> 14,009, reportExplicitAny 5,100 -> 4,780, total
144,743 -> 143,471, with no rule increasing repo-wide or in any file.
Budgets ratcheted: basedpyright -1,272 across 48 rules, ruff-strict -85,
type-discipline -37
2026-08-16 03:56:02 +00:00
ansh-agrawal
67c4eb86b1 feat(proxy): add opt-in flag to require rpm/tpm for project models (create + update) 2026-08-11 15:22:56 +05:30
ryan-crabbe-berri
83ab6e08da
fix(proxy): invalidate cached project object on project update and delete (#36028)
* fix(proxy): invalidate cached project object on /project/update and /project/delete

The auth path reads projects cache-first via get_project_object with a 60s
TTL and no freshness check, but no project write endpoint ever evicted the
project_id:{id} cache entry. A project cached before /project/update added a
model allowlist kept an empty models list in cache, so _run_project_checks
skipped can_project_access_model and project-bound keys could call team
models outside the project allowlist until the TTL expired. The same
staleness applied to blocked status and budget fields, and /project/delete
left the deleted project enforceable from cache.

Evict the cache entry after the DB write in update_project and
delete_project via a shared delete_cached_project_object helper, with the
cache key derivation shared with get_project_object.

* fix(proxy): broadcast project cache invalidation to all workers and make eviction best-effort

Single-worker eviction leaves every other worker serving its in-memory copy
of the mutated project until the 60s TTL expires, so a project allowlist
change was still bypassable on multi-worker deployments. Add a coordination
Redis pub/sub channel (litellm_proxy.auth_cache_invalidation): project
eviction publishes the cache key and a per-worker subscriber deletes the
local in-memory entry, with the next auth read refetching from the DB.
Subscriber starts on any deployment with a coordination Redis and falls back
to the TTL when none is configured.

Also wrap the eviction in a best-effort catch: the DB write has already
committed when eviction runs, so a cache backend error must not turn a
successful update into a 500 or abort the remaining ids in /project/delete.

* fix(lint): sort auth cache invalidation import and suppress best-effort shutdown catch

The strict-budget gate flagged the new import block as un-sorted (I001) and
the broad except in stop_auth_cache_invalidation_subscriber (BLE001); the
catch is intentional since a failing stop must not break proxy shutdown, so
it carries a named suppression instead of counting against the budget.
2026-08-07 15:19:00 +00:00
ryan-crabbe-berri
2792887e47
fix(proxy): give proxy_admin_viewer read parity with proxy_admin (#35851)
* fix(proxy): give proxy_admin_viewer read parity with proxy_admin

Route-level checks already default-allow management GETs for the viewer
role, but ~15 handlers compared user_role to PROXY_ADMIN only, dropping
viewers into regular-user scoping (/key/list, /user/info, /model/info,
guardrails, prompts, agents, memory, workflows, MCP catalog, coordination
redis settings, credential migration check, enterprise projects). Swap
those read paths to user_api_key_has_admin_view; write gates unchanged.

The dashboard now presents the viewer session as Admin for all gating
(effectiveSessionRole) so every page fetches with admin visibility, with
userRoleLabel/isViewOnly preserving the account-menu label and the
playground cost guard. The server remains the write authority.

* refactor(agents): remove side-effectful health_check param from GET /v1/agents

Addresses a security review finding on the admin viewer read parity change:
listing agents with health_check=true made the proxy issue a server-side GET
to every agent URL, so a read-scoped caller could trigger request fan-out
beyond their object permissions. The list endpoint is now a pure read for
every role.

Removes the query param, the URL probing helper and its timeouts, the
AgentHealthCheck httpx provider tag, and the dashboard's Health Check
toggle. Requests still passing health_check=true get the full list back
with the param ignored.

* fix(proxy): keep credential encryption check proxy_admin only

The residual scan behind GET /credentials/migrate-encryption/check loads
every model, credential, MCP, team, and verification-token row and runs a
decryption attempt on each stored value. Extending it to proxy_admin_viewer
let a read-only account repeatedly trigger deployment-wide scans, so the
route keeps its original full-admin gate.

* fix(agents): restore health_check, keep list fast path proxy_admin only

Restores the agent health_check feature exactly as before this PR: the
query param, the URL probing helper, the httpx provider tag, and the
dashboard toggle all return, so existing callers keep the filtering
contract. The viewer expansion is instead reverted at its source: the
GET /v1/agents admin fast path stays PROXY_ADMIN only, so a
proxy_admin_viewer goes through the object-permission scoped branch as
before and cannot fan out health checks beyond their allowlist. The
viewer read of a single agent stays viewer-inclusive since it has no
side effects.
2026-08-05 18:33:55 +00:00
mateo-berri
fbfb63c948 chore(typing): clear 2.7k basedpyright Any errors across 15 hotspot files
Replace Any-typed seams with real types in the files carrying the highest
reportAny/reportExplicitAny density: typed Prisma read helpers in the MCP
db layer and verification token repository, TypedDicts for OAuth credential
payloads and aggregated spend rows, a DailySpendRecord protocol for the
daily activity endpoints, and concrete request/response types in the
volcengine, openai evals, azure batches, azure_ai count_tokens, and ocr
transformation modules. Modernize touched annotations to PEP 604/585 forms.

No casts, no type: ignore, no noqa, no new Any annotations, no behavior
changes. Whole-tree basedpyright: reportAny 27,005 -> 24,427,
reportExplicitAny 7,439 -> 7,280, no rule increased anywhere. Budgets
ratcheted: basedpyright -2,869, ruff-strict -1,505, type-discipline -167.
2026-07-26 18:39:26 -07:00
ryan-crabbe-berri
e195532c14
fix(proxy): count only active users toward license seat limit (#31227)
* fix(proxy): count only active users toward license seat limit

SCIM-deactivated users (metadata.scim_active == false) are kept in LiteLLM_UserTable for audit and reactivation, but they were still counted toward the per-user license limit, so deactivating a user never freed a seat. Okta never sends a SCIM DELETE and Entra only hard-deletes well after deactivation, so deactivation has to be what frees the seat

Add UserRepository.count_billable_users(), which counts every row except those where metadata.scim_active is false (absent, null, and true all count), and route the user-create license gate, the free-SSO 5-user cap, and the enterprise /user/available_users display through it. A separate litellm_active_users Prometheus gauge reports the billable count while litellm_total_users keeps its original meaning so existing dashboards are unaffected

* fix(proxy): floor billable user count at zero

count_billable_users() runs two separate count queries (total, then deactivated). Under a burst of deactivations between them, the deactivated count can momentarily exceed the earlier total and produce a negative result, which would flow into is_over_limit as a negative and show a negative seat count in the display and gauge. Clamp the result to zero so a transient race can never yield a nonsensical negative; the value self-corrects on the next call

Addresses Greptile P1 on the PR

* refactor(proxy): count teams via TeamRepository in available_users

* style: ruff format changed files at line-length 120
2026-06-29 18:01:02 -07:00
ryan-crabbe-berri
5a62806fdc
chore(lint): remove PLR0915 too-many-statements ruff rule (#30574)
Drops PLR0915 from ruff's extend-select along with its per-file-ignores,
and strips the now-unused `# noqa: PLR0915` directives across the codebase
(RUF100 would otherwise flag them as unused). The C901 suppression that
shared a directive with PLR0915 in streaming_handler.py is preserved.
2026-06-16 16:52:49 -07:00
yuneng-jiang
e78d87ee00
Merge pull request #27011 from stuxf/fix/project-update-cross-team-hijack
fix(proxy): close project hijacking and key org IDOR (VERIA-55)
2026-05-01 18:01:06 -07:00
user
1b2756811e
fix(proxy): close project hijacking and key org IDOR
Two related authorization gaps in management endpoints:

1. `/project/update` evaluated permission against the team_id supplied in
   the request body. By passing `data.team_id` pointing at a team they
   admin, a caller could hijack any project — `_check_user_permission_for_project`
   was given the attacker's team_object and happily checked admin
   membership against that. Drop the team_object kwarg so the helper
   re-fetches the existing project's team. Also require admin rights on
   the destination team when reassigning a project across teams, so a
   team admin cannot shed projects into another team's namespace.

2. `/key/update` accepted any `organization_id` and only checked that
   the org existed before applying limits. A caller could thereby point
   their key at an arbitrary org. Add `_validate_caller_can_assign_key_org`
   which enforces the same membership rule already applied on the
   `/key/list` filter path (`validate_key_list_check`); proxy admins and
   no-change updates skip the check.

Tests cover both helpers in isolation: existing-team-admin allow,
unrelated-team admin deny, proxy-admin shortcut, org-member allow,
non-member deny, missing user_id deny, no-memberships deny.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:32:38 +00: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
Yuneng Jiang
000ce70127
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_migration_projects
# Conflicts:
#	litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py
#	uv.lock
2026-04-24 12:52:10 -07:00
Yuneng Jiang
d3a1f63af2
[Refactor] Proxy: move projects management to enterprise package
Remove the /project/* management endpoints and the enable_projects_ui
admin-settings flag from the OSS litellm package. Project endpoints now
live under litellm_enterprise and are wired through the existing
enterprise router; OSS builds return 404 for every /project/* route.

The enable_projects_ui UI flag is registered back onto UISettings via a
small extension registry when the enterprise package is imported, so the
admin toggle and downstream key/sidebar gating continue to work in
enterprise builds. On OSS, explicit PATCH attempts with the flag return
403 with a clear enterprise-only message instead of being silently
dropped.

Pydantic request/response types (NewProjectRequest, UpdateProjectRequest,
DeleteProjectRequest, NewProjectResponse) stay in litellm/proxy/_types.py
because management_endpoints/common_utils.py and pydantic-shape tests
import them. LiteLLM_ProjectTable and all FK columns in schema.prisma
are unchanged.
2026-04-13 21:41:12 -07:00
Krish Dholakia
6bb1d77f45
Org level tpm/rpm limits + Team tpm/rpm validation when assigned to org (#15549)
* fix(support-model-specific-tpm/rpm-limits): Allows setting rate limits by tpm/rpm for models by team

* fix(key_management_endpoints.py): enforce guaranteed throughput with key-level model tpm/rpm limits, when team-level tpm/rpm limits are set

* test: add unit testing

* feat(schema.prisma): add metadata to litellm budget table

* feat(proxy/utils.py): add org limits to user api key auth

allows org level tpm/rpm limiting to work

* feat: add org level tpm/rpm limits + inherit org id in key from team

enables org level tpm/rpm limits

* feat: validated working org tpm/rpm limits

* feat: support updating org level, model specific tpm/rpm limits

* fix: working key validation for org level tpm/rpm limits

* fix: working validation for orgs when giving tpm/rpm to teams

* fix(key_management_endpoints.py): fix tpm/rpm limits on orgs

* fix(key_management_endpoints.py): support limits

* refactor: remove duplicate var

* fix: refactor to avoid ruff errors

* fix: fix typign

* fix: fix linting error

* fix: fix testing

* fix(key_management_endpoints.py): document params
2025-10-25 13:40:29 -07:00
Ishaan Jaff
b9ffa98c55
[Feat] Proxy CLI: Create a python method to login using litellm proxy (#14782)
* fix: cli auth with SSO okta

* fix: add LITTELM_CLI_SERVICE_ACCOUNT_NAME

* fix: get_litellm_cli_user_api_key_auth

* use existing_key CLI

* fix: use existing key

* test auth commands

* test_cli_sso_callback_regenerate_vs_create_flow

* feat: add CLI Token Utilities

* fix: get_stored_api_key

* move file

* fix: get_valid_models

* fix config.yaml

* TestCLITokenUtils

* TestGetValidModelsWithCLI

* fix: tie user id to keys created through CLI

* fix: add teams interface to CLI

* add /keys/update to the list client commands

* fix /sso/cli/poll to return the user_id

* fix: working TeamsManagementClient

* fix CLI Login command

* fixes for auth

* Potential fix for code scanning alert no. 3400: Clear-text logging of sensitive information

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* ruff fix

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-09-22 21:28:38 -07:00
Krrish Dholakia
dba78046b9 fix(internal_user_endpoints.py): show remaining users when free SSO users is enabled 2025-08-20 22:26:01 -07:00
Krish Dholakia
ac15ca3014
Teams - Support default key expiry + UI - support enforcing access for members of specific SSO Group (#12023)
* fix(team_endpoints.py): support setting default key expiry

allows admin to set key expiry on all team member keys

makes it easier to setup default team for experimentation

* feat(key_management_endpoints.py): allows admin to set duration for keys created by team members

* feat(team_endpoints.py): support team_member_key_duration on `/team/update`

allows setting max time team member keys are valid for

* fix(team_info.tsx): ui component to update team member key duration

* fix(team_info.tsx): support updating team member key duration, if set

* feat(teams.tsx): add team member key duration param ui component

allow admin to set this on UI

* feat(ui_sso.py): support restricting ui access by sso group

allows controlling who can/can't access the UI

* feat(ssomodals.tsx): add initial commit adding sso group access to admin ui

* feat(proxy_server.py): support reading + writing ui_access_mode from db

allows admin to configure allowed sso groups from UI

* feat(ui_sso.py): support enforcing all teams on sso jwt handler

if ui access mode set via ui, support reading the value and enforcing it

* feat(ui/): ui component for controlling sso access group

allow admin to only allow users within specific sso group to log into UI

* fix(uiaccesscontrolform.tsx): fix field names

* feat(ui_sso.py): return received sso response in the clientside error message - enables easier debugging

* test: add unit tests

* fix: minor fixes
2025-06-24 21:58:07 -07:00
Krrish Dholakia
15eadbeb4d fix(internal_user_endpoints.py): add remaining team usage on license usage api 2025-06-21 13:46:30 -07:00
Krish Dholakia
84999ddef3
UI - Fix remaining users activity if no limit + allow filtering by model access groups (#11730)
* feat(enterprise/): fix remaining users check on license

* fix(usage_indicator.tsx): if no max user set, don't render remaining user info card

only for users with user limits on their license

* fix(leftnav.tsx): only show remaining users to admin

* feat(columns.tsx): don't allow sorting on model access groups

it's a list[str]

* feat(model_dashboard.tsx): add model access group filters
2025-06-14 13:38:58 -07:00
Krish Dholakia
c569056ea8
Show remaining users on UI (#11568)
* docs(deploy.md): move docker recommendation to `main-stable`

* feat(enterprise/internal_user_endpoints.py): expose endpoint for checking available premium users

* feat(usage_indictor.tsx): add new element to help track remaining premium users

* feat(usage_indicator.tsx): show premium user remaining usage

allows users with user caps to know how much is left

* fix(vertex_and_google_ai_studio_gemini.py): bubble up stream is not finished, even if stop reason is given

prevents early completion of stream

Closes https://github.com/BerriAI/litellm/issues/11549

* fix(streaming_handler.py): respect is_finished = False in hidden params

internal logic for preventing ending stream early

* fix(litellm_license.py): add function to check if user is over limit

* fix(internal_user_endpoints.py): add function to check if user is over limit

* refactor: move test

* docs(customer_endpoints.py): document new param
2025-06-09 22:04:45 -07:00