# Security checklist — walk this before promoting to prod

Two layers guard prod. The **automated gate** runs in every app's deploy
workflow (`scripts/security-check.mjs` — report-only on dev/staging,
BLOCKING on `main`, plus `npm audit --audit-level=critical`). This checklist
is the **human layer**: the things a scanner cannot see. Walk it with the
user in plain language before merging staging → main.

## What the automated gate already catches (don't re-litigate)

Committed secrets / a tracked `.env` / private keys · SQL built by
interpolation or concatenation · `eval` / `new Function` · shell exec with
interpolated input · critical dependency advisories. If the prod deploy
fails on the gate, read the Actions log — every finding prints its fix.
**Never remove or bypass the gate step to make a deploy pass.**

## The human checks (scanner-blind spots)

1. **Authorization, not just authentication.** Logged-in is not allowed.
   For every route that reads/writes data: could user A pass user B's id and
   touch B's data? Filter by the session's user id server-side, never trust
   an id from the request body.
2. **Every input validated at the edge.** Each POST/PATCH handler starts
   with a zod `safeParse`, with sane max lengths. No handler touches
   `await c.req.json()` raw.
3. **Secrets flow one way.** Values live in env vars (set via
   `awesomate_app_set_env`), are never logged, never echoed in responses,
   never sent to the browser.
4. **Rendering is escape-safe.** Browser code uses `textContent` /
   `createElement` for user data; server HTML never string-interpolates
   user input without escaping.
5. **The n8n webhook is not world-callable.** If n8n is attached, the
   workflow checks the `X-Awesomate-Webhook-Secret` header (set during
   attach). Test: call the webhook without the header — it must refuse.
6. **Public API surface is intentional.** api-only starter: is `API_KEY`
   set for prod, or is a public API genuinely intended? CORS open on
   purpose?
7. **Sensitive data is minimized.** Only collect what the app needs; if it
   stores personal data, tell the user plainly: connections are TLS and
   secrets are encrypted, but the DATABASE is not blanket-encrypted at
   rest — sensitive fields deserve app-level encryption (ask before adding).
8. **Errors don't leak.** 500 responses return a generic message, not stack
   traces or SQL (the starters already do this — keep it).

## Static sites

No server code, so the gate doesn't run — the risks are the lead form's
webhook (point 5) and third-party scripts (don't add any that aren't
needed). Never put an API key in page JavaScript: anything in a static page
is public.

## How to talk about it

Non-technical user — say what you checked, not the jargon: *"Before making
it live I checked that people can only see their own data, that the contact
form can't be abused, and that your API key never appears anywhere public.
The deploy pipeline also scans every release automatically."*
