CedarCopilot wants to merge 4 commits into staging from fix/debug-tool-org-admin-authz
Live on prod, no production signal yet
Since deployment, CloudWatch identified 38 production external-mcp.tool_call executions for toolId=debug/action=execution-detail: 37 outcome=ok with resultSuccess=true and 1 tool-error with resultSuccess=false. Successful calls averaged 716.97 ms (p95 3164 ms, p99 7388 ms).
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verification reached its budget before a verdict.
Production telemetry since 2026-09-10T13:10:02Z shows 3 external-mcp debug tool-call-detail executions, all outcome=ok and resultSuccess=true. They were split across 2 callers (<email>: 2; <email>: 1), with no tied errors. However, all 3 responses were found=true for authorized.
Libra has verdicts on 0 of 7 tracked behaviors on prod; 7 are still being checked. Libra checks hourly for 3 days after each deploy.
debugTool.ts's four "dual-scope" actions (recent-executions, execution-detail, tool-call-detail, and health-report's per-user branch) were built with a two-tier access model: self, or a literal Cedar staff account (resolveCallerIsStaff, checked via email.endsWith('@cedarcopilot.com')). That meant an org admin/owner could not view a teammate's own execution/conversation/health data, even within their own organization, unless they also happened to be a Cedar employee. Confirmed live: a real org admin got "Not authorized to view run <runId>." trying to view a teammate's execution-detail in their own org.
resolveCallerIsStaff + === callerUserId comparisons in all four actions with a single call to the canonical authorize() (services/auth/authorize.ts), whose DEFAULT_POLICY already declares 'subagent_execution:user': 'SELF_OR_ORG_ADMIN' and has the Cedar-staff bypass built in internally , matching the reference call shape resolveAgentForAdmin already uses for test-run's own targetUserId gate.execution-detail/tool-call-detail keep their fixed, generic "Not authorized to view ..." denial message (not authorize()'s own reason), preserving the existing "don't reveal whether a run/tool-call exists" property , authorize()'s denial text names the target user id, which would otherwise leak the record's owner.diagnose-conversation , NOT migrated. Its callerCanAccessConversation() → assertConversationOrgAccess() check already allows any member of the caller's own org to diagnose any conversation belonging to that org , broader than conversation:user's SELF_OR_ORG_ADMIN policy, not narrower. Routing it through authorize() would tighten existing behavior (a plain member could no longer diagnose a teammate's conversation), which is a separate product question, not this bug. Flagged in the file's own comment rather than silently changed.health-report's org-wide (orgId) branch , unchanged, still Cedar-staff-only. It's a distinct, more sensitive capability (every user in an org at once) with no matching DEFAULT_POLICY row; subagent_execution:org already exists but gates a different action (a real org-level agent send, not a health-data read).create-monitor , untouched, Cedar-staff-only by design, no self-scoped variant.Added to apps/server/src/mastra/tools/debug/__tests__/debugTool.test.ts (mocking authorize() as a boundary, matching test-run-subagent.test.ts's convention , the org-admin/plain-member/cross-org policy semantics themselves are already exhaustively covered generically, for every DEFAULT_POLICY row including subagent_execution:user, by services/auth/__tests__/authorize.test.ts):
pnpm --filter @zero/server exec vitest run src/mastra/tools/debug/__tests__/debugTool.test.ts → 59/59 passing.
pnpm --filter @zero/server run types → clean.
pnpm --filter @zero/server exec vitest run src/mastra/tools/debug/__tests__/debugTool.test.ts (59 passed)pnpm --filter @zero/server exec vitest run src/services/auth/__tests__/authorize.test.ts src/services/playbook/__tests__/test-run-subagent.test.ts (350 passed, no regressions)pnpm --filter @zero/server run types (clean)eslint on both changed files (clean; one pre-existing unused-var lint error in theLibra has not measured any production surfaces for this change yet.