Retroactive security review of PR #22 (Central API developer console — merged as 0c325fb, live at 0d1741d).
Discipline-debt clearance — PR #22 touched all four security-reviewer trigger surfaces (auth, crypto, audit, tenant boundaries) and merged without the subagent running, against the standing instruction in CLAUDE.md.
Full report (with reproduction steps + remediation for each finding) lives at qa-log/security-review-pr22.md.
Net assessment
Medium risk overall. No Critical findings. No tenant API key rotation required. Tenant scoping (the load-bearing security property) is correctly enforced; the tests/console-proxy.test.ts suite already covers A-01 + A-10 scoping. The actual issues are documentation drift, an enumeration channel, and an attribution gap on the audit log.
Findings (action items)
Medium — land this week
Low — land next week or fold into related work
Informational
Things checked + clean (8 items)
See report. Highlights: every SQL query in platform.ts is pg-parameterised + tenant-scoped; no dangerouslySetInnerHTML anywhere in dashboard; no JWT or password in log lines; Helmet CSP + trust proxy 1 correctly set behind Caddy.
Recommendations beyond findings
- Run
threat-model-update skill to reconcile A-09 with shipped reality.
- Adopt zod via
dep-add for the 8+ repeated if (!field) checks in console.ts.
- Add CSP
report-uri (open item in threat model).
Subagent
Continuation reference: agentId acdae2de12c322caa — use SendMessage to resume the same security-reviewer session with follow-up questions.
🤖 Generated with Claude Code
Retroactive security review of PR #22 (Central API developer console — merged as
0c325fb, live at0d1741d).Discipline-debt clearance — PR #22 touched all four
security-reviewertrigger surfaces (auth, crypto, audit, tenant boundaries) and merged without the subagent running, against the standing instruction inCLAUDE.md.Full report (with reproduction steps + remediation for each finding) lives at
qa-log/security-review-pr22.md.Net assessment
Medium risk overall. No Critical findings. No tenant API key rotation required. Tenant scoping (the load-bearing security property) is correctly enforced; the
tests/console-proxy.test.tssuite already covers A-01 + A-10 scoping. The actual issues are documentation drift, an enumeration channel, and an attribution gap on the audit log.Findings (action items)
Medium — land this week
localStorage;docs/threat_model.mdA-09 claims "client memory" → either move to HttpOnly cookies OR update A-09 to match implementation. Runthreat-model-update, file an ADR./api/console/signup→ return uniform 202 regardless of email existence; emit verification email out-of-band.actor_type='api_key'withactor_id=NULL→ plumbactorType: 'console'+actorEmailthroughplatform.tsandrecordAuditEvent.Low — land next week or fold into related work
req.console.tenantId) on POST/PATCH/DELETE insrc/routes/console.ts.jti+aud='zeroauth-console'to console JWT; verifyaudinverifyConsoleToken; Redis-backedjtirevocation list.?limit=query param in 5 console handlers; reject with 400invalid_limitinstead of relying onsanitizeLimitdownstream.Informational
error:field for human strings; convention is{ error: '<machine_code>', message: '<human>' }.Things checked + clean (8 items)
See report. Highlights: every SQL query in
platform.tsispg-parameterised + tenant-scoped; nodangerouslySetInnerHTMLanywhere in dashboard; no JWT or password in log lines; Helmet CSP +trust proxy 1correctly set behind Caddy.Recommendations beyond findings
threat-model-updateskill to reconcile A-09 with shipped reality.dep-addfor the 8+ repeatedif (!field)checks inconsole.ts.report-uri(open item in threat model).Subagent
Continuation reference: agentId
acdae2de12c322caa— useSendMessageto resume the same security-reviewer session with follow-up questions.🤖 Generated with Claude Code