Security Checklist

You are running a security review of: $ARGUMENTS

This is a rapid surface-area scan, not a full penetration test. It catches the common, high-impact issues. Run as part of code review, before any feature ships to production, and after any significant change to authentication, authorization, or data access code.


How to Use This Checklist

For each item, mark:

  • Pass — Confirmed safe, with evidence
  • Fail — Confirmed vulnerable, create a task immediately
  • ⚠️ Review — Cannot confirm without more context, investigate further
  • N/A — Not applicable to this code path

1. Input Validation

  • [ ] All user inputs are validated before any processing, database write, or downstream call
  • [ ] Data types are enforced: numbers reject strings, emails are verified as valid format, dates are parsed strictly
  • [ ] Length limits are enforced: no accepting unbounded strings where a maximum length is known
  • [ ] File uploads: file type validated by content (MIME type + magic bytes), not just the extension
  • [ ] File uploads: stored outside the web root with a random generated filename
  • [ ] Numeric inputs: negative numbers, zero, and values at limits are handled explicitly

Orientation commands:

grep -rn "\\$_GET\|\\$_POST\|\\$_REQUEST\|request\.\(body\|query\|params\)\|req\.body\|req\.query" \
  --include="*.{php,js,ts,py,go,rb}" . | grep -v node_modules | grep -v vendor | head -40

2. Output Encoding

  • [ ] HTML output is escaped: all user-controlled data rendered into HTML uses the template engine's auto-escaping
  • [ ] Raw HTML rendering (v-html, dangerouslySetInnerHTML, {!! !!}, |safe) is absent or justified with a comment
  • [ ] JSON responses use proper serialisation, never string concatenation
  • [ ] SQL values are never concatenated into queries — parameterised statements or ORM only
  • [ ] Shell commands: no exec(), system(), shell_exec() with user input; if unavoidable, input is whitelisted not escaped

Orientation commands:

# Check for raw HTML output
grep -rn "dangerouslySetInnerHTML\|v-html\|{!!\|\.html(\|innerHTML\s*=\|raw(" \
  --include="*.{php,js,ts,vue,jsx,tsx}" . | grep -v node_modules | grep -v vendor

# Check for SQL string building
grep -rn '"\s*\.\s*\$\|query.*\+\|".*SELECT.*"\s*\.' \
  --include="*.{php,js,ts,py}" . | grep -v node_modules | grep -v vendor | head -20

3. Authentication

  • [ ] All protected routes check authentication — no route that should require login is accessible without a valid session/token
  • [ ] Session IDs regenerated on login (prevents session fixation)
  • [ ] Tokens are validated on every request, not just stored in a cookie and trusted
  • [ ] JWT tokens: algorithm is explicit and verified (never accept alg: none)
  • [ ] Password hashing uses bcrypt, argon2, or scrypt — never MD5, SHA1, or unsalted SHA256
  • [ ] Brute-force protection on login: lockout, rate limit, or exponential back-off
  • [ ] Logout invalidates the server-side session, not just the client cookie
  • [ ] Remember-me tokens are hashed before storage (token theft = account takeover)

Orientation commands:

grep -rn "password\|hash\|bcrypt\|argon\|md5\|sha1\|sha256" \
  --include="*.{php,js,ts,py,go,rb}" . | grep -v node_modules | grep -v vendor | head -30

4. Authorisation

  • [ ] Every action checks ownershipWHERE user_id = :current_user_id or equivalent on every data-modifying query
  • [ ] Authorisation happens at the data layer, not only at the UI (no "the button is hidden, so it's fine")
  • [ ] Role/permission checks are consistent — the same check is used everywhere the resource is accessed
  • [ ] Indirect object references: changing a record ID in the URL cannot access another user's record
  • [ ] Mass assignment protection: API does not accept and apply arbitrary user-supplied field names to models
grep -rn "->find(\|findOrFail\|->first(\|->get(" \
  --include="*.{php,js,ts}" . | grep -v node_modules | grep -v vendor | head -30
# Check each result: is the result then scoped to the current user?

5. Secrets Management

  • [ ] No secrets in source code: no API keys, passwords, private keys, or tokens as literals
  • [ ] .env files are in .gitignore and never committed
  • [ ] .env.example exists with placeholder values, not real values
  • [ ] No secrets in git history: check recent commits and file history for sensitive files
  • [ ] Secrets are loaded from environment variables, not from config files that are committed
# Hunt for secrets
grep -rn -E "(password|secret|token|api.?key|private.?key)\s*[=:]\s*['\"][^'\"]{6,}" \
  --include="*.{php,js,ts,py,go,rb,json,yaml,toml,env}" . | grep -v node_modules | grep -v vendor | grep -v ".git"

# Check git history
git log --all -p --follow -- "**/.env" | grep "^+" | grep -v "^+++" | head -30

6. Error Handling

  • [ ] Stack traces are never exposed to the end user in production
  • [ ] Error messages are generic on the client side but specific in server logs
  • [ ] Exception handlers exist at the top level — no unhandled exceptions produce debug output
  • [ ] 404 responses do not reveal whether the resource exists vs. user is not authorised (returns 404 in both cases for sensitive resources)
  • [ ] Account enumeration is prevented — login error messages say "invalid credentials", not "no user with that email"

7. HTTP Security Headers

Check that the following headers are sent on all HTML responses:

Header Required value Risk if missing
Content-Security-Policy Restrictive policy XSS, data injection
X-Frame-Options DENY or SAMEORIGIN Clickjacking
X-Content-Type-Options nosniff MIME sniffing attacks
Strict-Transport-Security max-age=31536000; includeSubDomains Downgrade attacks
Referrer-Policy strict-origin-when-cross-origin Data leakage via referrer
# Check headers on a running instance
curl -I https://your-app.com/ | grep -iE "content-security|x-frame|x-content-type|strict-transport|referrer-policy"

8. Dependencies

  • [ ] Dependency audit run recently with no known vulnerabilities at High/Critical severity
  • [ ] Lock files committed (package-lock.json, composer.lock, etc.)
npm audit --audit-level=high
composer audit
pip-audit
go list -json -m all | govulncheck

9. CSRF Protection

  • [ ] State-changing requests (POST, PUT, PATCH, DELETE) are protected against CSRF
  • [ ] CSRF tokens are present in forms or verified via SameSite=Strict cookies
  • [ ] API endpoints that use cookie authentication validate the Origin or use CSRF tokens

Output Format

## Security Checklist Results — [Module / Feature Name]
**Date:** YYYY-MM-DD

| # | Category | Item | Status | Notes |
|---|---|---|---|---|
| 1 | Input Validation | All inputs validated | ✅ | Validated via FormRequest |
| 2 | SQL | No string concatenation in queries | ✅ | ORM only |
| 3 | Auth | Session regenerated on login | ❌ | Missing Auth::login() rotation |
| 4 | Secrets | No secrets in source | ⚠️ | Check git history for old .env commit |

## Critical Issues (must fix before shipping)
1. [Issue description, file:line, fix]

## Recommended Fixes
1. [Issue description, file:line, fix]