People roster endpoint leaks the company directory to profile-missing (degraded) sessions
Why (Accounting (Diagnostics), 2026-06-29)¶
Found while reviewing the Codex commit batch (owner asked to fix overlooked
bugs). GET /api/people/roster returns the addressable-people directory (names,
emails, roles, subsidiaries). Its auth gate could be passed by a session whose
RBAC profile failed to load.
Diagnosis¶
The endpoint gated only on
isAccessAllowed(session.user.role, session.user.status).
When the server-side RBAC profile read fails, auth fails closed by setting
user.profileMissing = true β but the degraded session is left as
role = 'pending', status = 'active' (documented in T-073). isAccessAllowed
returns true for ('pending', 'active') (it only rejects on missing role/
status or non-active status), so a profile-missing session sailed through and
could scrape the roster.
Every other protected surface already rejects this state:
lib/api/withSubsidiaryAuth.ts:88, lib/rbac/serverAuth.ts:101,
lib/rbac/serverAuthApp.ts:83 all gate on session.user.profileMissing. The
roster endpoint was the outlier.
What shipped¶
pages/api/people/roster.ts β the 403 gate now also rejects when
session.user.profileMissing is set, matching the other protected endpoints:
if (
session.user.profileMissing ||
!isAccessAllowed(session.user.role ?? undefined, session.user.status ?? undefined)
) {
return res.status(403).json({ error: 'Forbidden' })
}
Verification¶
- β
Attestation (Accounting (Diagnostics)): read
AGENTS.md; checked the board by scope, not UID β no task covered the roster endpoint's missing profileMissing gate. Tracking T-139. - Source: Accounting (Diagnostics) Β·
https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea NODE_OPTIONS=--max-old-space-size=4096 npx tsc --noEmitclean;npx vitest rungreen.
Follow-up (not done β flagged for owner)¶
The deeper question is whether isAccessAllowed should reject role === 'pending'
outright, which would close this class of gap for every caller at the source.
That is a system-wide RBAC behavior change (it touches onboarding flows), so it
is intentionally left for a dedicated, owner-reviewed change rather than bundled
here.
Blast Radius¶
Single endpoint auth gate. No data-shape or roster-content change; the only behavioral change is that degraded/profile-missing sessions now get 403 from the roster endpoint, consistent with the rest of the protected API surface.
Commit index (backfilled 2026-07-01, best-effort Β· Coaching (Diagnostic))¶
Candidate related commits, auto-backfilled from git on main: commits whose message references this task's UID or a PR number it cites. Not verified β this squash-merged history can't yield a precise per-task list, so rows tagged (mentions only) name the task in passing (may be tangential) and untagged work commits may be missing. Treat as a starting point: verify, prune tangential rows, and append any real ones per the AGENTS.md "record every related SHA" policy.
- (no git-discoverable commit references this UID or its PRs β append real SHAs here as identified.)