Checklist
The vibe coding security checklist: 15 checks before you deploy
A practical pre-deploy security checklist for apps built with AI assistants — secrets, access control, authentication, input handling and configuration.
Key takeaways
- 01Most incidents in AI-built apps come from a short list of mistakes: exposed keys, missing ownership checks and permissive configuration.
- 02The “two-account test” catches the most common access-control bug in under five minutes.
- 03If a secret was ever committed or deployed, rotate it. Deleting it from the code is not enough.
- 04Some checks live in your code; others live in dashboards. Review both.
AI coding tools are very good at producing software that works. They are much less reliable at producing software that is safe to put in front of real users. The gap usually isn’t exotic. It’s the same handful of mistakes, repeated across thousands of projects: a key that ended up in the browser, an endpoint that checks who you are but not what you’re allowed to touch, a setting loosened during debugging and never tightened again.
This checklist is designed to be run before every deploy. Each item includes a quick way to check it yourself. Work through it top to bottom — the first sections cover the issues attackers find fastest.
Secrets and keys
1. No secret keys in client-side code
Anything that runs in the browser can be read by anyone who visits your site. That includes components marked "use client", anything bundled by Vite or webpack for the frontend, and mobile app bundles.
How to check: search your project for common secret prefixes and names — sk_live, sk_test, service_role, secret, private_key, Bearer. Then check which of those files end up in the browser.
2. Public environment variables only hold public values
Frameworks expose variables with certain prefixes to the browser on purpose: NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_, REACT_APP_. They are compiled into the JavaScript your visitors download.
How to check: list every variable with one of those prefixes and ask of each one: would I be comfortable posting this value publicly? Publishable keys (like a Stripe pk_ key or a Supabase anon key) are designed for this. Secret keys are not.
3. No credentials in your repository — or its history
A .env file committed once stays in Git history even after you delete it.
How to check: run git log --all --oneline -- .env in your project. If it returns anything, treat every secret that file ever contained as exposed. Make sure .env and similar files are listed in .gitignore.
If anything in this section fails: move the secret to server-side code, then rotate it in the provider’s dashboard. Assume a key that was public for any length of time has been copied. See how AI-built apps leak API keys for a deeper walkthrough.
Access control
4. Every endpoint checks ownership, not just login
The most common serious bug in AI-generated APIs is fetching or updating a record by its ID without checking that it belongs to the person asking. It’s known as an insecure direct object reference (IDOR).
How to check — the two-account test: create two accounts. Signed in as the first, open a resource (an invoice, a profile, a document) and copy its URL or API request. Sign in as the second and try the same URL. If you can see or change the first account’s data, you have a broken access control bug. More on why AI writes this bug so often.
5. Admin actions are enforced on the server
Hiding an admin button in the interface doesn’t protect the endpoint behind it.
How to check: find every route that deletes, exports or changes other users’ data. Confirm each one verifies the caller’s role on the server — not just that they’re signed in.
6. Database access rules are on, and strict
If your frontend talks directly to the database — common with Supabase and Firebase — the database rules are your access control.
How to check: in Supabase, confirm Row Level Security is enabled on every table in an exposed schema and that policies restrict rows to their owners; the dashboard’s Security Advisor flags common problems. In Firebase, review Firestore and Storage rules for anything that allows reads or writes without checking request.auth. See our guide to Supabase Row Level Security.
7. Requests can’t set fields they shouldn’t
Passing the whole request body into a database update lets users set fields like role, isAdmin, plan or ownerId.
How to check: look for updates that use the raw request body. Replace them with an explicit list of fields the user may change.
Authentication
8. Tokens are verified, not just decoded
Decoding a JSON Web Token reads it; verifying it proves it hasn’t been forged. AI-generated code sometimes uses a decode function where a verify function is needed.
How to check: search for jwt.decode, jwtDecode and similar calls in server code. Server-side authentication should use a verification function with your secret or public key and an explicit algorithm.
9. Sessions and reset links expire
Tokens without an expiry, and password-reset links that work forever, turn a single leak into permanent access.
How to check: confirm session tokens have a reasonable lifetime, logout invalidates the session on the server, and password-reset links expire after first use or a short time window.
10. Passwords are stored with a slow, salted hash — or not at all
If you store passwords yourself, they must be hashed with an algorithm designed for passwords, such as Argon2, bcrypt or scrypt. Fast hashes like MD5 or SHA-256 are not suitable. Better still, use a well-maintained authentication provider.
Input and output
11. Database queries are parameterized
Building SQL with string concatenation or template strings allows injection.
How to check: search for queries assembled with +, ${} or f-strings. Use your database library’s parameter placeholders instead.
12. User content is never rendered as raw HTML
React’s dangerouslySetInnerHTML, Vue’s v-html and direct innerHTML assignments render whatever they’re given — including scripts.
How to check: search for those three patterns. If user-supplied content flows into any of them, sanitize it with a maintained library such as DOMPurify, or render it as text.
Configuration
13. Debug mode is off in production
Debug pages and detailed error messages reveal stack traces, file paths, settings and sometimes secrets.
How to check: confirm production configuration disables debug mode (for example DEBUG = False in Django) and that error responses are generic.
14. CORS only allows your own origins
A CORS configuration that reflects any origin while allowing credentials lets any website make signed-in requests to your API.
How to check: find your CORS setup. Replace wildcards and origin: true with an explicit list of your own domains.
15. Every dependency exists, is maintained and is the one you meant
AI assistants sometimes suggest packages that don’t exist. A USENIX Security 2025 study of code-generating models found hallucinated packages in roughly 5.2% of suggestions from commercial models and 21.7% from open-source models — names an attacker could register and fill with malicious code.
How to check: for each new dependency, confirm it’s the official package, actively maintained and widely used. Run your package manager’s audit command (npm audit, pip-audit or equivalent) for known vulnerabilities.
Make it a habit, not a project
Security reviews fail when they’re treated as a one-off event. The most effective approach is a short, consistent check every time you ship — especially after a large AI-generated change, and whenever you add authentication, payments, file uploads or an admin area.
VibeTect is designed for exactly that moment: it’s built to inspect your project on your own machine for the code-level issues behind many items on this list — exposed secrets, access-control gaps, unsafe authentication, risky configuration and vulnerable code patterns — and explains each finding in plain English. The checks that live in dashboards (items 6 and parts of 9) still need a look where they’re configured. No single tool replaces judgment; a good checklist and a fast scanner make that judgment much easier to apply.