# Production Security Checklist

> Use when shipping or auditing a web app for production. Runs a 14-point security and data-lifecycle audit — per-user authorization (IDOR), password-reset expiry, input sanitization (SQLi/XSS), API exposure, rate limiting, safe error handling, DB indexes, logging and alerting, blue-green rollback — plus GDPR/PDP-safe account deletion via cryptographic erasure.

- Skill: `mufidhadi/production-security-checklist` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add mufidhadi/production-security-checklist`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mufidhadi/production-security-checklist/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- License: MIT
- Author: mufidhadi (https://skillmd.com/u/mufidhadi)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mufidhadi/production-security-checklist

---


# Production Security Checklist

## Overview

A 14-point audit for a web app or API before it goes to production — and again
whenever a release touches authentication, data access, storage, or backups.

Every check states the **evidence** that passes it, so the agent (or the reviewer)
can produce a verdict per item instead of an opinion. Checks 1–9 cover the
pre-deploy surface; checks 10–14 cover account deletion and the data that survives
it (backups, replicas, analytics, third parties).

## When to Use

- Deploying a new web app / API to production, or promoting a release candidate.
- Reviewing any change to auth, authorization, storage, or backup handling.
- Answering "is this production-ready?" before a handover, demo, or audit.
- Designing account deletion, data retention, or GDPR / UU PDP compliance.

Don't use for: host/OS hardening, cluster policy, network segmentation, or
full penetration-testing scope. This checklist is application-layer.

## How to Run It

1. Walk checks 1–9 against a running environment (staging is fine, production
   data is not needed). For each check, record `pass`, `fail`, or `unknown` plus
   the evidence you actually saw — never mark `pass` from reading code alone when
   the check can be exercised.
2. Walk checks 10–14 against the data map: database, replicas, backups, object
   storage, logs, analytics, caches, and third-party processors.
3. Report failures with the smallest reproducible proof (request + response, or
   the query plan) and the fix. **Completion criterion: every one of the 14
   checks has a status and evidence, or is explicitly marked out of scope.**

## Part 1 — Before Deploy

### 1. Per-user authorization (IDOR)
**Ask:** if I'm logged in as A, can I reach B's data?
**Pass:** every read/write is scoped to the authenticated identity
(`WHERE owner_id = :current_user`), resource IDs are unguessable (UUIDv4, not
sequential), and a request for someone else's object returns 404/403.
**Fail:** editing an ID in the URL or request body returns another user's record.
**Fix:** enforce ownership in the data layer, not in the template or the client.

### 2. Password-reset link expiry
**Ask:** do reset (and invite / OTP / magic) links expire?
**Pass:** single-use, time-limited (15–30 minutes is typical), bound to the user,
invalidated by use and by any password change.
**Fail:** a link generated yesterday still resets the password today.

### 3. Sanitize and validate every field
**Ask:** is every field sanitized on the server?
**Pass:** parameterized queries or ORM bindings (no string-built SQL), output
encoding appropriate to context (HTML/JS/URL), and server-side validation on
every input, including query params, headers, webhook payloads, filenames, and
uploaded file metadata.
**Fail:** `' OR 1=1--` returns data, or a `<script>` payload is echoed unescaped.
Client-side validation alone is never a pass.

### 4. Don't expose the API to the whole world
**Ask:** is the API open to anyone?
**Pass:** requests are restricted to expected origins (CORS allowlist, no
`*` combined with credentials), internal/admin endpoints are restricted by
network or auth, and unused routes/methods are closed.
**Fail:** wildcard CORS with cookies, or an admin route reachable unauthenticated.

### 5. Rate limiting
**Ask:** are limits applied?
**Pass:** per-IP / per-user / per-key limits on authentication, write, and
expensive endpoints (search, export, report), returning 429 with `Retry-After`.
**Fail:** login, reset, or OTP can be hammered thousands of times unthrottled.

### 6. Safe error handling
**Ask:** what does a failure state look like to the client?
**Pass:** a custom error page or JSON body per failure state; no stack traces,
SQL, file paths, hostnames, or framework versions; detail goes to server logs only.
**Fail:** a 500 that leaks the stack, or a 200 response carrying an error payload
(monitoring will never fire).

### 7. Indexes on the main queries
**Ask:** do the hot queries use indexes?
**Pass:** filtered/joined columns on high-traffic queries are indexed, verified
with `EXPLAIN` (index scan, not sequential scan).
**Fail:** sequential scans on large tables — or the opposite extreme, every column
indexed, which pays write and storage overhead for nothing.

### 8. Logging and monitoring
**Ask:** how will you know it broke at 3 a.m.?
**Pass:** structured logs for auth events, admin actions, and errors; alerts on
critical failures (5xx rate, queue depth, repeated failed logins, cron silence).
**Fail:** the first signal is a user complaint.

### 9. Rollback plan
**Ask:** if this deploy crashes, can you roll back?
**Pass:** blue-green (or canary/rolling with the same property) — a bad release is
undone by a switch, without re-running migrations, and the previous version is
verified to still work against the current schema. The rollback is rehearsed and
timed, not theoretical.
**Fail:** "rollback" means restoring last night's backup.

## Part 2 — Account Deletion and the Data Behind It

### 10. Deletion is more than one `DELETE`
**Ask:** when a user deletes their account, is their personal data really gone?
**Pass:** erasure covers the production database **and** backups, replicas,
object storage, search indexes, caches, analytics/warehouse, log retention, and
third-party processors, within the retention window the policy promises.
**Fail:** data is deleted from production but still sitting in the last 30 days of
backups — that is a reportable GDPR / UU PDP gap, not a technicality.

### 11. Never erase by restoring, editing, and recompressing backups
**Ask:** how do you plan to remove one user from historical backups?
**Pass:** you don't touch the backups at all (see check 12–13).
**Fail:** a job that rehydrates, edits, and re-uploads terabytes per deletion
request — slow, expensive, and a great way to corrupt your own backups.

### 12. Encrypt sensitive data per user, under a per-user key
**Pass:** envelope encryption — a per-user data key encrypts that user's sensitive
fields; the key itself is wrapped by a key-encryption key held in a managed key
store (KMS/HSM/vault). Keys are never stored next to the ciphertext.
**Fail:** one global application key — destroying it would mean re-encrypting
everyone, which is not erasure.

### 13. On deletion, destroy the key — not the data
**Pass:** account deletion destroys the user's data key. Backups are untouched;
the ciphertext inside them becomes permanently undecryptable ("cryptographic
erasure" / crypto-shredding).
**Fail:** a deletion flow whose correctness depends on finding every copy of the
data across every store — that path fails silently as the system grows.

### 14. Prove the erasure
**Pass:** the erasure path is documented, key destruction is audit-logged
(who, when, which subject), and a restore test confirms the deleted user's data is
unreadable after backup recovery.
**Fail:** no evidence trail — an auditor asking "show me" gets a shrug.

Deep dive on checks 12–14, key hierarchy, rotation, and the restore test:
see `references/cryptographic-erasure.md`.

## Common Pitfalls

1. Treating "logged in" as "authorized". Authentication is not authorization.
2. Sanitizing only the fields you remember — headers, webhook payloads, and file
   metadata are inputs too.
3. Client-side validation counted as validation.
4. Rate limiting only login, forgetting reset, OTP, signup, search, and export.
5. Error responses that return 200 with an error body — alerts never trigger.
6. Indexing every column instead of the ones the hot queries actually use.
7. Treating backups as out of scope for deletion requests.
8. One global encryption key, which makes "destroy the key" impossible without a
   full re-encryption.
9. Verifying authorization from the UI only — test with two real accounts, both
   directions (A→B and B→A), including direct API calls.
10. Sketching a rollback plan that has never been executed once.

## Verification Checklist

- [ ] Cross-account access tested with two users; returns 403/404
- [ ] Reset / OTP / invite tokens are single-use and time-limited
- [ ] All database access parameterized; server-side validation on every field
- [ ] Origin allowlist in place; no wildcard CORS with credentials
- [ ] Rate limits active on auth, write, and expensive endpoints
- [ ] Error responses leak no internals; alerts wired to 5xx and cron silence
- [ ] Hot queries verified with `EXPLAIN` (index scan, not sequential scan)
- [ ] Rollback rehearsed and timed on a real environment
- [ ] Account deletion covers backups, replicas, analytics, logs, third parties
- [ ] Per-user keys live in a managed key store; destruction is audit-logged
- [ ] Restore test passed: recovered backup yields unreadable deleted-user data

## Source

Distilled from two Instagram reels by **Mohamad Al Sayed** (software-engineering
security series) — one on pre-deploy hardening, one on account deletion and
backups. Rules restated and expanded with pass/fail evidence and verification
criteria. Original reels: `instagram.com/reel/Dc0b0K-thYr`,
`instagram.com/reel/DdB4g89NEbJ`.

