Start here: don’t skip these
If the full list feels like a lot, do these 7 first. They cover the mistakes that most often end in a data leak, a hijacked account, or a surprise bill.
- Keep every API key and secret on the server, never in frontend code
- Use your database public key on the frontend, never the admin key that skips your security rules
- Turn on row-level security for every database table
- Confirm each user can only reach their own records, not just that they are logged in
- Rate-limit your API, especially login and anything that costs you money per call
- Set billing caps and alerts on every paid service
- Use parameterized queries so user input can never run as a command
Then work through the full list below when you have the time.
The full checklist
🔐 Secrets and keys
1. Keep keys and secrets on the server
Why it matters: If an API key lives in code that runs in the browser, anyone can open developer tools and copy it, then spend money on your accounts.
Audit my codebase for any API keys, secrets, or tokens exposed to the client or bundled into frontend code. Move every secret into server-side environment variables, and make sure the frontend only ever calls my own backend rather than calling a third-party API with a secret key. Show me each place you found a problem and how you fixed it.
2. Keep secrets out of your Git history
Why it matters: Once a secret is committed, it stays in your Git history even after you delete it, and bots scan public repos for exactly this.
Check whether any secrets, API keys, or .env files have ever been committed
to this repository, including in older commits. Make sure .env and all secret
files are in .gitignore. If anything sensitive is already in the history, tell
me exactly what it is and walk me through rotating those keys and removing them.
3. Use your public database key on the frontend, not the admin key
Why it matters: A lot of vibe-coded apps accidentally use the admin database key on the client, which skips every security rule you set up, including row-level security.
Check which database key my frontend uses. Confirm it is the public or anon key
and never the service role or admin key that bypasses row-level security. If
the admin key is exposed anywhere on the client, move it to the server and
replace the client usage with the public key.
🧑🏻💻 Database
4. Turn on row-level security for every table
Why it matters: Without it, anyone who finds your database endpoint can pull every user’s data, not only their own.
Enable row-level security on every table in my database. For each table, write
policies so users can only read and change their own rows. Do not use allow-all
or USING (true) policies. List every table with the policy you applied, and
flag any table you were unsure about.
5. Encrypt sensitive data
Why it matters: If someone gets into your database and everything sits there in plain text, one leak exposes all of it.
Review how my app stores sensitive data such as personal information and tokens.
Make sure sensitive fields are encrypted at rest and never stored in a readable
form. Tell me which fields you changed and which ones you recommend I review.
⛔️ Auth and access control
6. Enforce authentication on the server
Why it matters: If your rules only live in the frontend, someone can skip them by calling your backend directly.
Check that authentication and authorization are enforced on the server for every
protected route and action, not just hidden in the frontend UI. Show me any
endpoint that trusts the client to say who the user is, and fix it so the
server verifies the user on every request.
7. Check that each user owns the record they are touching
Why it matters: A logged-in user who changes an ID in a request can often pull up someone else’s data if you only check that they are logged in.
Audit my app for broken access control. For every endpoint that reads or updates
a record by ID, confirm the code checks that the logged-in user actually owns
that record, not just that they are authenticated. Show me each place and the
ownership check you added.
8. Only accept the fields a user is allowed to change
Why it matters: If an update accepts any field, a user can add something like role=admin to a normal profile update and promote themselves.
Check my create and update endpoints for mass assignment. Make sure each one
only accepts the specific fields a user is allowed to change, and ignores
anything else such as role, is_admin, or account status. Show me which
endpoints you locked down.
9. Store session tokens in secure cookies
Why it matters: Tokens kept in localStorage can be read by any script on the page, and a stolen one often keeps working forever.
Review how my app stores login and session tokens. Move them out of localStorage
and into secure, http-only cookies, and make sure sessions expire after a
reasonable time. Show me what you changed.
10. Hash passwords if you built your own login
Why it matters: If you store passwords in a readable form, one database leak exposes every account.
If my app stores passwords directly, make sure they are hashed with a strong
modern algorithm such as bcrypt or argon2, and never stored or logged in plain
text. If I am using an auth provider that handles this, confirm it and tell me
I am covered.
🚦 Rate limiting and abuse
11. Rate-limit your API
Why it matters: Without limits, one person can hit your endpoints thousands of times, taking your app down or running up your bill. On login, it also lets them guess passwords as fast as they want.
Add rate limiting to my API routes. Apply stricter limits on login, signup,
password reset, and any endpoint that calls a paid service such as an AI model
or email provider. Enforce the limit on the server so it cannot be bypassed
from the client.
12. Set billing caps and alerts
Why it matters: A sudden spike on a paid service can become a large bill before you ever notice.
Help me set up billing caps and usage alerts on the paid services my app uses,
so I get notified early instead of finding out from a large invoice. List which
services support this and walk me through configuring each one.
13. Add bot protection to public forms
Why it matters: Open signup and public forms get hit by bots creating fake accounts or spamming submissions.
Add bot protection to my signup form and any public-facing forms, using a
CAPTCHA or a similar challenge. Make sure it runs on the server before the
request is processed, not only in the browser.
🔃 Input and output
14. Use parameterized queries
Why it matters: If your app builds queries out of raw user input, someone can type commands that run against your database.
Audit my database queries for SQL or query injection. Replace any query that
builds a string out of user input with parameterized queries or the safe
methods my database library provides. Show me every query you changed.
15. Validate and sanitize user input
Why it matters: Trusting whatever a user submits leads to broken data and opens the door to several kinds of attack.
Add server-side validation and sanitization for all user input across my app.
Validate type, length, and format, and reject anything that does not match.
Do this on the server even if the frontend already validates. Show me which
inputs you covered.
16. Escape user content before displaying it
Why it matters: If your app shows text a user typed without escaping it, someone can slip in code that runs in other people’s browsers and steals their sessions. This is cross-site scripting, and it is a different attack from injection into your database.
Check my app for cross-site scripting. Make sure any user-generated content
is escaped or sanitized before it is rendered, and avoid unsafe methods that
inject raw HTML. Show me each spot you fixed.
17. Lock down file uploads
Why it matters: If people can upload files and you do not check them, someone can upload a file that runs code on your server.
If my app allows file uploads, validate the file type and size on the server,
restrict allowed types to what I actually need, and store uploads somewhere
they cannot be executed. Show me how you secured the upload flow.
18. Do not return more data than the screen needs
Why it matters: APIs often hand back whole database records, including fields the screen never uses, like other people’s emails or internal flags.
Review my API responses for excessive data exposure. Make sure each endpoint
only returns the fields the client actually needs, and never leaks sensitive
fields such as password hashes, tokens, or other users' personal data. Show me
which responses you trimmed.
💰 Payments (only if you take money)
19. Verify payment webhook signatures
Why it matters: Your payment provider notifies your app when a payment succeeds, and if you do not verify that message is real, someone can fake it and get your product for free.
If my app receives payment webhooks, verify the webhook signature on every one
so only genuine events from my payment provider are accepted. Reject anything
that fails verification. Show me the verification you added.
20. Set prices on the server, never from the client
Why it matters: If the price comes from the frontend and your server trusts it, someone can change what they pay before it reaches you.
Make sure my app never trusts a price or amount sent from the client. Set and
verify all prices on the server based on the product being purchased, not on
values submitted by the browser. Show me where prices currently come from and
fix any that come from the client.
🤖 AI features (only if you use LLMs)
21. Defend against prompt injection and unsafe output
Why it matters: Users can hide instructions inside their input to hijack your AI feature, and blindly running or rendering the model’s output can expose your app.
Review how my app uses AI models. Add protection against prompt injection by
keeping user input separate from my system instructions so user input cannot
override my prompts. Treat the model output as untrusted, so it is escaped
before display and never executed directly. Show me what you changed.
22. Cap AI usage per user
Why it matters: Without per-user limits, one person can loop your AI feature and drain your credits overnight.
Add per-user usage limits on any feature that calls a paid AI model, so a
single user cannot make unlimited requests. Enforce the cap on the server and
return a clear message when it is reached. Show me how you applied it.
📦 Deployment and ops
23. Force HTTPS
Why it matters: Without HTTPS, data between your users and your app travels in plain text that anyone on the same network can read.
Make sure my app forces HTTPS everywhere and redirects any HTTP traffic to
HTTPS. Confirm my hosting setup enforces this and tell me if anything is still
served insecurely.
24. Add security headers
Why it matters: A handful of standard headers block common attacks, such as your site being loaded inside a scam page to trick your own users.
Add the standard security headers to my app, including Content-Security-Policy,
X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security.
Use sensible defaults for my stack and explain what each one does.
25. Turn off debug mode and hide source maps and .git in production
Why it matters: Debug mode, exposed source maps, and an accessible .git folder can hand an attacker a map of your app and its secrets.
Check my production setup for debug mode left on, source maps served publicly,
and the .git folder being accessible. Turn off debug in production and make
sure none of these are exposed. Show me what you found.
26. Keep secrets out of error messages
Why it matters: Detailed errors can leak stack traces, file paths, and secrets straight to whoever triggered them.
Review how my app handles errors in production. Make sure users see a generic
error message while the full details are logged privately, so stack traces and
secrets are never shown to the client. Show me what you changed.
27. Keep your dependencies updated
Why it matters: A lot of apps get breached through a known hole in an out-of-date package rather than anything custom.
Scan my dependencies for known vulnerabilities and list anything outdated or
flagged. Update the ones that are safe to update, and tell me about any that
need manual attention or could be a breaking change.
28. Turn on logging and monitoring
Why it matters: Without logs you will not know an attack happened, and careless logs can leak secrets themselves.
Set up basic logging and monitoring so I can see errors and suspicious activity.
Make sure passwords, tokens, and other secrets are never written to the logs.
Show me what you added and where the logs go.
29. Set up automatic backups
Why it matters: If your data gets wiped or corrupted and you have no backup, it is gone for good.
Help me set up automatic backups for my database on a regular schedule, and
confirm I can actually restore from them. Walk me through the setup for my
hosting or database provider.
30. Turn on two-factor for your own accounts
Why it matters: Attackers often get in through your hosting, database, or domain account rather than your code.
Give me a short checklist of the accounts most worth protecting with two-factor
authentication, including my hosting, database, domain registrar, and email, so
I can turn it on for each one.
📱 Optional: building a mobile app?
Keep API keys out of your app bundle
Why it matters: Anyone can unpack a mobile app and read the strings inside it, so a key shipped in the bundle is effectively public.
This is a mobile app. Check for any API keys or secrets bundled into the app or
its JavaScript bundle. Move them server-side and have the app call my backend
instead of holding secrets directly.
Store tokens in secure storage, not AsyncStorage
Why it matters: AsyncStorage is not encrypted, so a login token kept there can be read off the device.
Check where my mobile app stores auth tokens. Move them out of AsyncStorage
and into the platform secure storage, such as Keychain on iOS or Keystore on
Android.
Validate deep links
Why it matters: A deep link that triggers actions without checks can be abused to make your app do things the user never intended.
Review how my app handles deep links, and make sure any action triggered by a
link validates the user and the request before doing anything sensitive.
Do not rely on biometrics alone for sensitive actions
Why it matters: A biometric check on the device can be bypassed, so it should gate the interface, not stand in for a real server-side check.
Confirm that biometric authentication in my app only controls local access, and
that sensitive actions are still verified on the server rather than trusting
the device.
How to use this guide
Run through the Start Here seven before anything else. Then work down the full list, skipping the Payments, AI, and mobile sections if they do not apply to your app. Paste each prompt into your AI coder one at a time rather than all at once, so you can review what it changes on each pass. Come back to the whole checklist after any major update, since new features tend to open new holes.