Skip to content

feat(anthropic): workload identity federation, pluggable identity sources, and provider-level setup - #38013

Closed
derhornspieler wants to merge 57 commits into
BerriAI:litellm_internal_stagingfrom
derhornspieler:litellm_anthropic_wif_oidc_28607
Closed

feat(anthropic): workload identity federation, pluggable identity sources, and provider-level setup#38013
derhornspieler wants to merge 57 commits into
BerriAI:litellm_internal_stagingfrom
derhornspieler:litellm_anthropic_wif_oidc_28607

Conversation

@derhornspieler

@derhornspieler derhornspieler commented Aug 23, 2026

Copy link
Copy Markdown

TLDR

Problem this solves:

  • Anthropic-direct auth requires a long-lived static sk-ant key
  • Anthropic shipped Workload Identity Federation, but LiteLLM had no support
  • Vertex and Azure already federate here, Anthropic could not
  • No way to set a provider up once and reuse it

How it solves it:

  • Shared RFC 7523 JWT-bearer token exchange engine, Anthropic is its first consumer
  • Short-lived sk-ant-oat01 tokens minted from an OIDC assertion
  • Four ways to obtain that assertion, including a self-signed issuer and Keycloak
  • Every Anthropic surface uses it: chat, /v1/messages, files, batches, skills, passthrough, token counting and discovery
  • Federation config is server-owned: refused in a request body, pinned to api.anthropic.com unless an operator allowlists a gateway, and only a proxy admin may put a deployment into or alter a federated configuration
  • One provider credential, model discovery, per-model enable/disable and aliases

User Flow

Before: a platform team with a no-static-secrets policy cannot point LiteLLM at an Anthropic federation rule

  1. They open http://localhost:4000/ui/?page=models and pick Add Model, whose only Anthropic credential fields are api_base and api_key
  2. They set the federation variables on the proxy instead, leave api_key unset, and declare model: anthropic/claude-haiku-4-5
  3. They send POST http://localhost:4000/v1/chat/completions with {"model": "claude-haiku-4-5", "messages": [...]}
  4. The proxy answers 401 Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params
  5. Because there is no federation setting to guard, any team admin who can manage a team-scoped deployment can already set every field the deployment does have

After: the same team configures the provider once and every discovered model authenticates without a static key

  1. In the Claude Console they create a federation issuer, a service account, and a rule, which yields an fdrl_..., an org id, and an svac_...
  2. They open http://localhost:4000/ui/?page=models, choose Add Provider, pick Anthropic, and select an auth method other than API key
  3. They fill that method's fields. Secret-bearing fields take a reference such as os.environ/KC_CLIENT_SECRET, never a pasted secret
  4. For the self-signed issuer the wizard shows the JWKS to paste into the Console, without which Anthropic cannot verify anything
  5. They press Discover and the wizard lists the models the credential can actually see, from a live call rather than a static list
  6. They untick the models they do not want, rename any of them, and add alternate names
  7. They press Create. Unticked models are created already disabled, and can be enabled later from the models table
  8. They send the same POST http://localhost:4000/v1/chat/completions and get 200 with the model's reply
  9. Anyone, team admin or proxy admin alike, who puts a federation field straight into a POST http://localhost:4000/model/new body is refused with 401 naming the field, because these fields choose which server secret is read and where it is sent; a team admin cannot reach POST http://localhost:4000/credentials at all (401, proxy admin only), and the proxy admin attaches federation through a named credential or config.yaml instead

Relevant issues

Fixes #28607

Docs: BerriAI/litellm-docs#1000

Linear ticket

Resolves LIT-6107

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
  • 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

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

Federated end to end (contributor's run at 197fd1e, before at fde3075)

Four models on anthropic/, no api_key anywhere, federation ids from the environment, and the identity token projected at a path inside the OIDC file allowlist:

model_list:
  - model_name: claude-haiku-4-5-wif
    litellm_params: {model: anthropic/claude-haiku-4-5}
  - model_name: claude-sonnet-5-wif
    litellm_params: {model: anthropic/claude-sonnet-5}
general_settings: {master_key: os.environ/LITELLM_MASTER_KEY, store_model_in_db: true}
ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID
ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/token
  1. curl -s localhost:4100/v1/chat/completions -d '{"model":"claude-haiku-4-5-wif","messages":[{"role":"user","content":"Reply with exactly: WIF OK"}],"max_tokens":16}'
  2. Before: {"error":{"message":"litellm.AuthenticationError: Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. ..."}}. After: content: WIF OK | model: claude-haiku-4-5-wif | tokens: 20
  3. The same request against /v1/messages with claude-sonnet-5-wif. Before: {"type":"error","error":{"type":"authentication_error","message":"x-api-key header is required"}}. After: content: WIF OK | usage: 19 in / 7 out
  4. curl -s localhost:4100/v1/messages/count_tokens -d '{"model":"claude-haiku-4-5-wif","messages":[{"role":"user","content":"count these tokens please"}]}'. Before: no credential, the count never reaches Anthropic. After: {"input_tokens":11}, Anthropic's own count
  5. curl -s localhost:4100/v1/chat/completions -d '{"model":"claude-haiku-4-5-wif", ..., "anthropic_identity_token":"attacker-supplied"}'. Before: the field is neither refused nor honoured and the call fails for want of a key. After: {"error":{"message":"Authentication Error, Rejected Request: anthropic_identity_token is a server-owned workload identity federation parameter and cannot be set in a request body; configure it on the deployment instead.","type":"auth_error","code":"401"}}
  6. curl -s localhost:4100/public/providers/fields. Before: credential_variants: None. After: credential_variants: ['api_key', 'wif_token', 'wif_token_file', 'wif_internal_issuer', 'wif_keycloak']

The federated 200 legs were not re-run at the tip: the federation rule, service account, and identity token live only in a Claude Console organization the reviewer cannot provision, so the review runs below cover every leg that does not need a live federation rule. The tip changes nothing on the path those legs exercise beyond the three fixes proven below

Review run at f2240df vs base e585aab, carried to 57b9e94 since that commit only touches the dashboard wizard (two proxies, --num_workers 2 each, real Anthropic key on the control models, real spend)

Same config, same 30 requests in the same order on both sides, S01 to S23 in the run log. Identical on both sides: liveliness and readiness, /v1/chat/completions, /v1/messages, /v1/responses, /v1/messages/count_tokens and /anthropic/v1/messages passthrough on a static-key deployment and on an env-key deployment (all 200, pong), key generate, chat, key info spend and delete, the spend log row, /v1/model/info, /v1/models, /ui/, /health. The differences are the feature:

  1. POST /v1/chat/completions with "anthropic_federation_rule_id":"fdrl_x" in the body (S10). Base: 400 AnthropicException - invalid_request_error after the field reached Anthropic as an unknown parameter. Tip: 401 Authentication Error, Rejected Request: anthropic_federation_rule_id is a server-owned workload identity federation parameter and cannot be set in a request body; configure it on the deployment instead.
  2. POST /provider/models/discover with {"custom_llm_provider":"anthropic"} (S13). Base: 404. Tip: 200 with the live model list; the same call carrying anthropic_federation_rule_id (S13b) is refused with the same 401
  3. POST /model/new carrying federation fields inline, as a team admin (S15e) and as the proxy admin (S15h): both 401 naming the field. A team admin on POST /credentials (S15g): 401 Only proxy admin can be used to generate, delete, update info for new keys/users/teams. Route=/credentials. A team admin creating a plain deployment (S15f) still gets 200
  4. GET /public/providers/fields (S12). Base: no credential_variants key. Tip: credential_variants: [api_key, wif_token, wif_token_file, wif_internal_issuer, wif_keycloak] on the anthropic entry, every other provider unchanged
  5. A deployment carrying bogus federation ids while ANTHROPIC_API_KEY is set in the proxy environment (S11) answers 200 pong on both sides: the static key wins over federation, see Caveats

Defects found in review, before at a7ed600 and after at f2240df, the dashboard defect after at 57b9e94 (proxy booted without ANTHROPIC_API_KEY, --num_workers 2, router_settings: disable_cooldowns: true)

Config: claude-wif-keycloak (keycloak, anthropic_keycloak_client_secret_ref: os.environ/KC_SECRET, token url pointing at an unreachable https://127.0.0.1:9/...), claude-wif-tokenfile (token file inside LITELLM_OIDC_ALLOWED_CREDENTIAL_DIRS), claude-wif-issuer (internal_issuer inline, anthropic_issuer_signing_key_ref: os.environ/ISSUER_KEY holding a P-256 PEM), claude-wif-issuer-cred (litellm_credential_name pointing at a credential_list entry mirroring the docs example), federation ids as os.environ/ references, every one a placeholder (fdrl_rigbogus...) since no live rule is available

  1. POST /v1/messages on claude-wif-keycloak (D03). Before: 401 anthropic_identity_source is 'keycloak', but anthropic_issuer_audience, anthropic_issuer_signing_key_ref, anthropic_issuer_subject, anthropic_issuer_ttl_seconds, anthropic_issuer_url belongs to a different identity source, so the endpoint could never federate: /v1/messages hands the resolver every deployment field with the unset ones as None, and the resolver rejected them as foreign. After: the request reaches the identity source, 401 Could not obtain the OIDC identity token (unreadable) from oidc/keycloak/...: could not reach the keycloak token endpoint https://127.0.0.1:9/realms/rig/protocol/openid-connect/token: ConnectError. The same flip on claude-wif-issuer-cred (D09) and on a credential-backed model (D13)
  2. POST /v1/chat/completions on claude-wif-keycloak (D02) and on claude-wif-issuer (D07) and claude-wif-issuer-cred (D08). Before: 401 ... keycloak client secret <withheld: not a secret reference> could not be read and 401 ... internal_issuer signing key <withheld: not a secret reference> could not be read: config.yaml loading expanded os.environ/KC_SECRET into the raw secret, and the identity source, which dereferences that pointer itself at use time, then saw a value that is not a reference. After: D02 reaches Keycloak (ConnectError from the unreachable rig endpoint), D07 and D08 mint and send the assertion and get Anthropic's own answer, 401 The token endpoint returned HTTP 400: error: invalid_request_error - federation_rule_id is not a well-formed fdrl_ tagged ID, which is exactly the placeholder rule id. The token-file model (D04, D05) returns that same Anthropic answer on both sides, and a credential created through POST /credentials (D10 to D13) was never affected because the credentials API never expanded the reference
  3. Async create_file and create_batch validated the provider credential on the event loop before dispatching, so a federated token exchange during an async file upload or batch create blocked every other request on that worker. No curl shows this deterministically; the regression tests in tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py assert the async hook is awaited (and a sync hook runs off the loop thread) and fail on the previous handler
  4. The Add Provider wizard on the models page kept the previous provider's form values after switching providers, so an Anthropic key typed on step two travelled into the OpenAI credential when the operator went back and picked OpenAI. The wizard now resets its form on a real provider change (the credential name is separate state and stays); the integration test in AddProviderPanel.integration.test.tsx types a key, switches provider, and asserts the new credential is created with only the key typed for it, failing on the previous panel with the first key

Observed alongside, none caused by this PR:

  • /v1/messages/count_tokens maps a federation auth failure to 500, pre-existing
  • Two workers: a /model/new model can miss its /credentials credential on the other worker, pre-existing
  • Static or ambient ANTHROPIC_API_KEY silently wins over deployment federation, by design
  • Master key rotation drops the blocked flag on keys, pre-existing
  • Nine-character secrets mask to a single asterisk in error text, pre-existing

Type

New Feature

Caveats (if any)

Low

  • Static api_key or an ambient ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN takes precedence over the deployment's federation fields, silently (S11): the same precedence every provider uses, and the docs PR gets a note; unset the env var on a no-static-secrets proxy
  • The federated 200 legs are the contributor's run at 197fd1e, not re-run at the tip, since the federation rule and identity token exist only in a Claude Console organization; everything reachable without one is proven at the tip
  • Identity token files must sit under LITELLM_OIDC_ALLOWED_CREDENTIAL_DIRS, and the token exchange only talks to api.anthropic.com unless the operator adds hosts to LITELLM_ANTHROPIC_WIF_ALLOWED_HOSTS, so a gateway or test double needs that set
  • A freshly registered inline JWKS takes about a minute to become usable; a data plane 401 does not invalidate the cached token, which self-heals; the token cache is per process, so each replica mints its own
  • Federation fields are refused inline on POST /model/new for the proxy admin too, not only for team admins: the sanctioned paths are a named credential (POST /credentials then litellm_credential_name) or config.yaml. Editing a federated deployment stays proxy-admin only, including changing its api_base or attaching a credential that carries federation values
  • The OpenAPI schema now lists CredentialItem-Input and CredentialItem-Output instead of one CredentialItem, because credential_values_to_delete is excluded from serialization. The wire shape is unchanged; a generated client pinned to the old schema name regenerates. Keeping one name would mean splitting the request and response models, more churn than the rename costs
  • A clientside-credential request that overrides api_base gets a different synthesized per-request model id than before, since the federation disable flag now rides those params. Cosmetic, the id is not persisted
  • anthropic_workspace_id is refused in a request body even on a Bedrock deployment, where it is harmless; it stays refused for uniformity
  • The tag management writer that appends a tag to a deployment's params is not behind the federation admin gate; it only touches tags, so it cannot change a federation field
  • /public/agents/fields does not list the federation variants, so the agents page offers API key only; the models page does
  • A non-Anthropic provider reusing the Anthropic config picks up a static key, pre-existing
  • The five older admin panels on the models page are shown to view-only admins by the same role masquerade the new Add Provider tab now guards against; pre-existing and left for a follow-up

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

@CLAassistant

CLAassistant commented Aug 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment thread litellm/types/router.py
@veria-ai

veria-ai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 7 · PR risk: 0/10

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 030b0b3f19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/llms/base_llm/auth/token_exchange.py
Comment thread litellm/llms/base_llm/auth/client_credentials.py Outdated
Comment thread ui/litellm-dashboard/src/components/networking.tsx
@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds Anthropic workload identity federation, reusable provider credentials, live model discovery, and provider-level setup across the SDK, proxy, and dashboard

  • Adds shared JWT bearer exchange, identity-source, signing, caching, retry, and redaction infrastructure
  • Integrates federated authentication across Anthropic chat, Messages, files, batches, skills, passthrough, token counting, and discovery
  • Adds proxy authorization and server-owned configuration controls for federation settings
  • Adds an Anthropic provider wizard with credential variants, model selection, aliases, and enablement controls

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains from the previously reported issues

No blocking failure remains

Important Files Changed

Filename Overview
litellm/llms/base_llm/auth/token_exchange.py Adds the shared token-exchange engine and correctly addresses the previously reported retry and credential-reflection cases
litellm/llms/anthropic/wif.py Connects Anthropic federation configuration and identity sources to the shared exchange engine
litellm/llms/anthropic/batches/handler.py Propagates federation parameters and offloads synchronous credential validation for asynchronous batch retrieval
litellm/llms/anthropic/common_utils.py Extends Anthropic authentication resolution and header handling for federated credentials
litellm/proxy/management_endpoints/model_management_endpoints.py Adds provider discovery and administrative controls for federated model configuration
ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/panels/add-provider/AddProviderPanel.tsx Adds the provider-level Anthropic setup and discovery workflow

Reviews (22): Last reviewed commit: "fix(proxy): keep WIF secret pointers unr..." | Re-trigger Greptile

Comment thread litellm/llms/base_llm/auth/token_exchange.py
@codspeed-hq

codspeed-hq Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing derhornspieler:litellm_anthropic_wif_oidc_28607 (57b9e94) with litellm_internal_staging (5c034fd)

Open in CodSpeed

…oken exchange engine

Adds a provider-agnostic JWT-bearer token exchange engine (litellm/llms/base_llm/auth/)
with two-tier refresh, single-flight minting, negative caching, response caps, and
RFC 6749 error redaction, plus the Anthropic WIF adapter and wiring: env and
litellm_params config, sync and async facades, and beta-header merging fixes on the
skills, files, batches, messages, and passthrough surfaces

WIF is the lowest credential tier, so a deployment that does not configure it behaves
exactly as before. Verified end to end against the live token endpoint from a container:
both /chat/completions and /v1/messages complete over a minted sk-ant-oat01 token with no
api_key configured anywhere

Two protocol details were confirmed against the live endpoint rather than the docs. The
exchange needs no anthropic-beta header, so none is sent. service_account_id mints fine
when omitted for a single-service-account rule, so it stays optional

Resolves BerriAI#28607
…nthropic providers

The federation exchange derives its token endpoint from the deployment's api_base and returns
an Anthropic-org credential, so it must only ever run for Anthropic itself. It did not.
VertexAIAnthropicConfig inherits AnthropicModelInfo.validate_environment, and the MiniMax and
Tencent /v1/messages configs inherit the Anthropic validate step, so a proxy configured with the
ANTHROPIC_* federation variables POSTed the workload's OIDC assertion to those providers' own
hosts, confirmed against a real deployment base

Eligibility is now declared per class and read from that class's own __dict__, so a subclass
written for another provider inherits nothing, and the get_auth_header facades refuse to mint
unless a caller states that it authenticates against Anthropic's own API. The six federation
fields join the banned request-body parameters: they select which server-side secret is read and,
with api_base, where it is sent, so a caller-supplied value was an exfiltration primitive for any
environment variable or mounted token file. The exchange client no longer follows redirects, since
only the initial token URL is validated, and a base that already ends in /v1 no longer yields a
doubled /v1/v1/oauth/token
…olled request paths

An independent review of the previous commit found the narrowing incomplete. The federation
fields select which server-side secret is read, and together with api_base decide where it is
sent, so they are deployment decisions on every surface:

- They are rejected from any request body unconditionally, ahead of the general banned-parameter
  check, because both client-side credential opt-ins would otherwise re-enable them. The inert
  workspace id keeps its existing behaviour, since Bedrock Claude Platform already accepts that
  spelling in a request body.
- /health/test_connection takes a litellm_params object that never reached the request-body check,
  and its existing guard only covers os.environ references, not oidc ones. It rejects them now.
- A client-redirected api_base clears the federation fields and marks the deployment, so a token is
  not minted for a caller-chosen host. The mark is what stops the environment-configured path,
  which cannot be cleared out of a dictionary

Batch retrieval now threads litellm_params, so it authenticates the same ways every other Anthropic
surface does rather than failing ahead of the transformation that resolves them

The exchange engine always publishes a result for its single-flight entry, so an unexpected failure
can no longer leave every later caller waiting on a leader that never finishes. Error bodies are
rendered from structured fields only, and a body that echoes the submitted assertion is dropped
rather than logged and returned. Endpoint normalization now works on the URL path, so a
single-label host is left alone and a pathological base cannot exhaust the stack
…eration

The workload assertion could only come from a mounted file or an environment variable, which assumes
a platform that already projects one. Two more sources sit behind an explicit
anthropic_identity_source discriminator, and its absence keeps today's resolver exactly as it was:

- internal_issuer signs a short-lived ES256 assertion with an operator-supplied key, resolved through
  the usual secret reference so it can live in a secret manager. Its JWKS is exported for the operator
  to register with Anthropic, without which the source cannot be used at all.
- keycloak fetches the assertion from a client_credentials grant, with the client secret likewise held
  behind a reference rather than in the configuration

The engine gains an optional assertion source on the spec, so a source that needs more than a string
can supply one without the reference ever carrying a secret: it stays a hash of the non-secret fields
and remains what the cache keys on and what errors name. Failures carry a redacted detail, so a
Keycloak hop is diagnosable rather than collapsing into one opaque message

The server-owned field list is now derived from one definition, so every field added here is rejected
from request bodies and cleared on a client base override without a second edit. Credential params
carry the federation fields too, which is what the files, batches, and passthrough surfaces read

Verified against the live token endpoint: an ES256 assertion from internal_issuer, with its exported
JWKS registered on the federation issuer, mints a token. Note that a freshly registered inline JWKS
takes up to about a minute to become usable
…eration

Federation was configurable but only per model, and only by hand. This adds the provider-level flow
the feature was missing, reusing what already exists rather than introducing parallel concepts

Provider metadata gains optional credential variants: a selector plus per-variant field lists, so a
provider whose credential shape branches describes that in the same metadata every provider already
publishes, and the existing generic renderer drives it with no provider-specific branch. Anthropic
publishes five, one per way of authenticating. Fields holding a secret reference render as text, not
as password inputs that would invite pasting the secret itself

Model discovery could not see a credential configured through litellm_params, only through the
environment, so a federated provider had nothing to discover. Providers now expose discovery that
receives the deployment params, and an admin-only endpoint discovers a provider's models from a
stored credential by name. The credential fields are never accepted in that request, since choosing
which server-side secret is read is not a caller's decision

Enable and disable reuse the existing blocked flag rather than a second notion of the same thing,
which meant letting model creation persist it so a model can be created already disabled instead of
being created live and then paused. Alternate names reuse the existing public name and group alias.
One credential now feeds many models, so switching a credential's auth variant has to be able to
remove the previous variant's fields, which credential updates could not express before

The Admin UI gains an Add Provider flow over all of it: pick the provider and auth method, fill that
method's fields, export the JWKS when the identity is self-signed, discover the provider's models,
then enable, rename, and alias them before creating. Two pre-existing UI defects surfaced while
building it: a variant-capable provider could render through the stale flat field list before its
metadata loaded and leave a validation rule behind that blocked submission, and the credential
editor left the previous variant's fields in place when the variant changed
…ject

/model/block and /model/unblock declare the proxy model table as their response model, and FastAPI
validates the returned row against it with from_attributes. The before-validator that parses the
JSON string columns assumed a mapping, so it called .get on the row object and both endpoints
answered 500 even though the block itself had already been applied

The validator now passes an object through untouched and keeps parsing the string columns it exists
for, which is the case the database actually produces. Found while exercising the provider setup's
enable and disable toggle against a live proxy
…types

The discovery and JWKS routes were missing from the generated types. Regenerating on a developer
machine can drop the enterprise routes when that package is not importable, which turns the sync
check into a large deletion; this spec was dumped from the built container image, so the diff is
only the two added endpoints
…-encode Keycloak credentials

The retry path re-read the assertion from its source to decide whether an error body
reflected it back. A rotating source (a token file rewritten between attempts, a
Keycloak fetch that mints a fresh token) hands back a different value, so the assertion
the failing request carried stayed in the error text. _Unauthorized now carries the
assertion the attempt used and the redaction runs against that, which also drops one
extra read of the secret per failure

Separately, RFC 6749 2.3.1 wants the client id and secret form-urlencoded before the
base64, so a reserved character in either one no longer corrupts what the far side
decodes back out of Basic auth
…g the event loop

The async file and batch handlers called the provider's sync validate_environment
directly. For the workload identity tier that hook performs a blocking token exchange,
so a mint stalled the whole loop. They now go through one facade that awaits the
provider's async hook when it has one and offloads the sync hook to a worker thread
otherwise, so every other surface keeps its existing behaviour

Batch retrieval also copies the litellm_params it hands down rather than sharing the
caller's dict, and get_anthropic_headers moves its credential selection into a helper
so the four auth tiers read as four branches instead of one nested chain
… fields

These fields pick which server-side secret is read and where the resulting assertion is
sent, so a team admin who can otherwise manage a team-scoped deployment must not be able
to set them. The create, update and patch model paths and both credential write paths now
reject them for anyone below proxy admin, mirroring the existing blocked-flag gate. The
field list is derived from anthropic_wif_litellm_params rather than copied, so a new
federation field is covered the day it is added

Also fixes two things CI caught: credential_values_to_delete is a PATCH instruction rather
than part of the credential, so it stays out of dumps that feed config loading and the
Prisma write, and the provider discovery route is registered in the backend allowlist
…es for failed rows

The tab list put add-provider ahead of auto-routers, which reordered the tabs an
existing test pins. It now sits after auto-routers, where the admin-only tabs start

The wizard also wrote a model_group_alias entry for every discovered row it tried,
including rows whose model creation had just failed, leaving an alias pointing at a
deployment that does not exist. Alias additions are now taken from the rows that
actually got created
Both posters built their HTTPHandler inline, so nothing exercised the guarantees that
matter there: redirects stay disabled, an HTTPStatusError comes back as its response
rather than escaping, and a None response raises instead of being dereferenced. Each
now takes a handler factory that defaults to the real one, which is the same injection
seam the rest of this package already uses for its poster and secret reader

Coverage for litellm/llms/base_llm/auth goes from 90% to 97%: client_credentials from
79% to 98%, with only its assert_never arm left, and token_exchange from 90% to 96%
…l schema

Excluding credential_values_to_delete from dumps makes pydantic emit separate input and
output schemas for CredentialItem, since the field is accepted on a write and absent from
every read. The generated types now say so: the credential read routes return the output
shape, which no longer carries the PATCH-only field, and only the write route accepts it
@derhornspieler
derhornspieler force-pushed the litellm_anthropic_wif_oidc_28607 branch from d965781 to ad03777 Compare August 23, 2026 17:34
Comment thread litellm/proxy/credential_endpoints/endpoints.py
@derhornspieler

Copy link
Copy Markdown
Author

@greptileai Pushed fixes for the retry redaction and the blocking files and batches paths. Please re-review at ad03777

@derhornspieler

Copy link
Copy Markdown
Author

@codex review Both P1 findings are fixed: the retried assertion is now redacted, and Keycloak client credentials are form-encoded

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad03777789

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/proxy/credential_endpoints/endpoints.py Outdated
Comment thread litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py Outdated
Comment thread litellm/llms/base_llm/auth/token_exchange.py Outdated
… litellm_anthropic_wif_oidc_28607

# Conflicts:
#	basedpyright-code-budget.json
#	litellm/proxy/credential_endpoints/endpoints.py
#	litellm/proxy/management_endpoints/model_management_endpoints.py
#	tests/test_litellm/router_utils/test_fallback_event_handlers.py
#	type-discipline-budget.json
@derhornspieler

Copy link
Copy Markdown
Author

@greptile-apps review

The merge with the moved base pushed three budget gates over their ceilings.

credential_hydration collected the per-credential field lookups with a list
comprehension over an async call, which LIT002 reads as building a mutable
value by accumulation. Replaced with asyncio.gather plus chain.from_iterable,
so the lookups now also run concurrently instead of one after another.

The credential form helper test carried an inline object literal that put
no-large-inline-object-arg one over its 560 ceiling. Hoisted it to a named
const.

Two patch sites in the pass-through relay test were counted by TQ008. Both
are genuine boundaries, the http client the test asserts bytes against and
the logging hook that would otherwise want a database, so they carry
suppressions naming the reason rather than being restructured.
The last merge rebuilt litellm/proxy/_experimental/out, so 480 generated
files landed in the diff and pushed it to 603 files, past the 500 file
ceiling the review bot will look at.

That bundle is refreshed by its own periodic chore commit rather than by
feature branches, so this restores the directory to the base branch state.
The dashboard source changes stay; the compiled output comes back with the
next scheduled artifact refresh.
@derhornspieler

Copy link
Copy Markdown
Author

@greptile-apps review

Dropped the regenerated dashboard bundle that a merge pulled in, so this is back to 123 files and under your limit

The paginated /v1/models fetch handed the shared http client a read-only
mapping for its query params. That client merges the URL's own query string
in by mutating the mapping it is given, and a mappingproxy has no update, so
the call raised AttributeError as soon as after_id was set. Any org with more
models than fit on one page could not discover them at all.

after_id now rides the URL instead, which is the same mechanism the client
uses to carry query params, so nothing needs to mutate.

The existing pagination test missed this because its stub client only recorded
params and never merged them the way the real one does. The new test drives
the real HTTPHandler over a mock transport, so the second page goes through
the code that actually mutates. It fails with the original AttributeError
against the previous implementation.

Also clears the basedpyright reportArgumentType errors this branch added:
make_spec now takes typed keyword arguments rather than splatting an untyped
dict, both token_response helpers build their body in one shot instead of
assigning an int into a str-inferred dict, the duplicated credential decrypt
block is one helper, and HTTPHandler.get accepts a Mapping for headers, which
it only forwards.
…lats

Two test call sites splatted a plain dict of strings into a strongly typed
callable. Because every parameter then looks like it might receive a str,
basedpyright raised one error per parameter: 118 for the GenericLiteLLMParams
construction and 30 for the completion call, 148 in a file that otherwise has
14. Passing the values as keywords types them properly and drops the file to
those 14 pre-existing ones.

The leak assertion that walked the old kwargs dict now walks a tuple of the
same field names, so it still proves none of them reach the request body.

HTTPHandler.get takes Mapping[str, Any] for headers rather than the
Mapping[str, str] of the previous commit. The parameter was a bare dict, which
accepts any value type, so narrowing it to str rejected existing callers that
pass looser mappings and added six errors of its own.
_create_deployment merges its untyped params dict with the zeroed PTU pricing
mapping and spreads the result into LiteLLM_Params. The merge widens every
value to Unknown | float | tuple[()] | Mapping[str, float], and because a
spread is checked against each field in turn, basedpyright raised one error per
field: 183 on that one line.

This branch adds eighteen anthropic federation fields to that model, so it
inherited eighteen more errors on a line it does not touch, which is the whole
of the reportArgumentType budget breach. The same effect landed two more in the
azure passthrough transformation.

Naming the merged value as the heterogeneous config mapping it is fixes the
line rather than the symptom. LiteLLM_Params still validates every field at
runtime, so nothing is loosened that was previously enforced. The file drops
from 402 of these errors to 237, which puts the rule below its base count
instead of over its ceiling.
@derhornspieler

Copy link
Copy Markdown
Author

@greptile-apps review

Three commits since your last pass: a model discovery pagination fix, typed keyword args in the tests, and a type annotation on the params spread in router.py

…itellm_anthropic_wif_oidc_28607

# Conflicts:
#	litellm/llms/anthropic/common_utils.py
#	litellm/llms/anthropic/experimental_pass_through/messages/transformation.py
#	litellm/llms/anthropic/skills/transformation.py
#	litellm/proxy/management_endpoints/model_management_endpoints.py
#	litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py
#	tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py
#	tests/test_litellm/llms/anthropic/experimental_pass_through/test_reasoning_effort_fields.py
#	tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py
#	tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py
#	tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

Comment thread litellm/llms/base_llm/auth/token_exchange.py Outdated
… credential

The reflection scan only ever compared eight-character windows of the response
against the secret, so a secret with fewer credential characters than that could
never match once an endpoint echoed it percent-encoded or split up, and the
verbatim check needs the raw form. Cap the run at the secret's length so a short
client secret is compared whole in every wire shape.
…encent messages

Upstream e14f485 makes validate_anthropic_messages_environment raise
AuthenticationError when no credential resolves instead of sending the request
unauthenticated. The two regression tests that prove MiniMax and Tencent never
mint an Anthropic federation token now assert that raise, which a wrongly
enabled federation path would still break by attaching a bearer instead.
…age audit

GET /credentials/{credential_name}/jwks serves the public key set of an internal
issuer credential for the Add Provider wizard, a derived read-only view rather
than Terraform-managed state.
@derhornspieler

Copy link
Copy Markdown
Author

@greptile-apps review Fixed since the staging merge: the short-secret redaction gap, the MiniMax and Tencent tests for upstream's missing-key raise, and the JWKS coverage allowlist.

@mateo-berri

Copy link
Copy Markdown
Contributor

@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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f2240df. Configure here.

auth_header: Final = self.anthropic_model_info.get_auth_header(api_key, api_base)
auth_header: Final = await self.anthropic_model_info.aget_auth_header(
api_key, api_base, allow_workload_identity=True
)

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.

Files handler drops deployment WIF params

Medium Severity

afile_content now mints a federation token but calls aget_auth_header without litellm_params. Deployment-scoped federation fields never reach the resolver, so batch-result fetches that go through this handler still fail unless the same values happen to be in the process environment.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f2240df. Configure here.

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.

This handler has no runtime caller; anthropic file_content routes through get_provider_files_config and base_llm_http_handler.retrieve_file_content, which receives litellm_params.

@mateo-berri

Copy link
Copy Markdown
Contributor

Superseded by #38818, an internal copy of this branch at 57b9e94 so CircleCI runs on a litellm_ head. Commits and authorship carry over

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support Anthropic Workload Identity Federation (OIDC JWT-bearer token exchange)

3 participants