Adds three new UISettings flags gated by check_org_admin_feature_access:
- disable_key_generate_for_org_admin
- disable_team_create_for_org_admin
- disable_model_add_for_org_admin
When enabled by a proxy admin, users with the ORG_ADMIN role receive a 403
on /key/generate, /team/new, and /model/new respectively. All other roles
(proxy admin, internal user, team) are unaffected and continue through
their existing auth checks. Flags are persisted to litellm_uisettings and
synced into general_settings via _RUNTIME_GENERAL_SETTINGS_FLAGS so the
enforcement helper can read them at request time.
* fix(proxy): honor object_permission for managed vector store access
* perf(proxy): preload team object_permission on UserAPIKeyAuth
Populate team_object_permission during virtual-key and JWT auth when the
team is loaded, so can_user_access_vector_store uses it in memory first
and only falls back to get_object_permission by id when missing.
Made-with: Cursor
* fix(team_endpoints): auto-add SSO team members to org for proxy admins
* test: proxy_admin vs team_admin security boundary for team→org move
* screenshots: before/after for team-org SSO fix
* fix(team_endpoints): restore staging security features dropped in SSO commit
Co-Authored-By: Ishaan Jaff <ishaan@berri.ai>
* style: black formatting for team_endpoints
Members tab column reads this field; dropping it from the type in the
previous revert broke the type check without affecting the reverted
render logic.
MCP server CRUD endpoints (/v1/mcp/server*) were bundled with MCP
tool-call / passthrough endpoints under llm_api_routes, so setting
DISABLE_LLM_API_ENDPOINTS=true on admin-only nodes also blocked the
Admin UI from listing, adding, or attaching MCP servers.
Separate mcp_inference_routes (data-plane, gated by
DISABLE_LLM_API_ENDPOINTS) from mcp_management_routes (control-plane,
gated by DISABLE_ADMIN_ENDPOINTS). Keep mcp_routes as a union for
backward compat with allowed_routes=["mcp_routes"] virtual key configs.
Upgrade is_management_route to pattern-aware matching so
/v1/mcp/server/{path:path} resolves for concrete IDs.
The `_test-unit-services-base.yml` reusable workflow attached every job
to the `integration-postgres` GHA environment to read three "secrets":
DATABASE_URL, POSTGRES_USER, POSTGRES_PASSWORD. These are not secrets —
the postgres service container is spawned per-job on localhost and
destroyed with the job, so the user/password are bootstrap values for a
throwaway container and the URL is always `postgresql://…@localhost:…`.
Each environment attachment produces a "temporarily deployed to
integration-postgres" deployment record, which the PR timeline renders
as a message per matrix shard per push. With 14 proxy-db shards that's
~14 notifications per push, drowning the PR conversation.
Changes:
* Hardcode POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB and the derived
DATABASE_URL in `_test-unit-services-base.yml`.
* Delete the `environment: integration-postgres` attachment.
* Delete the `secrets:` declarations on the reusable workflow and on
the two callers (test-unit-proxy-db.yml, test-unit-security.yml).
* The `services:` container still starts a fresh postgres per job;
the connection string now matches what the container boots up with.
Security review: no regression. The environment wasn't gating anything
real — no protection rules configured, no approval gates, and the
branch restriction is already enforced by `on: push: branches: [...]`
on both caller workflows. Zizmor pedantic-mode findings are identical
before and after (same 6 pre-existing findings, zero new ones).
The `integration-postgres` environment and its three "secrets" in repo
settings are now unreferenced and can be deleted from repo admin.
test_db_schema_migration.py has exactly one test, and that test is mostly
waiting on prisma subprocesses (~170s: prisma migrate deploy + prisma
migrate diff). No CPU-bound Python work inside the test body, and only
one test in the file means xdist's parallelism is unused regardless.
Previous run on commit 5df9f397e6: 10.0m wall-clock for the shard, of
which 4:56 was silence between step start and pytest banner — the cost
of 4 xdist workers each cold-starting (pytest plugin load + litellm
import + pytest-cov instrumentation) so that exactly one of them could
pick up the single test.
Switching to workers: 0 takes the serial pytest branch in the base
workflow, which already handles this case correctly (no -n, no --dist).
Single-process startup instead of 4. Expected wall-clock: ~6m.
Temporary MCP OAuth sessions were kept in process-local memory, so on
multi-instance/LB proxy deployments a session created on instance A could
not be found when the follow-up /server/oauth/{server_id}/... request
landed on instance B.
Persist temporary session records to Redis (encrypted with the existing
proxy encryption helpers) as a best-effort L2 cache alongside the current
in-memory L1. Convert get_cached_temporary_mcp_server to async and await
it from the authorize/token/register OAuth endpoints.
Made-with: Cursor
Vertex multi-region endpoints (e.g. us, eu) use the rep host pattern, not
{geo}-aiplatform.googleapis.com. Regional IDs still contain a hyphen.
common_utils.get_vertex_base_url centralizes the rule for SDK/API URL building.
Proxy pass-through duplicates the same branching in a local get_vertex_base_url
(with trailing slashes) to avoid importing from common_utils there; live
WebSocket passthrough uses the same multi-region host logic for wss://.
Tests cover us/eu for the common_utils helper.
Made-with: Cursor
Relative labels ("today", "in 2 days", "on May 12, 2026") mixed three
shapes in one column, breaking scannability. Always render MMM D, YYYY
for consistency and easier at-a-glance comparison across members.
Two changes:
1. workers: 8 -> 4 on every non-serial proxy-db shard. ubuntu-latest is a
4-core runner; -n 8 oversubscribes 2x and workers block each other
during their cold-start imports (pytest-cov instruments every litellm
module per worker). Measured ~441% CPU locally with -n 8 on 8 cores
(i.e. ~55% effective). Matching -n to physical cores should give
~2x faster worker startup, which is where most of the ~9m wall-clock
per shard goes (7+ minutes is plugin load + xdist imports before any
test runs).
2. Revert the -k split on test_proxy_utils.py. It was split into
proxy-utils-a-h / proxy-utils-i-z as a semantic-adjacent hack; merge
back to a single proxy-utils shard. Still uses --dist=worksteal so
xdist can balance the 188 parametrized cases across workers.
Also drops the now-unused `keyword` input from _test-unit-services-base.yml
and its matching matrix field across all proxy-db entries.
Shard count: 14 -> 13 (+ the assert-shard-coverage guard).
Sibling tests were mutating litellm.proxy.proxy_server.master_key and
prisma_client with raw setattr. Values leaked across tests in the same
xdist worker, flipping the auth short-circuit in user_api_key_auth and
causing unrelated tests (e.g. test_ui_view_session_spend_logs_pagination)
to return 401 instead of 200.
Replace raw setattr with monkeypatch in the two offending files and add
an autouse conftest fixture that snapshots/restores the known-leaky
module globals for every proxy test.
Default GHA matrix job names join every matrix field, producing unreadable
check labels like:
'proxy-db (logging-misc, tests/proxy_unit_tests/test_proxy_reject_logging.py
tests/proxy_unit_tests/test_audit_logs_proxy.py ..., 8, loadscope, "", 15)'
Set the job's display name to '${{ matrix.test-group }}' so each check
shows just 'logging-misc', 'proxy-utils-a-h', etc.
Previous run (13.8m total) was bottlenecked by shards with 9-12m wall-clock.
Setup + xdist spawn + coverage teardown is ~3m per shard, so each shard's
pytest runtime must stay under ~4m to fit inside 7m total.
Observed per-shard pytest times (before split):
db-and-spend 9:08 (170s outlier: test_aaaasschema_migration_check)
proxy-server 7:15
logging-and-callbacks 6:45
guardrails-budget-hooks 6:37
proxy-utils 6:23
auth-and-jwt 6:54
Split 6 shards into 12, keeping key-generation and endpoints-and-responses
(already <7m). Adds a `keyword` input to _test-unit-services-base.yml so
test_proxy_utils.py can be split by -k expression (same file, two runners).
New matrix entries:
auth-and-jwt -> auth-checks + jwt-and-keys
proxy-server -> proxy-server-core + proxy-runtime
logging-and-callbacks -> custom-logging + logging-misc
db-and-spend -> schema-migration (isolated 170s test) + db-and-spend
guardrails-budget-hooks-> guardrails-hooks + budgets
proxy-utils -> proxy-utils-a-h + proxy-utils-i-z (-k split)
The -k expression split is verified to cover every one of the 64 test
functions in test_proxy_utils.py exactly once. The assert-shard-coverage
guard still catches any file not in any shard.
The create-branch job in create-release.yml calls the reusable
create-release-branch.yml workflow, which requires contents: write.
The top-level permissions: {} blocks the inherited default, and only
the release job overrode it, so the nested call failed with:
The nested job 'create-branch' is requesting 'contents: write',
but is only allowed 'contents: none'.
Add the permission at the calling job level so the reusable
workflow is granted what it needs.