Guide · 4 min read
Twenty security holes that show up in vibe-coded apps
The same twenty mistakes keep landing in weekend apps. What each one costs, and a prompt you can paste into Cursor or Claude to audit your repo.
A model will happily give you a running product by Sunday night. It will also repeat the same unsafe defaults, because "it works" is the objective, not "it is safe." These twenty show up constantly. Copy the page, drop it into Cursor or Claude with the prompt at the end, and patch what comes back.
1. Committing .env to GitHub
Bots scrape public repos within minutes of a push. Removing the file later does not save you: git still has the history. Put .env in .gitignore, keep secrets on the server, and rotate every key that ever sat in a commit.
2. Real API keys in frontend code
Whatever lands in the JS bundle is public. NEXT_PUBLIC_ and EXPO_PUBLIC_ mean anyone can read the value in devtools. Only publishable keys belong in the client. The rest stay in edge functions or a backend.
3. Row level security left off
On Supabase or Firebase, RLS off plus an anon key is a master key. Anyone who lifts it from your bundle can read and write every row. Turn RLS on, scope policies to auth.uid(), and prove it with a second account.
4. Permission checks only in React
if (user.isAdmin) in the UI blocks nobody. People edit the response, hit the API directly, or flip a flag in devtools. Authorization belongs on the server. The client only chooses what to render.
5. No rate limits
A script against login brute-forces passwords. A script against an AI route burns the bill overnight. Throttle by user and by IP on login, signup, and anything expensive.
6. SQL built with string concatenation
If user input is spliced into a query, someone can close the quote and append their own SQL. Use parameterized queries or an ORM. Never interpolate into a query string.
7. Validation only in the form
Client-side checks are UX. Anyone can skip the form and POST a negative quantity or a 10MB "email." Validate and sanitize on the server as well, with something like Zod.
8. User content rendered as raw HTML
dangerouslySetInnerHTML on text other people submitted lets them inject a script that runs in someone else's browser and steals the session. Render as text. If you truly need HTML, run it through DOMPurify first.
9. Passwords stored in plaintext
One dump exposes every account here, and every other site where that password was reused. Let the auth provider hash, or use bcrypt / argon2. Do not roll your own.
10. Auth tokens in localStorage
Any XSS on the page can read localStorage and leave with a live session. Use httpOnly cookies on web. On native, use secure storage such as expo-secure-store.
11. An unauthenticated /admin
A route nobody linked to is not a lock. Same for /debug that prints environment variables. Gate every internal path, and strip debug endpoints from production builds.
12. CORS set to *
Access-Control-Allow-Origin: * lets any site call your API with the user's cookies attached. Allowlist your own origins. Nothing else.
13. Signups without email verification
Unverified addresses mean fake accounts, spam, and takeover flows that claim an inbox the person does not own. Verify email before anything real is allowed.
14. Guessable IDs with no ownership check
Sequential IDs are fine. Sequential IDs with no "does this belong to the caller" check means /order/1001 becomes /order/1002 and a stranger's data. On every read and write, confirm the resource is theirs.
15. Writing the entire request body on update
An update handler that saves whatever JSON arrives lets someone add "role": "admin" or "is_premium": true to their own profile. Allowlist the fields a user may set. Ignore the rest.
16. Webhooks with no signature check
A Stripe handler that trusts any POST that looks right will accept a fake payment_succeeded and unlock paid features. Verify the signature against the webhook secret before you touch the payload.
17. Stack traces in production
Verbose errors leak paths, table names, and library versions, which is a map for anyone probing the app. Users get a generic message. Detail goes to logs.
18. Dependencies never updated
A package from last year has public CVEs and ready-made exploits. Run npm audit, turn on Dependabot, and patch on a schedule instead of after an incident.
19. No password strength or breach check
Without a minimum length and a leaked-password check, password123 becomes a live account. Set a floor, and turn on the breach check your auth provider already ships. Supabase has one.
20. File uploads with no checks
Accepting any file lets someone upload a script and hunt for a way to serve or run it. Check type and size, store outside the web root or in object storage, and never execute an upload.
Prompt to run with this list
Copy the list above, put this on top, and send the whole thing to Cursor or Claude:
Walk my repo against the 20 issues below. For each one, say whether the code has it, cite the file and line, and describe the fix. Do not change anything yet. Report first.
Keep this. Run it before you ship, and again after any large feature. Twenty minutes now is cheaper than a breach email later.
Continue through FloBitLab
Want this built for your team?
Tell me what you are trying to ship. I can take on the complete build, a defined technical scope, or work with your existing team.