Repository navigation
feat: project membership RBAC (stacked on service auth) - #1559
Closed
Josh Duffney (duffney) wants to merge 6 commits into
Closed
Josh Duffney (duffney) wants to merge 6 commits into
Josh Duffney (duffney) wants to merge 6 commits into
Conversation
Enforce fixed project capabilities and canonical authorization across API, CLI and Portal. Add durable access operations, operator-only bootstrap and repair, project-admin membership discovery, migration 031 and coordinated report shutdown/auth mode. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4257b230-1926-4101-9c45-498e16e5fa51
Cover deferred all-project navigation so story interactions cannot query the scoped view before React commits the route transition. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4257b230-1926-4101-9c45-498e16e5fa51
Task-prompt generation and feature extraction read prompts and feature definitions across all projects, sending other projects' data to the LLM and persisting foreign features on the current prompt. - task-prompts/generate filters recent prompts by the authorized projectId - task-prompts/:id/extract-features uses only the prompt's project features - prompt-features generate-prompt/extract-from-text and criteria/generate-prompt require projectId and always filter by it - criteria/mdp task-prompt lookup is scoped to its project - Portal AI calls now send the active projectId (previously 400) - Add unit and two-project integration regression tests - Regenerate OpenAPI snapshot/spec; document the rule in app-design.md Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e871494e-c9b6-4a6f-8665-96a0b6336218
Branch route authorization on the caller principal attached by the auth middleware: - Run tokens are accepted only on routes flagged runToken: true, for GET/HEAD, and only for the run's own non-deleted project. Other projects look absent (404); unflagged routes and writes return 403 run_token_not_allowed. req.authorizedProjectId is set as for users. - Service keys are accepted only on the routes their scopes grant (agents:read, agents:register, models:sync); everything else is 403 insufficient_scope. - Run and service callers skip the IdP JWT lease parse and cannot resolve to a user (currentUser returns 403 user_required). - The global /api/v1 gate admits verified run and service callers so the route guard can decide. User principals keep the existing membership behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Unit tests for the guard's caller branches: run tokens pass on flagged GET/HEAD routes in their own project without a user read or JWT lease, look absent (404) for other or deleted projects, and get 403 on unflagged routes, writes, metadata, platform and catalog routes; service keys pass in scope and get 403 insufficient_scope otherwise; callers never resolve to a user record. Auth-on integration tests against native Mongo through the full API: a run token reads criteria in its project, is rejected on another project's route, on unflagged routes and on POST; expired, finished and forged tokens get 401; the model scanner and register-agents keys pass in scope and get 403 out of scope. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Document how the route guard limits run tokens (flagged GET/HEAD routes in the run's project, 403/404 otherwise, no JWT lease) and service keys (scope-to-route table, 403 insufficient_scope), update the internal-caller cutover table, note the report trigger/tools gap and add the new integration suite to the release gates. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Project membership RBAC, stacked on the service-auth / run-token layer (#1558). This PR is #1532 rebased onto #1558, plus one commit that wires RBAC enforcement to #1558's run and service principals. Refs #1557.
Stack:
main ← #1531 ← #1558 (service auth / run tokens) ← this PR.Commits:
d7759c42, conflicts resolved against #1558)a248e76d)b7439535)Conflict resolution (cherry-picks)
auth/types.ts:Requestkeeps bothprincipal?(feat: authenticate service-to-API calls (run token + service keys) #1558) andauthorizedProjectId?(feat: add project membership RBAC and administration #1532).index.ts: the auth middleware keeps feat: add project membership RBAC and administration #1532'sgetProvider: () => authEnabled ? authProvider : nulland feat: authenticate service-to-API calls (run token + service keys) #1558's run-token / service-key verifiers.openapi/api-route.ts: feat: add project membership RBAC and administration #1532's policy/security default and feat: authenticate service-to-API calls (run token + service keys) #1558'sx-scope-run-tokenextension both apply.website/src/openapi/scope-openapi.json: regenerated.Guard wiring (
apps/api/src/auth/authorization.ts)decide()branches onreq.principal, which #1558 sets for run and service callers. Those callers havereq.user = ANONYMOUS_USERand noreq.auth.Run principal
runToken: true(14 GET routes), only for GET/HEAD, and only when the route policy isproject. Otherwise it returns403 run_token_not_allowed.requestId, references). It must equal the run's project and must not be deleted. Otherwise it returns404 not_found, the same non-disclosing contract a non-member user gets.req.authorizedProjectIdis set.Service principal
agents:read: GET/HEAD/api/v1/agentsagents:register: POST/api/v1/agentsand/api/v1/agents/:id/versionsmodels:sync: POST/api/v1/models/sync403 insufficient_scope.Other changes
explease parse. Their lease is the normal authorization budget, and their credential lifetime is enforced by feat: authenticate service-to-API calls (run token + service keys) #1558's verifiers.currentUser()rejects non-user principals (403 user_required), so user-only flows never treat a run or service caller as a user./api/v1gate admits authenticated principals.apiRoutepasses{ runToken }toguard(), including stream revalidation.Known gap
The post-processor's
/reports/triggercall and the report-generator tools don't send run tokens yet. Report generation is disabled by policy in this stack, so these paths aren't exercised. They are left for a follow-up.Demos
N/A. This is API authorization behavior; no Portal/CLI UI change beyond #1532's.
Before
N/A
After
N/A
Testing
Commands and results:
pnpm exec vitest run apps/api packages/shared apps/judge packages/model-scanning scripts apps/workers: passed (148 files, 2458 tests).pnpm exec vitest run apps/cli apps/portal: passed. The full unit set across api, shared, judge, model-scanning, scripts, workers, cli and portal was 251 files and 3499 tests.caller-authorization.integration.test.ts. 5 of its 7 tests fail without the guard-wiring commit.apps/api,apps/judge,packages/shared,apps/cli,packages/model-scanning(tsc --noEmit)pnpm --filter portal exec tsc -b)coder-acp-copilot-windows, which fails only because a sibling package'sdistisn't built locally. That is unrelated to this change.website/src/openapi/scope-openapi.json).Local end-to-end (Docker Compose,
SCOPE_AUTH_ENABLED=true, entra-local, isolated port offset/DB):Setup:
register-agentsandmodel-scanner.operator-access.js bootstrap-admin(preview, then--apply).Service and run callers:
register-agentsexited 0 and registered both agents and their dev versions.model-scanner-copilotexited 0 (+38models synced).succeeded(1/1 criteria passed).403 insufficient_scope:register-agentsonGET /agents,POST /models/syncandGET /criteriamodel-scanneronGET /criteria401 invalid_service_key.400 ambiguous_credentials.Run token for project X (minted for a separate synthetic processing request):
/criteria,/criteria/:id) → 200./criteria?projectId=Y,/criteria/<Y criterion>?projectId=Y) → 404./requests,/projects) andPOST /criteria→ 403run_token_not_allowed./users/me→ 401.401 run_token_expired; run no longer processing →401 run_not_active; tampered token →401 invalid_run_token.User principals:
Not verified end to end:
/reports/triggerand report-generator run-token paths (see known gap).Documentation and compatibility
docs/architecture/auth-rbac.mdnow has a new subsection, "Authorization of run and service callers" (allowed routes, project matching, status codes, lease handling). It also updates the internal-caller cutover table and the release-gate test list.Checklist
up()/down()and keep it CosmosDB-compatible. (feat: add project membership RBAC and administration #1532's031-create-project-memberships, unchanged.)NOTICE/NOTICE-REVIEW.txtwithpnpm noticeas needed. N/A: no dependency changes.