CedarCopilot wants to merge 0 commits into staging from fix/staff-check-authority-audit
Live on prod, no production signal yet
Libra has no production signal for this change on prod yet. Libra checks hourly for 3 days after each deploy.
Follow-up to #2749 (fix(debug): org admin/owner can view a teammate's debug data in their own org).
That PR fixed debugTool.ts's execution-detail/tool-call-detail/recent-executions/health-report actions, which used a bespoke resolveCallerIsStaff() check (self-or-Cedar-staff-only) instead of the codebase's real permission authority, authorize() , missing the org-admin-in-same-org tier DEFAULT_POLICY already declares (subagent_execution:user: 'SELF_OR_ORG_ADMIN').
A fresh grep confirmed analyticsTool.ts is the only other current call site, and it's legitimately, deliberately staff-only by design (cross-account draft-attribution data). But nothing stops a future tool or route from making the identical mistake , reaching for isCedarStaff/resolveCallerIsStaff directly instead of routing through authorize() (via assertActor/assertActorAllowed in tRPC, or resolveTargetUserContext in a Mastra tool).
This PR adds a systemic test modeled closely on the existing role-reads-confined-to-authority.test.ts precedent: it scans all of apps/server/src for resolveCallerIsStaff(/isCedarStaff( calls outside services/auth/, and requires every hit to sit in an explicit ALLOWED_STAFF_CHECK_CALLS allowlist with a kind and a written, verified reason. An undocumented new call site fails the test.
Tailored to this check rather than reusing the role-read categories verbatim:
STAFF_ONLY_CAPABILITY , a genuine, deliberately staff-only capability outside DEFAULT_POLICY's shape (no organization can hold or grant it) , e.g. analyticsTool.ts, trpc.ts's cedarAdminProcedure, debugTool.ts's remaining 4 calls (all documented as staff-only-by-design in that file's own header comment), staff-only-settings.ts's agentExecutionEnabled gate.CONTEXT , resolves/carries the staff flag for a decision made elsewhere, not itself the gate , e.g. mastra/mcp/external/resolve-session.ts.ADVISORY , explicitly non-authoritative by the file's own comment (UI hygiene, real gate elsewhere) , folded into http/app.ts's combined entry.DECISION , a real access decision made outside authorize(), self-or-Cedar-staff (or same-org-or-staff) shaped with no org-admin tier , the exact shape PR #2749 fixed. 11 files, pinned via EXPECTED_DECISION_ENTRIES: admin-crm-sync.ts, admin.ts, agent-executions.ts, aop.ts (one outlier route), connections.ts, documents.ts, files.ts, integrations.ts, mail.ts, wiki.ts, http/app.ts. None are security holes , self-or-staff is, if anything, too restrictive (same as debugTool's pre-fix behavior) , but they're the same class of gap, on parallel code paths #2749 didn't touch, now tracked instead of silently missed.Kept as-is. debugTool.test.ts's rewrite in #2749 is genuine behavioral regression coverage (asserts authorize() is called with the right resource/scope/targetUserId and the verdict is branched on correctly for self/plain-member/org-admin/cross-org/staff scenarios) , something this static allowlist scan cannot provide. No duplication: the new test only proves every staff-check call site is accounted for and reasoned about; the old test proves the fixed call sites actually behave correctly.
pnpm --filter @zero/server exec vitest run src/services/auth/__tests__/staff-check-confined-to-authority.test.ts , 5/5 passrole-reads-confined-to-authority.test.ts + debugTool.test.ts , 66/66 passpnpm --filter @zero/server run types , cleaneslint + prettier --check , cleanš¤ Generated with Claude Code
Libra has not measured any production surfaces for this change yet.