SHIP-SAFE
The security checklist for apps built with AI.
Before you trust this
AI helps you build fast — but it can quietly leave your app open to leaks, fraud, or takeover. SHIP-SAFE catches the most common holes. It does not guarantee you are secure, and it is not a substitute for a professional audit. Treat it as a floor, not a ceiling — and re-check every single time you ship a change.
Two ways to run the checklist
Walk the 33 checks yourself — or hand the work to the same AI you built with. Every category has a copy-paste prompt, plus one master prompt that runs all of it.
Tick your way down
Go category by category and check off each item. Your progress is saved on this device, so you can come back to it.
Paste a prompt
Drop the master prompt (or a single category prompt) into Claude, Cursor, Lovable, or your editor's assistant, and have it audit your code and report pass / fail with fixes.
- S1No secrets in frontend code or your repo.API keys, tokens, service/admin keys, database URLs — none of it ships to the browser or sits in git.
- S2Rotate anything that was ever exposed.Committed once = leaked forever. Rotate any key that touched git history, a screenshot, or a log.
- S3Least-privilege keys, per environment.Separate dev and prod keys. The browser only ever holds a public/anon key — never the service key.
- H1Use a vetted auth provider — don't roll your own.Supabase Auth, Auth0, Clerk, and friends. Custom login and hand-rolled crypto is where the subtle holes live.
- H2Strong passwords + a breach check.Enforce a minimum length and reject known-breached passwords (HaveIBeenPwned).
- H3MFA on admin and money actions.A stolen password alone should never be enough to move funds or reach an admin panel.
- H4Secure sessions.Cookies HttpOnly + Secure + SameSite; tokens expire; no long-lived tokens sitting in localStorage.
- H5Abuse-proof reset & magic links.Single-use, short expiry, rate-limited — or account takeover is one email away.
- I1Validate every input on the server.Type, range, and length enforced server-side — not only in the form the user can bypass.
- I2Parameterized queries only.Never build SQL or shell commands by concatenating input. This is how injection happens.
- I3Escape your output.Anything user-supplied that renders to a page is escaped or sanitized. This is how XSS happens.
- I4Safe file uploads.Limit type and size, store outside the web root, and never execute an uploaded file.
- P1Row-level security on every table.Each user can read and write only their own rows. On by default, everywhere.
- P2No public / USING(true) policies on sensitive data.An always-true policy is a public database wearing a lock that isn't shut.
- P3Authorize on the server, not the UI.Every sensitive action re-checks permission on the server or database, no matter what the front-end shows.
- P4Lock functions & endpoints to a role.Nothing that moves money or admin data is callable by anonymous users. Watch default-public grants.
- P5The client uses a restricted key.The browser never holds the service or admin key — only a scoped, public one.
- $1Amounts and balances computed server-side.Never trust a price, total, or balance that arrived from the client.
- $2Guard against double-spend and double-credit.Idempotency keys, unique constraints, and row locks — so a retry or a race can't pay twice.
- $3Caps default to the safe value.A missing limit should mean the restrictive tier — never “unlimited”.
- $4Never trust client-supplied ownership.The server decides who owns what — it doesn't read it from a field in the request body.
- A1Rate-limit the sensitive stuff.Logins, password resets, and money actions — throttled per user and per IP.
- A2Bound your AI features.If a user can trigger a paid model, cap the cost and guard against prompt-injection leaking data or taking actions.
- A3Protect public forms & signups.Throttle or add a challenge (CAPTCHA) so bots can't mass-create accounts or flood you.
- F1Verify your webhooks.Check the signature, reject replays, and fail closed if the signing secret is missing.
- F2Block SSRF.If your server fetches a user-supplied URL, refuse internal and cloud-metadata addresses.
- F3No * CORS on private endpoints.Reflect an explicit allowlist of origins, not “any site on the internet”.
- F4HTTPS & secure headers everywhere.No plaintext traffic, and no missing transport-security or content-type protections.
- E1Patch your dependencies.Track and update known-vulnerable packages instead of pinning and forgetting.
- E2No debug endpoints in production.Remove test, diagnostic, and backdoor routes before you launch — they never stay secret.
- E3Log security events — never secrets or PII.Record the suspicious ones and make sure at least one of them reaches a human.
- E4Back up + have a break-glass plan.Know exactly how to revoke and rotate credentials the moment something leaks.
- E5Re-run SHIP-SAFE every time you ship.Every new feature is new attack surface. The checklist is only worth anything if you come back to it.
Hand it to your AI
Paste the master prompt to audit everything at once, or open a single category below. They're written to be read-only — the AI reports and recommends, it doesn't touch your data.
You are a security reviewer auditing my application against SHIP-SAFE, a security baseline for AI-built apps. Review the codebase against all 8 categories and 33 checks below. For EACH check, report: PASS / FAIL / N-A, the specific evidence (file, line, or config), and — if FAIL — the exact fix. Rules: work READ-ONLY. Do not run anything that changes data, deletes records, or moves money. Rank findings by severity (money and data exposure first). End with a prioritized fix list. S — SECRETS: (S1) no keys/tokens/service keys in frontend or repo; (S2) rotate anything ever committed; (S3) least-privilege keys per env, browser holds only a public key. H — HARDEN ACCESS: (H1) vetted auth provider, no custom crypto; (H2) min password length + breached-password check; (H3) MFA on admin/money; (H4) HttpOnly+Secure+SameSite cookies, expiring sessions; (H5) reset/magic links single-use, expiring, rate-limited. I — INPUT/OUTPUT: (I1) server-side validation of all input; (I2) parameterized queries only; (I3) escaped output (no XSS); (I4) file uploads limited by type/size, never executed. P — PERMISSIONS: (P1) RLS on every table; (P2) no public/USING(true) on sensitive data; (P3) authorization on server, not UI; (P4) money/admin functions & endpoints not anon-callable; (P5) client uses a restricted key. S($) — SAFEGUARD MONEY: ($1) amounts/balances server-computed; ($2) double-spend/credit guards (idempotency, unique keys, locks); ($3) caps default to safe/restrictive; ($4) server derives ownership, never trusts a client field. A — ABUSE: (A1) rate-limit logins/resets/money per user and per IP; (A2) bound AI cost + prompt-injection defense; (A3) throttle/CAPTCHA public forms & signups. F — FORTIFY CONNECTIONS: (F1) verify webhook signatures, reject replays, fail closed; (F2) block SSRF to internal/metadata addresses; (F3) no `*` CORS on private endpoints; (F4) HTTPS + secure headers. E — EYES ON: (E1) patch dependencies; (E2) no debug/test endpoints in prod; (E3) log security events (no secrets/PII) + alert; (E4) backups + revoke/rotate plan; (E5) note that this must be re-run on every change.
SSecrets
Scan my entire repo AND the built frontend bundle for hardcoded secrets: API keys, access tokens, service/admin keys, database URLs, private keys, signing secrets. List every match with file and line, tell me which are shipped to the browser, and give me a safe way to move each into a server-side environment variable. Also scan git history and flag anything ever committed so I can rotate it.
HHarden access
Review my authentication and sessions. Confirm: I use a vetted auth provider (no custom login/crypto); passwords have a minimum length and reject breached passwords; admin and money actions require MFA; session cookies are HttpOnly + Secure + SameSite with a sane expiry and no long-lived tokens in localStorage; password-reset and magic links are single-use, expiring, and rate-limited. Report each as PASS/FAIL with the exact fix.
IInput & output
Check input and output safety. Confirm every user input is validated and bounded on the server (not just the form); all database access is parameterized (no SQL or shell built from input); all user-supplied content rendered to HTML is escaped or sanitized (no XSS); and file uploads are limited by type and size, stored outside the web root, and never executed. List every endpoint or component that fails and how to fix it.
PPermissions
Audit authorization. If I use Supabase/Postgres: confirm RLS is enabled on EVERY table, no policy uses USING(true) on sensitive data, and users can only read/write their own rows. Then confirm every sensitive action is authorized on the server/DB (not just hidden in the UI), every RPC/function/endpoint touching money or admin data is locked to the correct role (not anon-callable — watch default PUBLIC grants), and the browser uses a restricted key, never the service key. PASS/FAIL + fix for each.
SSafeguard money
Review money handling. Confirm amounts and balances are computed server-side and never trusted from the client; there are guards against double-spend and double-credit (idempotency keys, unique constraints, row/advisory locks); limits and caps default to the safe restrictive value (never unlimited on a missing config); and no server code trusts a client-supplied owner, price, or identity field. Walk each money path and show me its guards, or where they are missing.
AAbuse & rate control
Check rate-limiting and abuse controls. Confirm logins, password resets, money actions, and any paid AI calls are rate-limited per user AND per IP (and that the IP source cannot be trivially spoofed or collapsed behind a proxy). If my app has an AI feature, confirm it cannot be prompt-injected into leaking data or taking unauthorized actions and that its cost is bounded. Confirm public forms and signups are throttled or challenged. List anything unbounded.
FFortify connections
Review integrations and network edges. Confirm every incoming webhook verifies its signature, rejects replays, and fails closed when the secret is missing; the server cannot be tricked into fetching internal or cloud-metadata URLs (SSRF) from user-supplied links; CORS is not "*" on authenticated endpoints and uses an explicit origin allowlist; and HTTPS plus secure response headers are enforced. PASS/FAIL + fix for each.
EEyes always on
Check ongoing hygiene. List outdated or known-vulnerable dependencies and the safe upgrade. Find any debug, test, diagnostic, or admin endpoints still reachable in production. Confirm security-relevant events are logged without logging secrets or PII, and that suspicious ones would actually alert a human. Confirm backups exist and there is a documented way to revoke and rotate every credential if breached.
The check that matters most
SHIP-SAFE isn't a one-time gate you pass and forget. The single most important habit is E5: run all 33 again every time you ship a feature. AI writes new code fast, and every new endpoint, table, or button is new attack surface. A checklist you ran once, six versions ago, is protecting a version of your app that no longer exists.
SHIP-SAFE is free and always will be. If it saved your app from a bad day, you can throw the maintainers a coffee.
☕ Buy me a coffee