This is the floor, not the ceiling. Use the current OWASP Top 10 as the risk list and the OWASP ASVS as the detailed checklist; choose ASVS level 1 for simple sites, level 2 for anything with accounts, payments or personal data.
Threat model
Section titled “Threat model”- Any feature touching accounts, payments, personal data, file uploads, admin functions or LLM tools MUST have a short threat model in the spec: what is valuable, who might attack it, how, and what stops them.
Authentication
Section titled “Authentication”- Prefer a proven provider or framework auth over custom code. Never write your own password hashing or session crypto.
- Passwords hashed with a modern slow algorithm (Argon2id, scrypt or bcrypt) through the framework.
- Support multi-factor authentication for admin accounts at minimum.
- Session cookies:
Secure,HttpOnly,SameSite=LaxorStrict; rotate the session on login and privilege change; expire idle sessions. - Login, sign-up and password reset are rate-limited and do not reveal whether an account exists.
Authorisation
Section titled “Authorisation”- Deny by default. Every endpoint and page declares who may access it.
- Check permission per object on the server (does this user own this order?), not only per route.
- Never trust IDs, roles or prices sent from the client.
Input and output
Section titled “Input and output”- Validate every input on the server against a schema (type, length, format, allowed values).
- Use parameterised queries or the ORM; never build SQL or shell commands by string concatenation.
- Encode output for its context (HTML, attribute, URL, JS). Use the framework’s auto-escaping; treat any “raw HTML” escape hatch as a security review item.
- Sanitise user-supplied rich text with an allowlist sanitiser.
- Server-side requests to user-supplied URLs MUST use an allowlist and block internal addresses (SSRF).
Transport and headers
Section titled “Transport and headers”- HTTPS everywhere; HSTS enabled.
- A Content Security Policy that avoids
'unsafe-inline'and'unsafe-eval'for scripts (use hashes or nonces). X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin, a restrictivePermissions-Policy, andframe-ancestors 'none'(or a defined allowlist).- CORS allows only named origins; never
*with credentials. - CSRF protection on every state-changing form or cookie-authenticated request.
Secrets
Section titled “Secrets”- Secrets live in the platform’s secret store or environment variables, never in the repo, client bundle, logs, prompts or screenshots.
.envfiles are git-ignored;.env.exampleholds names only.- Rotate any secret that was exposed, even briefly, and record it.
- Use separate credentials per environment with least privilege.
- Validate uploads by content type and size, store them outside the web root or in object storage, rename them, and serve with safe headers. Scan where risk justifies it.
- Rate-limit authentication, forms, search, expensive endpoints and LLM calls.
- Bot protection on public forms where spam is likely.
Dependencies and patching
Section titled “Dependencies and patching”Checklist
Section titled “Checklist”- Threat model for sensitive features
- Framework auth, MFA for admins, secure cookies
- Deny-by-default, per-object authorisation
- Schema validation; parameterised queries; context encoding
- CSP without unsafe-inline scripts; security headers set
- No secrets in repo, bundle, logs or prompts
- Rate limits on auth and expensive endpoints