Repository navigation
feat(agents): authenticate Entra identities and delegated requests - #43722
Conversation
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
|
@greptileai Please review the shared-cache wiring at 5b10f6b. Existing enrollment and target-selection findings remain outside this focused follow-up |
|
bugbot run. Please check the latest cache wiring and retained revocation behavior; broader existing findings remain tracked separately |
c6610fa to
d462634
Compare
5b10f6b to
0fd4e93
Compare
|
@greptileai @cursor review Please review |
|
@cursor review Please verify current commit |
|
@greptileai Please review current commit |
This comment has been minimized.
This comment has been minimized.
|
@greptileai Fixed stale-registry admission in |
|
@cursor review Please verify |
|
@greptileai Please review |
|
@cursor review Please verify final commit |
There was a problem hiding this comment.
✅ 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 dd07ec8. Configure here.
|
Both hashing alerts require a truthy JWT key, but JWT principals carry |
…fixtures (#43958) * test(ci): repair stale request fakes, spend-log golden, auto-router labels, and Interactions spec lookups Request fakes now carry the scope a real Starlette request has, the GCS pub/sub spend-log golden gains the agent identity keys from #43722, the auto-router session tests follow the baseline_models contract from #43348, and the Interactions spec checks resolve the create body and resource paths from the live spec instead of hardcoded names * test(ci): move retired OpenAI text-completion fixtures to live vehicles OpenAI still serves native /v1/completions on the gpt-5.4 family, so the single-prompt cases move to text-completion-openai/gpt-5.4-nano. Multi-prompt batches and echo with logprobs now 500 on every OpenAI model, so those cases keep the same text-completion-openai transport pointed at Fireworks, which documents both. The optional-params test asserts the request body actually sent instead of a success callback whose assertions were swallowed * test(ci): use a serverless Fireworks model for the text-completion batch and echo cases gpt-oss-20b is on-demand only on Fireworks, so the CI key got 404 model not deployed; glm-5p3-flash is listed as serverless * test(ci): skip the ROI calculator repository listing in the security route sweep GET /roi-calculator/repositories (#43669) lists repositories from the configured GitHub API, api.github.com by default, so the S2 sweep's GET of every route made the owned proxy reach an external host and failed the egress check in 31 integration-security tests. It joins /get/latest_release_info in the deny list
TLDR
Problem this solves:
How it solves it:
Adds managed Entra authentication, bounded inference access, verified human delegation, and caller/target attribution. Authorization uses the same model selection as inference handlers and reads current target policy even when a worker's registry is stale
Stack 3/9, targeting
main. #43720 (identity contracts) and #43721 (permissions) are merged. This PR contains runtime integration only, with 2,383 additions and 265 deletions across 42 files. #43723 adds registration, #43724 budgets, and #43725–#43728 SCIM contracts, operations, activation, and dashboard support. Published descendants still target their immediate predecessors; this repair does not restack themUser Flow
Before: a registered agent's Entra token cannot authenticate for inference
haiku-ahttps://litellm-domain/v1/chat/completionswith that bearer token and{"model":"haiku-a","messages":[{"role":"user","content":"hi"}]}User doesn't exist in dbhaiku-balso returns HTTP 401After: the same agent can use its allowed model and cannot use ungranted models
haiku-ahttps://litellm-domain/v1/chat/completionswith that bearer token and{"model":"haiku-a","messages":[{"role":"user","content":"hi"}]}haiku-breturns HTTP 403 without calling the providerRelevant issues
Splits the approved scope from #43149 and #43459. The ordered stack #43720, #43721, #43722, #43723, #43724, #43725, #43726, #43727, #43728 collectively replaces #43149, #43229, #43230 and #43459; this PR covers runtime integration only
Affected release
Linear ticket
Resolves LIT-8958
Pre-Submission checklist
Verification
Current head
dd07ec807d1bf703633bb640fb10abfdd1b8d8ec, merge base3930c5bab664dc506af05be6a1bc09575870bf0dThe protected manifest was derived from changed production paths and their mapped tests before editing. The final 39-file run produced 4,932 passed, 5 failed, and 3 skipped. All five failures also reproduce on main: four Google live OpenAPI schema assertions and one asynchronous spend-callback ordering failure. No unrelated fixes or budget changes are included
Whole-production Ruff, test-tree lint, changed-production formatting, strict-rule, type-discipline, test-quality, and canonical basedpyright gates pass locally. The OpenAPI snapshot and import checks also pass
Local changed executable-line coverage is 259/262 (98.85%). Touched-function branch coverage is 617/778 (79.31%), including unchanged branches in large legacy functions. The managed authorization module has 100% line and branch coverage. Three changed lines remain uncovered: a defensive missing-logger return, an empty pre-initialization registry adapter, and the unreachable exhaustive-match assertion. These measurements are separate from hosted repository-wide coverage
Current-tip Veria and Bugbot reviews are clean. Greptile is 5/5 and explicitly withdrew both database-access findings after confirming the authoritative revocation contract. No actionable concerns remain across those three reviewers
Hosted Codecov reports the same 259/262 changed executable lines (98.85%); the three gaps match local coverage. The final report is complete after the coverage-producing jobs finished; repository-wide coverage is 82.46%
All ten GitHub-required checks pass. The optional MCP OAuth workflow awaits approval for the protected
e2e-changedenvironment. Other CI failures are the unchanged-lockfile OSV vulnerabilities, the four Google schema assertions reproduced on main, and two CodeQL hashing alerts. Both reported taint paths require a truthy key from a JWT principal that explicitly carriesapi_key=Noneandtoken=None; regression assertions confirm these values. Actual password hashing is unchanged. These alerts are explained, not suppressed or dismissed; security adjudication remains pendingIndependent source-build verification is complete at this head. Real Entra inference, ordinary-agent compatibility, custom authentication, stale-registry controls, revocation, CLI enrollment and delegation pass. The extra A2A body-model case remains a documented rejection, not a passing invocation. This PR is not being declared fully ready while the CodeQL flags await adjudication
Screenshots / Proof of Fix
Independent verification compared source builds at the exact hashes below, with imports resolved from their respective worktrees. PostgreSQL databases were isolated (
db_bEanddb_hG), and port 4000 was untouchedManaged bindings were seeded directly with SQL because the registration API belongs to #43723. Agent A was enabled, autonomous, bound to a real Microsoft Entra application requiring
Agent.Invoke, and granted only modelhaiku-a. JWT issuer, audience and Microsoft JWKS validation were configured. Both aliaseshaiku-aandhaiku-broute to the real paidanthropic/claude-haiku-4-5provider; the grant is what distinguishes access. Ordinary agent O was grantedhaiku-aThe same real Entra token was used for allowed and denied requests. The commands below preserve the captured request bodies and use descriptive names for private credential files. Outputs are sanitized response excerpts; the baseline service-principal identifier is redacted. The Responses output limit is intentionally small, so an HTTP 200 response can report
status=incompleteThe final captured processes were baseline PID 40039 and head PID 40040, started at 2026-09-30 19:48:49 UTC. They were stopped after capture. Delayed spend inspection confirmed provider success rows for each allowed head request and ordinary-key control, with no provider success for the denied requests
Before (3930c5b)
Chat completions
Managed agent, allowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. Create user via `/user/new` call."}Same managed token, disallowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. Create user via `/user/new` call."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","content":"Hello! How can I"}Responses
Managed agent, allowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. ..."}Same managed token, disallowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. ..."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","status":"completed","text":"Hello! \ud83d\udc4b How can I help you today?"}Messages
Managed agent, allowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. ..."}Same managed token, disallowed model
Observed HTTP 401, response excerpt:
{"error":"Authentication Error, User doesn't exist in db. 'user_id'=<sp-id>. ..."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","text":"Hello! \ud83d\udc4b"}After (dd07ec8)
Chat completions
Managed agent, allowed model
Observed HTTP 200, response excerpt:
{"model":"haiku-a","content":"Hello! \ud83d\udc4b"}Same managed token, disallowed model
Observed HTTP 403, response excerpt:
{"error":"The requested model 'haiku-b' is not available for this API key, or the model name is invalid. Check the models available to you and try again."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","content":"Hello! \ud83d\udc4b"}Responses
Managed agent, allowed model
Observed HTTP 200, response excerpt:
{"model":"haiku-a","status":"incomplete","text":"# Hi there! \ud83d\udc4b\n\nHow's it going? What can "}Same managed token, disallowed model
Observed HTTP 403, response excerpt:
{"error":"The requested model 'haiku-b' is not available for this API key, or the model name is invalid. Check the models available to you and try again."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","status":"incomplete","text":"Hey! \ud83d\udc4b How's it going? What can I help y"}Messages
Managed agent, allowed model
Observed HTTP 200, response excerpt:
{"model":"haiku-a","text":"# Hey there! "}Same managed token, disallowed model
Observed HTTP 403, response excerpt:
{"error":"The requested model 'haiku-b' is not available for this API key, or the model name is invalid. Check the models available to you and try again."}Ordinary agent control
Observed HTTP 200, response excerpt:
{"model":"haiku-a","text":"# Hey there! "}Additional live verification
These cases used the same source hashes, real HTTP requests, isolated databases and a real HTTP echo agent as the A2A upstream. Managed-only controls have no equivalent admitted managed principal on the baseline
fallback_to_db_teams: trueThe team-header setting is required when the real token has no team claim; this resolver is unchanged from main. The fault hook intentionally makes the shared policy lookup fail, so ordinary callers also return 503 under that injected outage. This confirms the documented writer-availability dependency; normal ordinary-agent controls remain 200
Type
New Feature
Bug Fix
Caveats (if any)
Severe
Medium
modelLow
Final Attestation
Note
High Risk
Changes core JWT authorization, team delegation, budget fail-closed behavior, and permission boundaries for managed agents—security-critical paths with broad proxy surface area.
Overview
Adds a managed-agent JWT admission path that recognizes Entra-bound agent identities, records authentication evidence, restricts them to inference/agent-discovery routes, and blocks virtual-key mapping and MCP token exchange for agent tokens.
Delegated execution picks a granting team from verified human SSO grants (
resolve_delegated_agent_team), tightens permission checks for identity-managed agents (including admins), and centralizes invocation prep (prepare_agent_invocation) with per-target fees andinvoked_agent_*fields onUserAPIKeyAuth. Custom-auth subjects are markedauthenticated_by_custom_authso they are not treated like DB-reloaded virtual keys.Attribution and ops: request metadata distinguishes actor vs target vs billing agent; A2A and MCP tool calls stamp
model/model_groupand MCP tool metadata; spend logs prefer final agent outcomes in session grouping and can charge autonomous agents without a human key. Sharedresolve_inference_modelaligns managed and proxy model defaults; Microsoft interactive SSO enrolls verified human subjects for delegation.Reviewed by Cursor Bugbot for commit dd07ec8. Bugbot is set up for automated code reviews on this repo. Configure here.