Day 90 of 100·Project Deep-Dive Day·514 words

The security audit — what you fixed, what remains

Every remediation in your own SECURITY_AUDIT, plus the outstanding items — said first, by you.

Know your own system

The strongest possible position on security questions: you audited yourself, documented findings with severities, fixed the criticals, and can name what's left. Recite it like that.

Zoth — the audit trail (from your SECURITY_AUDIT.md)

Criticals, remediated: cross-tenant access paths (the C-4 IDOR class — object fetches by bare ID before tenant checks) → every fetch became tenant-asserted in the query; self-approval (a user approving their own request) → blocked in decide with audit metadata; role escalation surfaces on identity/role endpoints → admin-gated (RequireRole) and role resolution moved server-side (roles re-read from DB per request, never trusted from the token).

Highs, remediated: API-key scope enforcement (write-scope required for mutations, keys can't impersonate admins — no user/role attached); credential encryption (AES-256-GCM envelopes, decrypt-per-call, never logged); token validation hardening (secret-length enforcement at boot, HS256, expiry); audit completeness (every state transition writes a chained row); webhook authentication (HMAC + timestamp window, fail-closed when unconfigured).

Outstanding, named (say these yourself): the audit-chain write race (M-8: GetLastHash→Insert isn't transactional → fix = sequence-ordered per-tenant chain, Day 50); VerifyChain's 10k cap and unmounted verify route; single symmetric encryption key (no per-tenant KMS/rotation); unmounted rate limiter; no RLS (relies on repository discipline + tests); and the transient RabbitMQ delivery mode (Day 48). Each has a fix and a trigger.

Traverse — the security surface

Sound: bcrypt-12 password hashing; JWT with revocation on password change; strict auth rate limits (5/15 min) with proxy-aware IP handling; Razorpay webhook HMAC over the raw body; idempotency keys everywhere; per-application file keys; log redaction before OTel export; BYOK keys isolated per user; separate S3 credentials per bucket.

Debts to name (and the one-line fixes):

  1. Hardcoded provider key in the AI ladder (a Groq key in source/config rather than env-only) — fix: move to env + rotate; the deeper fix is a secrets manager.
  2. Timing-unsafe comparisons: Razorpay webhook signature compared with !== (should be crypto.timingSafeEqual), and the recon token path already does it right — inconsistency is the finding.
  3. In-memory rate limiters (fragment at N replicas — Day 24).
  4. JWT has no expiry in some issuance paths (session-forever tokens) — a deliberate simplicity choice with a real revocation story only on password change; the fix is short-lived access tokens + refresh.
  5. No breach-list/credential-stuffing checks on login beyond rate limits.
  6. BYOK key storage less protected than zoth's credentials (plain vs. encrypted envelope) — the cross-project lesson: apply the better pattern from the other codebase.

The framing that makes this a strength: "I audited both projects against the OWASP-style classes (IDOR, broken access control, injection, secrets, timing, enumeration) and I can walk you through each finding, its severity, the fix, and whether it shipped. The remaining items are documented with owners and triggers — a security posture is a backlog with a triage discipline, not a claim of being finished."

Bulletproof your resume

Say these answers aloud before moving on

These are the follow-ups an interviewer will actually ask about your projects.

01

Tell me about a security bug you found and fixed.

My self-audit found a class of IDOR issues (C-4): several object fetches looked up rows by bare UUID and checked tenant ownership after loading — so a crafted ID could touch another tenant's data before rejection. I moved tenancy into the query itself (every fetch is WHERE id AND tenant_id, with resource ownership joined through the parent integration), which eliminates the fetch-then-check TOCTOU entirely, and added cross-tenant regression tests per controller. The related fixes from the same audit: blocking self-approval, gating role mutations behind admin, and re-resolving roles from the database per request instead of trusting token claims.

02

What's your biggest remaining security gap?

In zoth, the audit-chain write race — GetLastHash and Insert aren't transactional, so multiple API replicas could fork the chain, which would make the tamper-evidence unusable (the fix is a per-tenant sequence-ordered chain, scoped and ready). In traverse, the hardcoded provider key and the two timing-unsafe comparisons. Both lists are documented with severities rather than discovered by an interviewer — which is the point: I'd rather name the weakest link than have it found.

03

How do you test authorization?

Per-controller cross-tenant and cross-role tests: attempt the operation as a different tenant's user (expect 404/403 without existence leakage), as a non-admin on admin routes, as a non-owner on owned resources, and as an unauthenticated caller. Plus positive-path tests so the negative tests aren't passing vacuously. The design principle underneath: authorization bugs are omissions (a missing check on one route), so tests must enumerate routes rather than exercise features — which is why my remediation added per-controller cases instead of one integration happy path.