The vibe-coder security checklist
You built the whole app in a weekend. Cursor wrote most of it. Claude Code filled in the awkward parts. It works. You are now three days away from launching, and the security section of your notes still just says "add later."
This is the honest list of what breaks most on AI-generated web apps, in order of how often we see it. If you fix these ten things you are past the 95th percentile of shipped indie projects.
1. Your .env is public
The single most common finding. Someone committed .env to git, or the deploy target serves public/ instead of the app root, or the framework's default routing exposes hidden files. Test:
curl https://your-site.com/.env
curl https://your-site.com/.env.local
curl https://your-site.com/.env.production
If any of those return anything other than 404, even a redirect, you have a leak. Fix: block /.env* at the web server (nginx location ~ /\.env → 404) and rotate every secret in the file.
2. Your .git directory is downloadable
Same shape, worse blast radius. curl https://your-site.com/.git/config, if it returns a plain-text [core] block, an attacker can pull your entire git history including every deleted secret you ever committed. Fix: same nginx block, one line, does both.
3. Your Supabase RLS is off
Row Level Security disabled by default. If you generated a Supabase project and haven't manually enabled RLS on every table, any authenticated user (or in some cases any anonymous request with the anon key) can read every row. Test in the dashboard: Database → Tables → your table → RLS enabled?
Fix: turn it on for every table, then write policies. Start restrictive (deny all), then grant reads/writes explicitly per role.
4. Your Firebase rules are wide open
The default allow read, write: if true ships to production because the setup wizard warned you and you clicked through anyway. Attackers scan for open Firebase projects daily. Fix: publish rules that require request.auth != null plus a per-document owner check.
5. Your API keys are in the frontend bundle
OpenAI, Stripe, Twilio, Resend, if the key starts with sk_ or otherwise carries write permission, it should never appear in your JS bundle. Check: view-source your homepage, search for known prefixes. If you find one, the key is already leaked. Rotate immediately and proxy the calls through your backend.
6. Your login endpoint has no rate limit
An attacker can try 1,000 password combinations per second against your login route. Test with:
for i in {1..20}; do
curl -so /dev/null -w "%{http_code}\n" -X POST https://your-site.com/login \
-d "email=admin@example.com&password=wrong$i"
done
If all 20 return 200 (or the same "invalid credentials" HTML with no delay), you have no rate limit. Fix: put a limit like 5-per-IP-per-minute on /login and /register. Bonus: also limit password reset.
7. Your uploads accept anything
If you have a file-upload endpoint (avatars, CSV imports, whatever), test uploading a PHP file, then a polyglot (image that is also HTML), then a 10 GB file. If any succeeds, you have a problem. Fix: validate MIME by magic bytes not extension, cap size at the framework level, scan for polyglots.
8. Your HSTS is missing
Test: curl -sI https://your-site.com | grep -i strict-transport. If nothing prints, browsers will happily accept an http:// downgrade the first time a user hits your site. Fix: one HTTP header, one line of config, one restart.
9. Your query params are interpolated into SQL
You probably use an ORM and don't do this. But: search your codebase for raw(, DB::select(, ${req.query, + req.body, any pattern that concatenates user input into a query string. If it exists, an attacker will find it. Fix: parameterize every query.
10. You have no idea when you are being attacked
Your logs are somewhere. You have never looked at them. If someone starts probing your site right now, you will find out days later, from a customer complaint.
Fix: run npx @defen.so/init. Free tier gives you a real-time attack log for one site, an uptime monitor, and a weekly pentest, no credit card. When something matches an OWASP pattern, you get the URL, the payload, the IP, the ASN, and the verdict in your dashboard within seconds. It is not the point of this post to sell you anything but if you asked for the fastest way to close this loop, that is it.
The two-minute test
Fire the classics at your own site right now:
- playground.defen.so runs the same payloads at a real Defen.so-protected origin. Try each template, see what a working WAF returns, then try the same payload against your own URL.
- If any of them succeed against you (200 response with data), fix that one first.
Ship. The AI wrote most of your code. It didn't do the security part. Ten items on this list, thirty minutes of work each on the hard ones, and you are done.