- [ ]`npm run verify` passes guard `20-secrets`. It runs `scripts/secrets.sh --tracked`, which proves nothing credential-shaped is committed, and `--built dist/` over the bundle the build just made, which proves the *bundle* is clean, something the tracked scan cannot tell you
- [ ]`bash scripts/check-env.sh --file .env` exits 0 — proves every variable the server reads is set and shaped right, before it reads them. **Exit 2 is not a pass**
- [ ]`npm audit` shows no high or critical advisory in production dependencies — proves no known-exploitable code ships
- [ ]`bash scripts/preflight.sh` is clean against **both** front doors — `queuenorth.com` by default and `PREFLIGHT_ORIGIN=https://qn.isnull.dev` for the other. Proves headers, CSP and TLS survived the deploy on two separate ingresses that can rot independently
- [ ] The reCAPTCHA key in `dist/` is the **site** key, not the secret key — proves the one inlined value is the one that is safe to inline
## Standing checks
- [ ] No secret in the repository, in a log line, or in an error message
- [ ] Every externally reachable endpoint is either authenticated or deliberately public, and the deliberate ones are listed — they are, in `SECURITY.md`: `/api/health`, `/api/leads`, `/api/support`, and static files
- [ ] Every input that reaches a query or a filesystem path is validated at the boundary — Zod, then `sanitizeString()`, then a bound prepared statement
- [ ] Dependencies audited, and any accepted advisory recorded with a reason
### What the browser is handed
- [ ] No secret in the built client bundle, not merely none in the repository — proves the scan reached the artifact users actually receive
- [ ]*(only for a deployed service)* Response headers carry a CSP and a frame policy, and nothing is served over plain HTTP — proves clickjacking and injection meet resistance
> **The authorisation group and the session-token row were deleted from this
> list, not left unticked.** This product has no login, no session, no cookie and
> no role — see `SECURITY.md`. Three boxes that can only ever be ticked
> vacuously teach a reader that the boxes do not mean anything.
### What a stranger can learn or exhaust
- [ ]*(precautionary)* The two POST endpoints are rate-limited, and the limit has been exercised rather than assumed — proves a bot cannot run the form unattended overnight
- [ ] The honeypot field and reCAPTCHA scoring both actually reject, tested by bypassing them — proves the anti-abuse controls are enforced server-side and not merely present
- [ ] No endpoint returns a stored lead or support request — proves the database cannot be read back out over HTTP
> The account-enumeration row from the template is gone with the accounts.
> `preflight.sh --auth` exists and is deliberately never run here for the same
> reason.
### The compliance bar, which is not the launch bar
- [ ] There is a record of who changed what, and when — proves the question an auditor or a customer eventually asks can be answered at all
- [ ]*(only for a deployed service)* There is an environment that is not production to test against — **there is not.**`npm run docker:test` runs the image locally, which is close, and `qn-website-dev` on nebula is both the only deployment and the live site
The last two are a different bar from everything above them. The rest of this
list gets a release out of the door; those two get it through the first
compliance review.
## What got past this list
Add an entry whenever a real finding was not caught here, and then add the check
that would have caught it. A checklist that never grows is one nobody is honest
with.
| When | What was missed | The check now added |
| --- | --- | --- |
| 2026-05-11 → 2026-06-14 | The Zoho WebToLead tokens `xnQsjsdp` and `xmIwtLD` were hardcoded in `index.html` and later `src/pages/Contact.jsx`, and reached four commits on what was then a **public** repository, before being moved to environment variables at `05b27d2`. Low severity — they are public-by-design form identifiers a browser renders anyway — and deliberately **not** rewritten out of the history, which is now private | `scripts/secrets.sh` in the `pre-commit` hook, scanning the staged diff. This is the finding that makes the hook worth having in a project with no test suite to run beside it |
| 2026-08-18 | Nobody had ever checked whether the reCAPTCHA **secret** key had reached git. It had not — zero commits, zero tracked files — but "we would have noticed" is not a check | `secrets.sh --tracked` is now the first thing run in a fresh clone, per `docs/TOOLS.md` |
| 2026-08-18 | The live lead database had no backup and no restore had ever been attempted, and nothing in any document said so | `scripts/backup.sh` and `scripts/restore-check.sh`, **both run against production the same day**: a verified snapshot was taken from the running container and replayed into a scratch database — 2 tables, 3 rows, under a second. `docs/OPERATIONS.md` now carries a real *Last verified restore* date. What is still missing is a schedule and an off-machine copy, tracked in `Batch 15` |
| 2026-09-10 | `scripts/secrets.sh` had never run its private-key pattern: grep read the leading dashes as an option and `2>/dev/null` hid the error, so a staged private key passed the pre-commit hook. Its NAME=value pattern matched nothing at all until 2026-08-29, and missed quoted values until 2026-09-10. Nothing ran the bundle scan this checklist asked for | Every pattern is compiled before the scan, and an unreadable one exits 2 instead of passing; every grep takes `-e`; quoted values are caught; a documentation line is excused only by a visible `secrets-ok:` note; guard `20-secrets` now scans `dist/` as well (#235) |