# OWASP review checklist

Verified 2026-09 against [OWASP Top 10:2025](https://top10.owasp.org/2025) and [OWASP API Security Top 10 2023](https://owasp.org/API-Security/editions/2023/en/0x11-t10/). Work the categories against the scope; each row is "what to look for in the code" and "the fix that holds".

## OWASP Top 10:2025

| Category | Look for | Fix |
|---|---|---|
| **A01 Broken Access Control** | Lookups by id alone after an authentication-only check; role checks without an object-owner check; authorization decided in the client or in the UI; `[AllowAnonymous]`, commented-out guards, missing middleware on a new route; IDs that are sequential and enumerable; path traversal into file reads | Filter every query by owner or tenant; enforce with a policy or guard at the router, not per handler; return 404 rather than 403 where existence itself leaks; add a test that uses another user's id |
| **A02 Security Misconfiguration** | Debug or profiling endpoints reachable in production; stack traces returned to the caller; `Access-Control-Allow-Origin: *` together with credentials; default credentials; permissive storage or bucket policies; verbose error middleware added "temporarily"; TLS verification disabled | Environment-specific config with a secure default; explicit CORS origin list; generic error body plus a correlation id; keep the diagnostic behind a flag that is off by default |
| **A03 Software Supply Chain Failures** | New dependencies, version jumps, changed registry or git sources in a lockfile; postinstall scripts; vendored code pasted in; unpinned actions or tasks in pipelines | Pin with a committed lockfile from the official registry; justify each new dependency in the PR; review transitive additions; see `pipeline-and-supply-chain.md` |
| **A04 Cryptographic Failures** | Hardcoded keys, tokens, connection strings in code, config, tests or fixtures; MD5/SHA-1 for passwords; ECB mode; static or reused IVs; `Math.random()` for tokens; secrets in log lines or error messages; sensitive data stored unencrypted | Secrets from a vault or pipeline secret variables; Argon2, bcrypt or scrypt for passwords; AEAD modes with a per-message nonce; CSPRNG for anything security-relevant; rotate anything that was committed |
| **A05 Injection** | String-built SQL, shell commands, LDAP or XPath filters; ORM raw-query escapes; template expressions built from input (SSTI); `innerHTML`, `dangerouslySetInnerHTML`, `v-html`, `bypassSecurityTrust*` fed by user data; NoSQL operators accepted from the body (`{"$gt": ""}`) | Parameterised queries and bound arguments; argument arrays instead of shells; context-aware output encoding; sanitise HTML with a maintained sanitiser; reject operator objects at the boundary |
| **A06 Insecure Design** | Security-relevant logic with no negative test; a flow that trusts a client-supplied price, role, quantity or state; missing rate limit or lockout on authentication, reset and payment flows; secrets recoverable from a support path | Model the abuse case and encode it as a test; recompute trusted values server-side; add throttling and lockout where money, identity or messages leave the system |
| **A07 Authentication Failures** | JWTs decoded without verifying signature, issuer, audience and expiry; `alg: none` accepted; tokens in URLs or local storage when a cookie fits; no rotation on privilege change; password reset tokens that are long-lived, guessable or reusable; missing `HttpOnly`, `Secure`, `SameSite` | Verify the whole token with the library's verify call, never decode-and-trust; short-lived tokens with rotation; single-use, expiring reset tokens; identical responses for known and unknown accounts |
| **A08 Software or Data Integrity Failures** | Deserialization of untrusted data into arbitrary types (`BinaryFormatter`, `pickle`, unsafe YAML, Java native); auto-update or plugin loading from an unverified source; unsigned artifacts promoted between environments; mass assignment binding whole request bodies onto entities | Data-only formats with an explicit schema; allow-list the bound fields (DTOs); verify signatures and digests on anything downloaded or promoted |
| **A09 Security Logging and Alerting Failures** | No log on authentication failure, authorization denial or privilege change; secrets, tokens or personal data written to logs; user input written unescaped into logs (log injection); no alert on a security-relevant event | Log the security event with an opaque actor id and correlation id; redact secrets and personal data; sanitise newlines from logged input; say which events should alert |
| **A10 Mishandling of Exceptional Conditions** | `catch {}` that swallows a failed security check; a failure path that falls through to "allow"; error messages exposing SQL, paths or internals; resources (files, connections, locks) leaked on the error path; partial writes left uncommitted with no compensation | Fail closed: on error, deny; handle the specific exception, keep the internal detail server-side; release resources in a finally or using block; make the retry idempotent |

## OWASP API Security Top 10 2023

| Category | Look for | Fix |
|---|---|---|
| **API1 Broken Object Level Authorization** | Same as A01 at the object level — `GET /orders/{id}` with no owner comparison | Owner or tenant predicate in the query; test with a foreign id |
| **API2 Broken Authentication** | Weak or absent token verification, long-lived API keys, no lockout on credential endpoints | Verify fully; short-lived credentials; throttle |
| **API3 Broken Object Property Level Authorization** | Whole entity serialised back (internal flags, `isAdmin`, PII); whole body bound onto the entity | Explicit response DTOs and explicit bound fields, both directions |
| **API4 Unrestricted Resource Consumption** | No page size cap, no timeout on outbound calls, unbounded uploads or batch sizes, expensive queries reachable unauthenticated | Cap page size and batch size; time out and retry with a budget; limit upload size and rate |
| **API5 Broken Function Level Authorization** | Admin routes protected only by not being linked; verb-level gaps (`GET` guarded, `DELETE` not) | Deny by default at the router; test each verb |
| **API6 Unrestricted Access to Sensitive Business Flows** | Purchase, invite, reset or messaging flows with no automation defence | Rate limit per actor and per resource; add proof-of-work, approval or human checks where the flow has real-world value |
| **API7 Server Side Request Forgery** | Outbound requests built from user-supplied URLs; webhook targets, image fetchers, importers; redirects followed blindly | Allow-list hosts; resolve and reject private, loopback and metadata ranges; do not follow redirects across hosts; use a dedicated egress proxy |
| **API8 Security Misconfiguration** | As A02, plus missing security headers and unversioned error formats | Same as A02 |
| **API9 Improper Inventory Management** | `/v1` left running beside `/v2`; debug or internal endpoints shipped; undocumented routes added in the diff | Inventory and retire old versions; document every route the PR adds |
| **API10 Unsafe Consumption of APIs** | Third-party responses trusted without validation, sent straight into a sink or the UI | Validate and encode third-party data exactly like user input; time out and bound the size |

## Evidence rules

- Quote the smallest snippet that proves the finding, with `file:line`.
- Say who the attacker is (unauthenticated visitor, signed-in customer of another tenant, low-privilege employee), what they send, and what they get.
- If reachability depends on configuration you cannot see, put the finding under **Needs verification** with the exact check.
- Never print a full secret: file, line, first four characters, and "rotate".
