security(audit): log owner-bypass reads in GET /pets/:id/profile-summary (GRO-2062) #146
Reference in New Issue
Block a user
Delete Branch "flea/gro-2062-owner-bypass-audit"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a defense-in-depth audit row to
impersonationAuditLogswhen the staff-side owner-bypass path fires inGET /pets/:id/profile-summary. The portal route already audits viaportalAudit; the staff route did not.This is not a defect in the shipped access-control fix — the bypass logic in GRO-2013 is correct. The change makes every owner-bypass read observable in the same audit table the portal route already uses, so cross-tenant attempts and one-off reads can be reviewed after the fact.
Files changed
src/routes/pets.ts— newwriteOwnerBypassAudithelper (try/catch,console.erroron failure, never throws) + call site at the exact pointisOwner === trueis confirmed.src/__tests__/petProfileSummary.test.ts— positive (bypass fires → one row with correct shape), negative (groomer-linkage success → no row), and a cross-tenant regression test (denied bypass → no row).UAT_PLAYBOOK.md— newTC-API-3.19ddocumenting the audit assertion readable viaGET /impersonation/sessions/:id/audit-log.Architectural decisions (per the spec, all respected)
petIdandactorStaffIdare written inside the existingmetadatajsonb.resolveImpersonationClientIdis untouched. The audit write is a separate, isolated helper called only when the bypass fires.try/catchwithconsole.error; an audit failure cannot turn a working read into a 500. Mirrorssrc/middleware/portalAudit.ts.Audit row contract
When the owner-bypass actually fires, exactly one row is written:
No row is written on the normal groomer-linkage path, on 403/404/401, when the bypass is denied (cross-tenant), or when no impersonation header is present.
Test results
pnpm test petProfileSummary→ 38/38 passing (23 in deployedsrc/__tests__/, 15 in legacyapps/api/src/__tests__/).pnpm typecheck→ clean.servicesTableunused-var error is ondevand unrelated).Parent tracking issue: GRO-2062
Engineering sub-task: GRO-2063 (this PR)
cc @cpfarhood
LGTM. Code review complete — all acceptance criteria satisfied. Handing to CTO for dev merge.
CTO review — APPROVED.
Reviewed
1f888acagainst the GRO-2063 spec and acceptance criteria:isOwner === true && headerSessionId); never on groomer-linkage, 403/404/401, or absent header.action="read_profile_summary",sessionIdfrom header,pageVisited=c.req.path,metadata:{petId:pet.id, actorStaffId:staffRow.id}. No migration.writeOwnerBypassAudit(try/catch + console.error, never throws) — mirrorsportalAudit.ts.resolveImpersonationClientIdleft side-effect-free.Merging to
devand promoting touat.