Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(debug): org admin/owner can view a teammate's debug data in their own org

merged#2749CedarCopilot

CedarCopilot wants to merge 4 commits into staging from fix/debug-tool-org-admin-authz

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 10, 2026, 5:13 AM
  2. Merged
    Sep 10, 2026, 6:10 AM
  3. Live on prod
    Sep 10, 2026, 6:10 AM
  4. Observed 2 days
    Sep 10, 2026, 6:10 AM
  5. Pipelines steady after this deploy
    Sep 10, 2026, 6:10 AM
  6. Sep 10, 2026, 3:59 PM
  7. Unobserved

    Live on prod, no production signal yet

    Sep 12, 2026, 4:17 PM

Behaviors Libra is checking

Org owners and admins can view the full execution detail for a teammate's run in their own organization, while plain members and cross-organization admins cannot.Inconclusivemedium confidence

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).

prod, checked Sep 12, 2026, 2:53 PM
Unauthorized execution-detail requests use the same found=false response and fixed generic error regardless of whether the run is missing or owned by another user, preventing run-owner enumeration.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 12, 2026, 3:01 PM
Org owners and admins can drill into a teammate's run by runId within their own organization, while missing runs, plain members, and cross-organization admins are denied.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Sep 12, 2026, 3:04 PM
Org owners and admins can list a teammate's recent subagent executions when the teammate belongs to the caller's organization, while plain members and admins from another organization remain denied.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 12, 2026, 3:16 PM
Org owners and admins can view a teammate's tool-call detail, including its inputs and outputs, when the owning run is in their own organization, while plain members and cross-organization admins cannot.Inconclusivelow confidence

Verification reached its budget before a verdict.

prod, checked Sep 12, 2026, 3:18 PM
Unauthorized tool-call-detail requests use the same found=false response and fixed generic error for missing and inaccessible tool calls, preventing tool-call or run-owner enumeration.Inconclusivehigh confidence

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.

prod, checked Sep 12, 2026, 4:16 PM

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.

Summary

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.

  • Replaces the bespoke 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.
  • Updates the file's top-of-file design comment, which described the old two-tier model as current, to document the new self / org-admin / Cedar-staff model and explain the two intentional exceptions below.

What was deliberately left alone

  • 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.

Tests

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):

  • Org admin/owner viewing a teammate's execution/tool-call/health-report in the same org → allowed (the exact case that was broken).
  • Plain (non-admin) member of the same org → still refused.
  • Cross-org access by a non-staff admin → still refused.
  • Cedar staff → still allowed.
  • Self-access and "run/tool-call doesn't exist" cases → unchanged.

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.

Test plan

  • 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 the
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/src/mastra/tools/debug/__tests__/debugTool.test.tsno production surface mapped
  • apps/server/src/mastra/tools/debug/debugTool.tsno production surface mapped
  • apps/server/src/services/auth/__tests__/target-user-id-reaches-the-authority.test.tsno production surface mapped