← All field notes
App Security

How to Secure a Vibe-Coded App Before You Tell Anyone It Exists

A pre-launch security pass for AI-built apps: bundled keys, missing RLS, open API routes. What I check in 20 reviews, and how to do it yourself in 90 minutes.

Matthew TurleyJuly 31, 20266 min read

There's a window between "my app works" and "I posted it on X." That window is where you fix the stuff that gets you hacked. Once the link is public, you're patching in production while strangers poke at your endpoints. Before the link is public, you can fix everything calmly, on your own schedule, with nobody watching.

Most people skip the window entirely. They get the demo working in Lovable or Bolt on a Tuesday, and by Wednesday it's on Product Hunt. I get it. The momentum feels precious. But I've now reviewed 20 AI-built apps before launch, and the same three or four holes show up almost every time. None of them take long to fix. All of them are miserable to fix after launch.

Here's the pass I'd run if it were my app. Budget about 90 minutes.

Start with the bundle, because that's where your keys are

First thing I do on every review: build the app, open the output folder, and grep the JavaScript for anything that looks like a secret. Not the source code. The built bundle. The thing that gets shipped to every browser that visits your site.

Why? Because frontend frameworks have a convention that AI tools abuse constantly. Any environment variable prefixed with VITE_ or NEXT_PUBLIC_ gets compiled directly into your client-side JavaScript. That's by design. It's how the frontend gets config it legitimately needs. But when you tell Cursor "add OpenAI to my app" and it puts VITE_OPENAI_API_KEY in your .env, that key is now sitting in plaintext in a file anyone can download. View source. Ctrl+F "sk-". Done.

I found some version of this in a majority of the apps I reviewed. It's the single most common finding, and I wrote up the full mechanics of why frontend API keys leak separately if you want the deep dive. The fix is always the same shape: the key moves to a server route or edge function, the frontend calls your endpoint, your endpoint calls the provider. Twenty minutes of work, usually.

One wrinkle people miss. Rotating the key isn't optional. If it was ever in a deployed bundle, treat it as burned, even if nobody visited the site. Vercel and Netlify preview deployments are indexed more often than you'd think.

Then check whether your database says no to strangers

If you're on Supabase, and most vibe-coded apps are, your anon key being visible in the frontend is fine. Genuinely fine, it's designed to be public. The entire security model rests on Row Level Security policies instead. RLS is the thing that decides, per table, per row, whether the person holding that anon key can read or write.

Which means the question isn't "is my key exposed." The question is "is RLS actually on, and are the policies actually restrictive."

Here's the test, and you don't need any tooling. Open a terminal and curl your own REST endpoint with just the anon key:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/users?select=*" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

If that returns rows, anyone on the internet can read your users table. No account needed. I've watched this exact curl return full customer lists, email addresses, even hashed passwords on apps that were days from launch. The founders were always shocked. The AI had created the tables, and either never enabled RLS or wrote a policy like USING (true), which is RLS-shaped but permits everything.

Run that curl against every table you have. Then run it again with a real logged-in user's token and try to read a different user's rows. Both should fail where you'd expect them to fail. Ten minutes, and you've verified the actual boundary instead of assuming it.

The scale of this problem across the ecosystem is worse than any individual anecdote suggests. Our exposure report on AI-built apps has the aggregate numbers if you want to see how common open tables really are in the wild.

Walk your API routes like an attacker with a free afternoon

Third pass: every server route your app has, hit it while logged out. Then hit it logged in as user A, but with user B's IDs in the request.

Vibe-coded backends fail this constantly because the AI writes routes that trust the request body. A route like /api/orders?userId=123 that just... uses the userId you sent it. Change 123 to 124, get someone else's orders. That's an IDOR, insecure direct object reference, and it's boring and ancient and it works on brand-new AI-built apps in 2026 because the model optimized for "make the demo work" and the demo only ever had one user.

The fix is a habit, not a library. Server routes derive the user from the session token, never from the request payload. If you see req.body.userId anywhere near a database query, that's a finding.

While you're in there, look for any route that spends your money. An endpoint that calls OpenAI or Anthropic on the server with no auth and no rate limit is an open bar. Someone will find it and run their workload on your API bill. I'd expect at minimum a per-IP rate limit, and honestly Upstash gives you one in about fifteen lines.

The stuff that can wait, and the stuff that can't

I want to be honest about scope here, because pre-launch security advice tends to balloon into a 40-item checklist that nobody finishes. You don't need a pentest. You don't need SOC 2. You probably don't need a WAF on day one.

You need the bundle grep, the RLS curl test, and the logged-out route walk. That's the 90 minutes. Those three cover the failure modes that actually show up when someone hostile finds a vibe-coded app, because those are the ones that require zero skill to exploit. Everything I've described is doable with curl and a browser. That's exactly why it's what gets exploited.

If Stripe's involved, add one more: verify you're checking webhook signatures. An unverified webhook endpoint means anyone can POST a fake "payment succeeded" event and get your product for free. Stripe's docs make verification a copy-paste job, so there's no excuse to skip it.

Do it before the window closes

The whole point of doing this now is that "before launch" is the last time a security fix is cheap. No users to migrate, no disclosure email to write, no burned key already scraped by a bot. Just you, a terminal, and 90 quiet minutes.

If you'd rather have a second set of eyes on it, that's a thing I do. The Vibe Check Launch Risk Review is me running this pass, plus the longer tail of checks from those 20 reviews, against your app before you ship it. Some founders want that. Plenty just run the curl commands themselves, and honestly that's a fine outcome too. The point is that somebody runs them before the link goes public.

Because after the link goes public, you're not testing anymore. You're racing.

M
Matthew Turley, Continuum

Fractional CTO and embedded technical partner. 20+ years shipping production software.

Run a free Leak Check →