Vibe coding security risks — 7 things in every AI-built app
Seven specific security problems we find in nearly every vibe-coded app we scan. None are exotic. All are exploited. Each takes ten minutes to fix.
We scan a lot of AI-built apps. The same seven things turn up in nearly every one — not because the people who built them were careless, but because their AI coding tool defaulted toward “make the feature work” instead of “make the feature safe.” If you’ve shipped anything with Lovable, Cursor, Bolt, v0, Replit Agent, or Claude Code, there’s a good chance at least three of the seven below are live on your app right now.
None of these are exotic vulnerabilities. They’re the ones that get exploited every day. Each takes about ten minutes to fix once you know where to look.
1. Secrets in the frontend bundle
The single most common issue. Some version of:
- The OpenAI API key, embedded directly in JS so the chat feature works.
- The Supabase service role key, which is the literal admin password for your database.
- The Stripe secret key, used “to create checkout sessions client-side.”
- An internal admin token the AI invented to make some debugging path easier.
How it happens: you ask the AI for a feature that needs the key. The AI doesn’t know which environment variable convention keeps things server-side, so it either uses a public prefix (NEXT_PUBLIC_, VITE_, PUBLIC_) or hard-codes the key as a string literal. The build pipeline doesn’t notice. The key ships to every visitor.
How to check: open your live site, open devtools, search the JS bundle for sk_, eyJ, or the names of services you integrate with. If you find a real key, rotate it before you do anything else.
2. Database with Row-Level Security disabled or misconfigured
Specific to Supabase, Firebase, and any other “client talks to database directly” architecture. Two flavours:
- RLS off: the table was created without RLS enabled. Anyone with the project URL and the anon key (which is in every app’s bundle by design) can
select *from the table. - RLS on, policy wrong: the AI wrote a policy like
USING (true), which means “anyone can read everything.” The toggle in Supabase says “RLS enabled,” and the policy says “ignore me.”
We’ve watched anonymous users select * from users, subscriptions, chats, documents. The dataset is gone in seconds.
How to check: open Supabase → Authentication → Policies. Any table that says “RLS disabled” is public. For tables with RLS, read the policy SQL. Then test from a logged-out browser session — see Supabase RLS for vibe coders for the test pattern.
3. Admin pages with UI-only auth
The AI built /admin/users because you asked for it. It “protected” it by hiding the UI for non-admins: {user.role === 'admin' && <AdminPanel />}. The data is still fetched server-side regardless of who’s looking. Anyone can hit /api/admin/users directly with curl and get the JSON back.
The same pattern shows up with:
- “Delete user” endpoints that anyone can call.
- Internal admin queries (sales numbers, user emails, anything) that are loaded by the UI regardless of role.
- Webhooks that don’t verify the caller.
How to check: list every “protected” page in your app. For each one, open it in an incognito browser. Watch the network tab. Hit the API endpoints directly with no auth. If you get data back, the protection is UI-only.
4. .env files committed to git
The AI generated a .env.local with placeholder credentials, then started the dev server. The .env.local worked, you added real credentials to it, kept building, and it ended up in git. Sometimes deliberately (the AI ran git add . without realising). Sometimes through .gitignore being missing or wrong.
The result: your real credentials in your git history, permanently. Even if you delete the file in a later commit, the history retains the secret. Public repo: anyone can find it. Private repo: still bad if you ever expand access.
How to check: git log --all --diff-filter=A --name-only | grep -E "\.env". Anything that comes back: rotate the credentials, then clean history.
5. Source maps in production
Source maps are the original, un-minified, comment-included version of your code. They’re shipped alongside your production JS to make debugging easier. They also let anyone reverse-engineer your app instantly: every function name, every comment, every TODO, every internal API endpoint.
How to check: visit https://yoursite.com/_next/static/chunks/main-[hash].js.map (or your framework’s equivalent). If it returns JSON instead of 404, you’re shipping source maps. Or, in devtools Network tab while loading your site, look for any .map files.
The fix: turn off source maps in production builds. Next.js: productionBrowserSourceMaps: false. Vite: build.sourcemap: false. If you want them for error tracking, upload to Sentry/Datadog without serving them publicly.
6. Missing security headers
Browsers can enforce a lot of protection on your behalf — Content Security Policy, HSTS, anti-clickjacking, MIME sniffing prevention — but only if you tell them to via HTTP response headers. AI tools rarely set these. Vercel and Cloudflare set some defaults; everyone else, nothing.
Specifically:
- No
Strict-Transport-Security: someone on coffee shop wifi can downgrade your visitors to HTTP and intercept everything. - No
Content-Security-Policy: any XSS bug in your app can load attacker JS without restriction. - No
X-Content-Type-Options: nosniff: browser MIME sniffing can turn an uploaded file into an executable script.
How to check: visit securityheaders.com, put your URL in. It grades you A through F with specific recommendations. Anything below B is worth fixing.
7. Unused subdomains pointing at deleted services
The least obvious risk. You started building on Webflow. You moved to Vercel. The Webflow site is gone but the CNAME www.yourapp.com still points at Webflow’s servers. Anyone can sign up for Webflow, claim that hostname, and now controls a domain that visitors trust.
This is called subdomain takeover. It’s how phishing pages that look exactly like your login page end up hosted on a real subdomain of your real company.
Same vector with: old Heroku apps, old Netlify deploys, old GitHub Pages, old Tumblr accounts, old anything-as-a-service you’ve ever pointed a CNAME at.
How to check: enumerate your DNS records. For each CNAME or A record pointing at a third-party service, verify you still own that service. Delete records that point at nothing. The pillar piece has the longer version.
All seven, in one pass
Each of these takes ten minutes when you know where to look. Doing them once is fine. Doing them after every deploy is the tedious part. That’s what Patchable does — runs the same seven checks (plus 70 more) from the outside, after every deploy, and writes each fix as a prompt for Claude Code, Cursor, Bolt or Copilot. The scan is free. You only pay if there’s something to fix.
Seven problems in your AI-built app right now. Find out which. Run the scan.
Related reading
- The vibe coding security guide — the long-form pillar piece with full fix patterns.
- Vibe coding security scanner — scan your live app from this page.
- Is vibe coding bad? — the honest answer.
- Supabase RLS for vibe coders — fix for issue #2 above.