Skip to content

fix(jwt): accept a team alias in x-litellm-team-id - #42445

Merged
mateo-berri merged 2 commits into
mainfrom
litellm_jwt_team_alias
Sep 22, 2026
Merged

mateo-berri merged 2 commits into
mainfrom
litellm_jwt_team_alias

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • x-litellm-team-id on JWT requests only matched a team id
  • Sending a team's alias got a 403 even for a member of that team

How it solves it:

  • The header value is resolved through the existing team alias lookup
  • An alias is accepted only when its team would have been accepted by id
  • A value that already is a team id never costs an alias lookup
  • Denials keep naming the value the caller sent, never the resolved id

User Flow

Before: a JWT caller who belongs to a team selects it by alias and is refused

  1. The proxy admin creates a team with POST http://localhost:4000/team/new and {"team_id": "team-eng", "team_alias": "engineering"}
  2. A developer whose JWT lists team-eng sends POST http://localhost:4000/v1/chat/completions with x-litellm-team-id: team-eng and gets 200
  3. The same developer sends the same request with x-litellm-team-id: engineering and gets 403 Team 'engineering' from x-litellm-team-id header is not in your JWT's allowed teams
  4. The same 403 comes back with fallback_to_db_teams on, worded as is not in your team memberships
  5. Nobody could reach a team they were not in either way; the alias was simply unusable

After: the same request with the alias is served and billed to the same team

  1. The proxy admin creates a team with POST http://localhost:4000/team/new and {"team_id": "team-eng", "team_alias": "engineering"}
  2. A developer whose JWT lists team-eng sends POST http://localhost:4000/v1/chat/completions with x-litellm-team-id: team-eng and gets 200
  3. The same developer sends the same request with x-litellm-team-id: engineering and gets 200
  4. The same holds with fallback_to_db_teams on for a developer whose DB membership includes team-eng
  5. GET http://localhost:4000/spend/logs shows both requests attributed to team_id: team-eng
  6. The alias or id of a team the developer is not in, and any unknown value, still gets the same 403 as before

Relevant issues

Follow-up to #31438

Affected release

Linear ticket

Resolves LIT-7181

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Setup shared by both sides: two proxies per commit, each with 4 uvicorn workers on one Postgres, JWT auth against a real Keycloak realm (JWT_PUBLIC_KEY_URL pointing at its JWKS, team_ids_jwt_field: groups, user_id_jwt_field: sub), real gpt-5.5 calls through OpenAI and real claude-sonnet-5 calls through Anthropic. The claims rig carries the team id in the JWT's groups claim; the fallback rig sets fallback_to_db_teams: true, the JWT carries no team claim and the user is a DB member of the team. Base proxies were built from 25af172b85 on ports 20183 (claims) and 20184 (fallback), head proxies from 1071db1119 on ports 20181 and 20182. Team aud7181-ok-92fa5f has alias aud7181-ok-alias-92fa5f; aud7181-nonmember-alias-92fa5f is the alias of a team the caller is not in; aud7181-dup1-alias-92fa5f is an alias shared by two teams. Every case ran with the same three requests, $JWT being the Keycloak access token and $TEAM the header value named in the case

curl -s -w '\nHTTP %{http_code}' "$BASE/v1/chat/completions" -H "Authorization: Bearer $JWT" -H "x-litellm-team-id: $TEAM" \
  -d '{"model":"gpt-5.5","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'
curl -s -w '\nHTTP %{http_code}' "$BASE/v1/messages" -H "Authorization: Bearer $JWT" -H "x-litellm-team-id: $TEAM" \
  -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'
curl -s -w '\nHTTP %{http_code}' "$BASE/v1/responses" -H "Authorization: Bearer $JWT" -H "x-litellm-team-id: $TEAM" \
  -d '{"model":"gpt-5.5","input":"ping","max_output_tokens":16}'

Spend attribution was read back from Postgres by the call id the proxy stamps on each request

select team_id, status from "LiteLLM_SpendLogs" where metadata->>'litellm_call_id' = '<call id>';

Before (25af172)

Claims mode, header is the team id

  1. BASE=http://127.0.0.1:20183 TEAM=aud7181-ok-92fa5f
  2. /v1/chat/completions: {"id": "chatcmpl-EQqSj9rzB6qftAjHqOFGUSNoqIPPB", "model": "gpt-5.5", "object": "chat.completion", ...} HTTP 200
  3. /v1/messages: {"id": "msg_011CfJBu2aYxeuCeyhov3mvV", "model": "claude-sonnet-5", "type": "message", ...} HTTP 200
  4. /v1/responses: {"id": "resp_ko1DAZzVGvYuS2y7S3WQtMeMnF2t9CRjK...", "object": "response", ...} HTTP 200

Claims mode, header is the team alias, /v1/chat/completions

  1. BASE=http://127.0.0.1:20183 TEAM=aud7181-ok-alias-92fa5f
  2. {"error":{"message":"Team 'aud7181-ok-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. Allowed teams: ['aud7181-ok-92fa5f', ...]","type":"auth_error","param":"None","code":"403"}} HTTP 403

Claims mode, header is the team alias, /v1/messages

  1. Same BASE and TEAM
  2. {"error":{"message":"Team 'aud7181-ok-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403

Claims mode, header is the team alias, /v1/responses

  1. Same BASE and TEAM
  2. {"error":{"message":"Team 'aud7181-ok-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403

DB fallback mode, header is the team alias

  1. BASE=http://127.0.0.1:20184 TEAM=aud7181-ok-alias-92fa5f, user is a DB member of aud7181-ok-92fa5f
  2. /v1/chat/completions: {"error":{"message":"Team 'aud7181-ok-alias-92fa5f' (from x-litellm-team-id header) is not in your team memberships.","type":"auth_error","param":"None","code":"403"}} HTTP 403
  3. /v1/messages and /v1/responses: same 403 body

Alias of a team the caller is not in

  1. TEAM=aud7181-nonmember-alias-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-nonmember-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-nonmember-alias-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Unknown value

  1. TEAM=aud7181-does-not-exist-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-does-not-exist-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-does-not-exist-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Alias shared by two teams

  1. TEAM=aud7181-dup1-alias-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-dup1-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-dup1-alias-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Spend attribution

  1. Call ids of the by-id requests above: 8aef3df8-ad9d-47f8-9143-48f72c037dc1 (claims), 28fe73c8-051c-4960-8c34-af3d495f0978 (fallback)
  2. aud7181-ok-92fa5f|success for each; the alias requests were refused and wrote no spend row

After (1071db1)

Claims mode, header is the team id

  1. BASE=http://127.0.0.1:20181 TEAM=aud7181-ok-92fa5f
  2. /v1/chat/completions: {"id": "chatcmpl-EQqS3ZbP28zx8tqQ0gAuCEtO0UAXZ", "model": "gpt-5.5", "object": "chat.completion", ...} HTTP 200
  3. /v1/messages: {"id": "msg_011CfJBr2iQmEbxzM5ZkHhdT", "model": "claude-sonnet-5", "type": "message", ...} HTTP 200
  4. /v1/responses: {"id": "resp__bSlQu5k_ROBMpTtLIviRRPvtbikWGV...", "object": "response", ...} HTTP 200

Claims mode, header is the team alias, /v1/chat/completions

  1. BASE=http://127.0.0.1:20181 TEAM=aud7181-ok-alias-92fa5f
  2. {"id": "chatcmpl-EQqSNMKgRhlbX2ns7ugtHxwIWG0Me", "model": "gpt-5.5", "object": "chat.completion", ...} HTTP 200

Claims mode, header is the team alias, /v1/messages

  1. Same BASE and TEAM
  2. {"id": "msg_011CfJBsWM6wttw9rPsHDVeY", "model": "claude-sonnet-5", "type": "message", ...} HTTP 200

Claims mode, header is the team alias, /v1/responses

  1. Same BASE and TEAM
  2. {"id": "resp_pcQVZJT6FwbQiFXXjDjzqoEhFSPsYv1DcyQVOJtzVr2B...", "object": "response", ...} HTTP 200

DB fallback mode, header is the team alias

  1. BASE=http://127.0.0.1:20182 TEAM=aud7181-ok-alias-92fa5f, user is a DB member of aud7181-ok-92fa5f
  2. /v1/chat/completions: {"id": "chatcmpl-EQqTTL8Krl4uURcDVMt7gQpjW93r8", "model": "gpt-5.5", "object": "chat.completion", ...} HTTP 200
  3. /v1/messages: {"id": "msg_011CfJBxX49py8keq4CefLhz", ...} HTTP 200, /v1/responses: {"id": "resp_SiVHHJicldJzKVkRvkUBilvNCnyluIRy...", ...} HTTP 200

Alias of a team the caller is not in

  1. TEAM=aud7181-nonmember-alias-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-nonmember-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-nonmember-alias-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Unknown value

  1. TEAM=aud7181-does-not-exist-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-does-not-exist-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-does-not-exist-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Alias shared by two teams

  1. TEAM=aud7181-dup1-alias-92fa5f
  2. Claims rig: {"error":{"message":"Team 'aud7181-dup1-alias-92fa5f' from x-litellm-team-id header is not in your JWT's allowed teams. ..."}} HTTP 403
  3. Fallback rig: {"error":{"message":"Team 'aud7181-dup1-alias-92fa5f' (from x-litellm-team-id header) is not in your team memberships." ...}} HTTP 403

Spend attribution

  1. Call ids of the alias requests above: 1198e013-e94a-4fcc-8074-0e1353805521 (claims chat), 006c9701-6936-4a79-a31a-5b17288f9e6a (claims messages), 21381d3f-13e9-42e5-9dce-7c6b18e945b0 (claims responses), 2a1baf16-77ae-4ac6-b8d3-f80d1fba69fd (fallback chat), plus the by-id request c83f0884-c909-462a-809b-61e242769622
  2. aud7181-ok-92fa5f|success for every one of them: the alias requests are billed to the canonical team id, the same as the by-id request
  3. select count(*) from "LiteLLM_SpendLogs" where team_id like '%-alias-%' returned 0 over the whole run

Full audit run at the tip (1071db1)

The cases above are the curl slice of a larger run on the same four proxies. The happy matrix drove all three endpoints, streaming and not, through the OpenAI and Anthropic SDKs (sync and async) and raw curl, in both modes, with the id and the alias: 138 rows, every alias row 200 on head and 403 on base, every successful row correlated to exactly one spend row under aud7181-ok-92fa5f (112/112). The sad and edge cells covered the blocked team's alias, a team without the model (403 team_model_access_denied by alias and by id alike), an exhausted budget by alias (422 like the id header), upper-cased and 8 KB values, an alias renamed mid-run, and a route the team may not call. A 40-request mixed burst at the tip landed 40/40 spend rows under the canonical id. Chaos on the head rig: pausing Postgres mid-burst turned /health/readiness into 503 db: disconnected and it recovered to 200 on unpause, killing a worker got it replaced by the supervisor, and a clean restart served the alias again on the first request. The Admin UI logs and teams pages at the tip show the alias requests under the canonical team with non-zero spend. Not covered: the proxy-admin path of the same header, which is unchanged and still takes an id only, and JWTs that carry a single team_id claim instead of groups

CI on the tip

Every required check passes on 1071db1119. The non-required code-quality job fails in the recursive-function detector on litellm/proxy/bug_report_config.py::_render_json, a file this PR does not touch; main fixed that detector allowlist in #42442 about twenty minutes after this PR's last run started, and the same job passes on main's current head 1a714548a4. Re-running the job would reuse the stale merge ref, and a rebase would move the tip and invalidate the Bugbot, live-pr-risk and audit evidence for nothing, so it is left as is

Recording

A no-mocks screen recording at the tip shows the alias request refused on main and served on this branch with real gpt-5.5 traffic, plus the Admin UI logs page attributing both requests to the canonical team id. It was posted to the Slack thread that opened this ticket; no permalink is available to this session

Type

🐛 Bug Fix

Caveats (if any)

Low

  • A header value that is neither an allowed id nor an alias now costs one indexed team_alias read before the 403
  • A 5xx from the alias lookup surfaces as a 5xx rather than being relabelled a 403, matching how every other DB read in auth_builder already fails when the DB is down. A request that would have been served fails the same way on both sides in that state; only a request that was going to be refused anyway changes status. Discussed and settled in the Greptile thread on this PR
  • Docs still describe the header as taking a team id; docs follow-up in litellm-docs
  • The admin x-litellm-team-id path (team upsert for proxy admins) is unchanged and still takes an id only

QA runbook

Environment: a proxy booted with a JWT auth block (JWT_PUBLIC_KEY_URL at the Keycloak realm's JWKS, team_ids_jwt_field: groups, user_id_jwt_field: sub) and the E2E_KEYCLOAK_* variables from tests/e2e/other/idp.py, plus OPENAI_API_KEY for the chat call

  • tests/e2e/other/test_jwt_auth_e2e.py::TestJwtTeamHeader::test_team_header_with_the_team_alias_binds_the_same_team_as_the_team_id - a JWT caller can select their team by alias and the request is attributed to the same team id as when selecting it by id

    • POST /team/new with the master key and {"team_id": "<id>", "team_alias": "<alias>", "models": ["<model>"]}
    • Create a Keycloak user in a group named <id> and fetch an access token for them
    • POST /chat/completions with Authorization: Bearer <jwt> and x-litellm-team-id: <id>, expect 200
    • POST /chat/completions with the same token and x-litellm-team-id: <alias>, expect 200
    • Poll GET /spend/logs?request_id= and expect team_id equal to <id> on both rows
    • Sanity check: this test makes sense to add and is not hand-wavey (e.g., assert actual expected spend instead of just spend > 0) or potentially flaky
  • tests/e2e/other/test_jwt_auth_e2e.py::TestJwtTeamHeader::test_team_header_with_the_alias_of_a_team_the_caller_is_not_in_is_rejected_like_an_unknown_value - the alias of a team the caller is not in is refused with the same 403 shape as a value that names no team at all

    • Reuse the caller from the test above and POST /team/new for a second team with its own alias that the caller is not in
    • POST /chat/completions with x-litellm-team-id: <other alias>, expect 403
    • POST /chat/completions with x-litellm-team-id: <random unknown value>, expect 403
    • Expect both error messages to be identical once the header value is substituted out
    • Sanity check: this test makes sense to add and is not hand-wavey (e.g., assert actual expected spend instead of just spend > 0) or potentially flaky

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

ran /live-pr-risk and found no regressions/backward incompatible risks

Link to Devin session: https://app.devin.ai/sessions/326d1a76c8f840d8ae7a72e594d7bb84
Open in Devin Desktop: https://app.devin.ai/desktop/session/326d1a76c8f840d8ae7a72e594d7bb84?variant=devin


Devin Review


Note

Medium Risk
Changes JWT team selection and 403 behavior on a security-sensitive path, but scope is limited to header resolution with explicit anti-oracle rules and broad test coverage.

Overview
JWT callers can pin team context with x-litellm-team-id using a team alias, not only the canonical team id. get_team_id_from_header is replaced by async resolve_team_from_header, which returns a HeaderTeam (raw header value + resolved team_id) after optional get_team_object_by_alias lookup.

When the value is already an allowed JWT team id, resolution stays id-only (no alias DB read). Under fallback_to_db_teams, the header tries id lookup first and only falls through to alias when the team row is provably missing (TeamNotFoundError), so transient DB errors are not treated as alias misses.

403 responses and route/membership denials name only what the client sent in the header, not the resolved id, so non-member aliases and unknown values stay indistinguishable. Alias lookup 5xx errors propagate instead of being masked as membership denials.

auth_builder wires the resolved team_id for spend/limits while keeping provisional header teams from upserting under DB fallback. E2E and unit tests cover alias binding, denial shape, DB-fallback ordering, and team_allowed_routes with aliases.

Reviewed by Cursor Bugbot for commit 1071db1. Bugbot is set up for automated code reviews on this repo. Configure here.

The header only matched canonical team ids, so a JWT caller selecting one of their teams by its alias got a 403 even though they belonged to it. The header value is now resolved through the existing alias lookup before the JWT allowed-team check and the DB membership fallback, while a value that is already a team id never costs an alias lookup and denials keep naming the value the caller sent

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team September 22, 2026 07:28
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge because no actionable new issue or outstanding blocking finding remains.

Summary

This PR allows JWT-authenticated callers to select an authorized team by alias through x-litellm-team-id while retaining the canonical team ID for authorization, policy enforcement, and spend attribution.

  • Preserves the direct-ID fast path and resolves aliases through existing team lookup helpers.
  • Keeps JWT claims and database membership as the authorization boundary.
  • Preserves route checks and avoids exposing canonical IDs in alias-related denials.
  • Adds unit and end-to-end coverage for authorized aliases, non-member aliases, database fallback, route restrictions, and spend attribution.

Reviews (4) · Last reviewed commit: "fix(jwt): only alias a header team id th..."

greptile-apps[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread litellm/proxy/auth/handle_jwt.py Outdated
@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.11321% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/auth/handle_jwt.py 98.11% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_jwt_team_alias (1071db1) with main (25af172)

Open in CodSpeed

Under fallback_to_db_teams a header value whose team row read fails for any reason other than TeamNotFoundError now keeps the membership denial instead of falling through to the alias lookup, so a degraded read cannot select a different team that carries the value as an alias. Drops the HeaderTeam docstring that only restated its fields

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

greptile-apps[bot]

This comment was marked as resolved.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1071db1. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mateo-berri
mateo-berri merged commit 071cb49 into main Sep 22, 2026
98 of 100 checks passed
@mateo-berri
mateo-berri deleted the litellm_jwt_team_alias branch September 22, 2026 09:22
yuneng-berri added a commit that referenced this pull request Sep 26, 2026
…election to rc/1.103.0 (#43343)

* feat(otel): emit gen_ai.conversation.id from the caller's session id on v2 LLM spans (#42486)

* feat(otel): emit gen_ai.conversation.id from the caller's session id on v2 LLM spans

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep the caller's header session under missing_session_id: generate and read replayed payload session ids

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): drop only the proxy-minted session id so a caller id on the other metadata key survives

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep a replayed session id hidden when it only echoes the payload trace id

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): keep a replayed session id even when the payload trace id fell back to it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(otel): stop reading the replayed payload's session id, the generated marker does not survive replay

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(integration): audit gen_ai.conversation.id on otel v2 spans through a real proxy, sink and postgres

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(integration): keep otel conversation rigs alive for the whole session so shuffled shards do not reboot the proxy per test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): stop the audit rig proxies from probing sibling test peers for model info

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): record accepted OTLP batches in the sink instead of mutating the collector

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(otel): guard the accepted batch deque so snapshots cannot race sink appends

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: mrinal <mrinal@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
(cherry picked from commit a319690)

* fix(jwt): accept a team alias in x-litellm-team-id (#42445)

* fix(jwt): accept a team alias in x-litellm-team-id

The header only matched canonical team ids, so a JWT caller selecting one of their teams by its alias got a 403 even though they belonged to it. The header value is now resolved through the existing alias lookup before the JWT allowed-team check and the DB membership fallback, while a value that is already a team id never costs an alias lookup and denials keep naming the value the caller sent

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): only alias a header team id the database provably lacks

Under fallback_to_db_teams a header value whose team row read fails for any reason other than TeamNotFoundError now keeps the membership denial instead of falling through to the alias lookup, so a degraded read cannot select a different team that carries the value as an alias. Drops the HeaderTeam docstring that only restated its fields

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 071cb49)

* fix(jwt): say x-litellm-team-id matched no team id or alias in the 403 (#42495)

* fix(jwt): say x-litellm-team-id matched no team id or alias in the 403

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): tell the caller when x-litellm-team-id names an alias shared by several teams

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(jwt): deny a shared x-litellm-team-id alias exactly like an unknown value

A distinct 403 for an alias several teams share was raised before the
allowed-teams check, so any JWT could probe which aliases exist. The
alias lookup now treats the duplicate as a miss, and both denials say
the value does not resolve to a team id or a unique team alias, which
is true for unknown, unauthorized and duplicate values alike

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 08639fc)

* fix(jwt): let x-litellm-team-id select DB membership teams when the token also carries a team claim (#43206)

* fix(jwt): let x-litellm-team-id select DB membership teams when the token also carries a team claim

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* docs(jwt): describe header team selection under fallback_to_db_teams

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 7b4fd47)

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mrinal <mrinal@berri.ai>
Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: yassin <yassin@berri.ai>

This branch was successfully deployed

1 active deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants