Security Audit
Audits the implementation that exists, not the architecture that was intended. Every finding names a file, a line range and an attacker path. Every fix that code can make is made and verified.
The audit never concludes that a system is secure. It concludes that the listed checks were performed, with these results, on this revision.
1. Scope decision
| Trigger | Scope |
|---|---|
| a diff touching auth, payments, uploads, permissions or user content | the diff plus its reachable paths |
| an explicit audit request | the twenty four points, full repository |
| a reported vulnerability | the class of the vulnerability, everywhere it can occur |
| a dependency alert | the advisory, its reachability in this code, the upgrade |
| release readiness | the diff since the last release plus the standing checks |
Declare the scope before starting. An audit whose scope is unstated cannot be repeated.
2. The twenty four points
Identity and access
1. Authentication. How identity is established. Password storage algorithm and parameters. Session or token lifetime, rotation, revocation. What happens on logout. Whether a stolen token can be replayed. Multi factor where the product claims it.
2. Authorization. Where the decision is made. Whether every route, action and query enforces it server side. Whether the UI hiding a control is the only protection anywhere.
3. Object level authorization. For every query that takes an identifier from the request: is the row scoped to the caller? This is the single most common serious defect. Enumerate every such query in scope.
4. Privilege escalation. Whether a role, a plan, a team membership or an ownership field can be set by the client. Whether a lower privileged user can reach a higher privileged operation by a different route.
Input reaching a dangerous sink
5. Injection, SQL and NoSQL. Every query construction site. String concatenation, template literals into raw SQL, operator injection into a document query, dynamic ORM filters built from request keys.
6. Command injection. Any process spawn, shell call, or argument built from input. Any archive extraction, image conversion, or PDF generation that passes a user value to a binary.
7. Path traversal. Any file read, write, serve or delete where a path component comes from input. The resolved absolute path is confirmed inside the intended root.
8. Cross site scripting. Every sink where user content becomes markup:
raw HTML insertion, framework escape hatches, attribute interpolation,
javascript: URLs, user controlled style, SVG upload served from the app
origin, markdown rendered without sanitisation.
9. Server side request forgery. Every outbound request whose URL derives from input. Scheme allowlist, host allowlist, DNS resolution checked against private ranges, redirects not followed blindly, cloud metadata endpoints unreachable.
10. Deserialisation and template injection. Any evaluation of user data:
dynamic template compilation, eval shaped calls, unsafe deserialisation,
prototype pollution through merged objects.
11. File uploads. Type sniffed from content, size limited, filename generated by the server, storage outside the executable root, no execution permission, images re-encoded where the product allows it, and the served content type set explicitly.
Session and transport
12. Cross site request forgery. Whether cookies authenticate state changing requests, and if so whether a token or a same site policy protects them. Whether the same site value is actually set and not inherited from a default the team never checked.
13. Cookies. HttpOnly, Secure, SameSite, path, domain scope,
expiry, and whether a session cookie is rotated on privilege change.
14. Security headers. Content security policy and whether it is meaningful or a wildcard. Transport security. Frame options or frame ancestors. Content type options. Referrer policy. Permissions policy.
15. CORS. The allowed origins list. Whether it reflects the request origin. Whether credentials are allowed with a permissive origin, which is the combination that turns CORS into a vulnerability.
Data
16. Sensitive data exposure. Fields returned that the caller is not entitled to. Password hashes, tokens, internal identifiers, other users' data in an included relation, stack traces in production responses, verbose errors.
17. Secrets. Hard coded keys, secrets in the repository history, secrets in client bundles, secrets in logs, secrets in error messages, tokens in URLs, credentials in test fixtures.
18. Logging and privacy. What is logged at each level. Whether request bodies, headers or tokens end up in logs. Whether personal data is collected beyond what the feature needs. Retention where the project states one.
Logic and abuse
19. Business logic. Negative quantities, zero and fractional amounts, currency substitution, discount stacking, refund of more than was paid, state transitions taken out of order, expired resources still honoured, limits enforced in the UI only.
20. Race conditions. Check then act on a shared resource: coupon redemption, seat allocation, balance deduction, unique claim, double submission. Whether a unique constraint, a transaction or a lock enforces the invariant rather than a read followed by a write.
21. Payment flows. Amount authority server side. Currency validated. Webhook signature verified and the payload not trusted before verification. Idempotency on capture and refund. Order state machine that cannot be advanced by a client call. No secret key in client code.
22. Webhooks. Signature verification before parsing. Timestamp freshness. Replay protection through an idempotency record. Failure behaviour that does not acknowledge unprocessed events. No trust in payload fields that should be re-fetched from the provider.
23. Rate limiting and abuse. Login, password reset, signup, invitation, search, expensive endpoints, and anything that sends mail or costs money. Whether the limit is per identity, per address, or both, and whether it works across instances.
24. Account enumeration and disclosure. Login, signup, password reset and invitation flows returning different messages, status codes or response times for existing and non existing accounts.
Standing checks
Dependencies: run the project's audit command, read the advisories, judge reachability, upgrade or record. Infrastructure configuration visible in the repository: container user, exposed ports, debug flags, default credentials, permissive storage policies.
3. Protocol
- Declare the scope.
- Build or reuse the boundary map from
project-exploration. - Walk the twenty four points against that map, skipping only those with no surface, and record why each was skipped.
- For each finding: attacker path, precondition, impact, evidence.
- Rank by exploitability times impact, using
resources/severity-rubric.md. - Fix in code everything that code can fix. Verify each fix with a test that fails before and passes after.
- Separate what code cannot fix into the manual action list, with the exact command or console step.
- Re-run the affected checks.
- Report using section 5.
4. Fixed versus manual
Two lists, never merged.
Fixed in code
file, line, what changed, the test that proves it
Manual action required
what to do, where, why code cannot do it, and the consequence of not doing
it. Example: rotate the leaked key in the provider console, set the
environment variable in the deployment platform, enable the storage bucket
policy, restrict the database network access.
A leaked secret is always a manual action even after it is removed from the code, because deletion does not undo exposure. Say rotate, not remove.
5. Report format
Scope: apps/api auth and billing, revision abc1234
Points checked: 24, skipped 3 with reasons
critical lib/orders.ts:34 amount taken from the request body
Path: POST /api/checkout with amount 1
Impact: any order for one cent
Fixed: amount computed from the cart server side
Verified: npm test -- checkout, tampered amount rejected
high app/api/files/[name]/route.ts:12 path traversal
Path: GET /api/files/..%2f..%2f.env
Impact: read any file the process can read
Fixed: resolved path asserted inside the uploads root
Verified: npm test -- files, traversal case returns 400
medium no rate limit on POST /api/auth/reset
Impact: mail flooding and enumeration by timing
Fixed: limiter on the existing Redis client, 5 per hour per address
Manual action required
1 Rotate STRIPE_SECRET_KEY. It appears in commit 4f2a1c from March and is
therefore compromised regardless of the code fix.
2 Set SESSION_SECRET in the production environment. The code now refuses to
start without it instead of falling back to a default.
Not checked: mobile client, outside this repository.
6. Prohibitions
- Never state that a project is fully secure, hardened or without vulnerabilities.
- Never report a theoretical issue without an attacker path.
- Never fix a symptom while leaving the same class present elsewhere without saying so.
- Never write a proof of concept that exfiltrates real data.
- Never include a real secret in the report, the tests or the commit.
- Never silence a scanner to make a check pass.
- Never treat a passing dependency audit as an audit of the code.
7. Auto-critique
Score from 0 to 5: scope declared and respected, coverage of the applicable points, attacker path quality, evidence, correctness of the ranking, fixes verified, clean separation of manual actions, absence of overclaiming.
Threshold: no axis below 3, average at least 4. A finding without an attacker path is removed before delivery, not downgraded.
8. Interfaces
- Upstream:
project-exploration,input-validation. - Lateral:
backend-engineeringandfrontend-engineeringfor the fixes,debuggingfor exploitability. - Downstream:
testing-qualityfor regression tests,code-review-protocol,project-continuity,release-readiness.