Repository navigation
feat: add project membership RBAC and administration - #1532
Josh Duffney (duffney) wants to merge 3 commits into
Conversation
Test Results (Node.js 22)test: Run #343
❌ Some tests failed!
|
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
aca7e51 to
a248e76
Compare
Test Results (Node.js 22)test: Run #374
🎉 All tests passed! |
Review findingsI reviewed
P0 · 🚫 Blocking · Workers / Judge: Worker and judge API calls are unsigned, so most benchmark runs fail with auth onThe worker content clients, the judge's criteria provider and the agent-registration job call protected routes with no Local repro:
Unsigned callers:
Suggested fix: Option A: job-scoped run token (recommended). This is the same model as GitLab's
Option B: token exchange. This is the on-behalf-of design the spec describes, along the lines of RFC 8693.
Entra's own on-behalf-of flow doesn't fit either way: it needs the user's live token, and a queued run doesn't have one. Either way:
If this doesn't land in this PR: keep auth off anywhere benchmarks run, and update P1 · 🚫 Blocking · API: AI task-prompt endpoints read other projects' data
Other projects' data can end up in LLM input and output. Fix: filter by the authorized P1 · Non-blocking · Auth / Ops: ≤60 s revocation/outage bound isn't demonstratedThe mechanics are in place:
Cosmos and multi-replica behavior haven't been validated. Measure end to end from mutation or outage before claiming the bound. P2 · Non-blocking · API: Public
|
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
Fixed in b743953. Changes
Regression tests
Validation
|
Test Results (Node.js 22)test: Run #389
🎉 All tests passed! |
P0 follow-up: how a job-scoped run token (Option A) would workThis expands on Option A in the P0 finding. It's a design write-up; nothing is implemented yet. The short version
FlowsequenceDiagram
participant W as Worker
participant API as Scope API
participant DB as Mongo / Cosmos
participant J as Judge
participant A as Agent under test
W->>DB: claim run: queued→processing, set new run.claimId
W->>API: POST /api/v1/runs/:requestId/token (service credential + runId, claimId)
API->>DB: verify run is processing under that claim, submitter enabled and a member
API->>DB: store run.tokenHash = SHA-256(token)
API-->>W: scope_rt_<requestId>_<32 random bytes> (no-store)
Note over W: token held in worker memory only
W->>API: GET content routes (MCP, skills, extensions, resources, prompts, codebases) with bearer
W->>A: spawn agent
W->>J: POST /evaluate with run token
J->>API: GET /api/v1/criteria with the same bearer
W->>DB: run processing→done
Note over API: next call with the token is rejected
How each call is secured
Not stopped: someone who steals a live token can use it until the run ends, because it's a bearer token. The damage is limited to read-only content in one project, within the submitter's current rights. Binding the token to its sender (DPoP or mTLS) would close this, but needs in-cluster TLS first. Cryptography
Why opaque and not a signed JWT? Every call has to re-check run state and membership in Mongo anyway, because permissions embedded in a token can't be trusted. So a JWT's statelessness buys nothing. It would add a signing key, rotation and a second trust root next to Entra, and a JWT is signed, not encrypted. Others reach the same conclusion:
Data changes (Cosmos-friendly)
The service credential behind the exchangeOnly the exchange step needs a service identity:
Code touch points
What's left outside Option A
|
|
Replying to @duffney's job-scoped run-token proposal:
The job-scoped token approach makes sense. Could you clarify what additional scenario My understanding is that a run attempt moves forward from Would checking |

Summary
Second layer of the authenticated-delivery -> project-RBAC stack. This draft targets
duffney-authenticated-delivery-extraction, notmain, and depends on #1531.This is a reviewable extraction checkpoint, not merge or deployment approval. Remaining isolation/client gaps and acceptance limits are listed below.
Demos
Portal/CLI changes are user-visible. Before/after recordings for this cumulative RBAC layer are not attached; this PR remains draft. Executable Storybook interactions are covered below, but are not claimed as real-browser/MSAL acceptance. The lower layer's delivery-only browser evidence is not upper-layer acceptance.
Before
Not recorded for this extracted RBAC layer.
After
Not recorded for this extracted RBAC layer.
Testing
Results below were executed on the unchanged cumulative checkpoint in this PR. They were not rerun merely for publication.
Passed:
pnpm --filter shared buildPassed:
pnpm --filter telemetry --filter db-migrations buildPassed:
pnpm --filter api --filter cli --filter portal --filter post-processor --filter report-generator buildPassed:
pnpm --filter db-migrations buildPassed — 169 files / 2,225 tests, including API/CLI/Portal, access/artifact Storybook plays and relevant shared/migration/report-worker suites:
pnpm exec vitest run packages/shared/src/rbac.test.ts packages/shared/src/auth packages/shared/src/codebases packages/shared/src/queue/queue-processor.test.ts packages/db-migrations/src apps/api/src apps/cli/src apps/portal/src apps/workers/post-processor/src/post-processor.test.ts apps/workers/report-generator/src/report-queue-processor.test.ts --reporter=dotPassed — 6 suites / 56 tests, none skipped, using dedicated temporary MongoDB/Redis services with random databases/cache namespaces, signed bearer fixtures and cleanup. Includes actual migration CLI/readiness through 031, creator grants, membership administration/search/self-removal, operator bootstrap/repair, pending recovery and report-state cutover/rerun preservation:
AUTH_TEST_MONGO_URI=mongodb://127.0.0.1:58636 AUTH_TEST_REDIS_PORT=58637 pnpm exec vitest run --config vitest.integration.config.ts apps/api/src/auth/auth-flow.integration.test.ts apps/api/src/auth/auth-mode.integration.test.ts apps/api/src/auth/admin-operations.integration.test.ts apps/api/src/routes/project-membership.integration.test.ts packages/db-migrations/src/membership-authority.integration.test.ts packages/db-migrations/src/rbac-codebase-constraints.integration.test.ts --reporter=dotPassed:
pnpm --filter portal build-storybookPassed:
pnpm notice:checkPassed:
pnpm install --frozen-lockfile --ignore-scripts --offlinePassed:
docker compose --env-file .env.example --profile auth --profile portal -f docker-compose.yml -f docker-compose.dev.yml config --quietPassed:
docker build --target builder --build-arg NPM_CONFIG_REGISTRY=https://packagefeedproxy.microsoft.io/npm/ --tag scope-rbac-portal-check:4257b230 -f apps/portal/Dockerfile .Passed:
sh -n apps/portal/docker-entrypoint.shPassed:
git diff --checkandgit diff --cached --checkbefore commit.Nonblocking output included existing React/nested-button warnings, stale Browserslist data, bundle-size warnings and Docker's heuristic warnings about public
VITE_AUTH_*build arguments. No credentials were supplied as build arguments. No lint, cumulative RBAC real-browser/MSAL, production nginx/runtime, Cosmos, replica-set, mixed-version rollout or worker/benchmark execution acceptance is claimed.Documentation and compatibility
Updated
docs/architecture/auth-rbac.md,overview.md,app-design.md,codebases.md,db-migrations.md,post-processing.md,token-manager.md,authenticated-media.md,docs/research/realtime-data-flow.md, environment examples/reference and generated OpenAPI/CLI snapshots.Coordinated cutover required: deploy matching API/shared/Portal capability bundles; strict validation intentionally rejects mixed role bundles.
SCOPE_AUTH_ENABLEDdefaults on and controls API enforcement and Portal sign-in together. Explicit off permits unrestricted trusted local/test application access, not a recovery path; project scoping remains enforced, identity administration stays unavailable and report generation stays disabled. Remove retired login-bootstrap settings and use genuine enrollment plus explicit operator bootstrap. Apply migration 031 before protected traffic, stop old report producers/consumers and anonymous API replicas, and review legacy membership mappings. Migration 031 fails pending/generating reports and retains memberships/operation evidence on rollback.Migration 032/codebase slug-ref uniqueness is intentionally absent/deferred to #1521. Existing global/project-local indexes and genuine historical ledgers are preserved; uniqueness is not silently weakened or falsely marked applied. Native Mongo success does not establish Cosmos compatibility.
Known original gaps and release gates retained for review:
apps/api/src/routes/task-prompts.ts:51-59recent prompt-generation context and:268candidate feature extraction still lack project filters in secondary queries. Central route authorization does not scope these downstream queries automatically. These original isolation gaps are not fixed by extraction.EventSourceinapps/cli/src/commands/report.ts:272and unsigned API-backed worker codebase/prompt/skill/resource/extension/MCP clients inpackages/shared/src. Service/OBO issuance remains deferred; protected calls fail closed rather than obtaining an anonymous exemption. General authenticated benchmark/worker compatibility is not established.Checklist
up()/down()and keep it CosmosDB-compatible.down()are included and native Mongo tests pass; actual Cosmos acceptance remains a release gate.NOTICE/NOTICE-REVIEW.txtwithpnpm noticeas needed.shareddependency; lockfile updated andpnpm notice:checkpassed. External parser dependency/notices belong to the preceding delivery layer.