How to Secure Your Vibe-Coded App: A Developer's Guide
You vibe-coded an app and it works. Now make sure it's not leaking. A practical, end-to-end security pass for apps built mostly by an AI agent — without slowing you down.
You shipped it. Now let's make sure it's not on fire.
Vibe coding — describing what you want and letting an AI agent build it — is genuinely great. It's also a security minefield, because the agent optimizes for "it runs," not "it's safe." This guide is the security pass to run after the vibe and before real users.
You don't need to become a security engineer. You need to check the handful of things that actually cause incidents.
Step 1: Find the secrets
AI agents inline credentials constantly. Sweep for them:
grep -rniE "(sk_live|sk_test|api[_-]?key|secret|password|token)\s*[:=]\s*['\"]" .
For anything you find:
- Move it to an environment variable.
- Rotate the key — once it's in your git history, treat it as compromised.
- Add
.envto.gitignoreand confirm it isn't already committed:git log --all --full-history -- .env.
Step 2: Check what's in your client bundle
The browser gets your frontend code. Make sure it didn't get your backend secrets. In a Next.js app, only NEXT_PUBLIC_* should reach the client. Open DevTools → Sources and search the bundle for secret, sk_, and your provider names. If a server key is there, it's public — rotate it.
Step 3: Lock down your database
If you used Supabase/Firebase, the browser talks to the database directly, so access rules are your only backend.
- Supabase: enable Row Level Security on every table and add owner-scoped policies.
alter table public.notes enable row level security;
create policy "own notes" on public.notes
for all using (auth.uid() = user_id) with check (auth.uid() = user_id);
- Firebase: replace test-mode rules with owner checks (
request.auth.uid == userId) on Firestore and Storage.
Then test as a logged-out user: can you still read the data? If yes, fix it now.
Step 4: Add ownership checks
The most common serious bug in AI apps: any logged-in user can read anyone's data by changing an ID (IDOR). Audit every route that takes an id:
const doc = await getDoc(id);
if (doc.ownerId !== session.user.id) {
return new Response("Forbidden", { status: 403 });
}
Step 5: Validate input
AI assumes friendly users. Add a schema at every entry point:
import { z } from "zod";
const Body = z.object({ title: z.string().min(1).max(200) });
const r = Body.safeParse(await req.json());
if (!r.success) return new Response("Bad request", { status: 400 });
Step 6: Kill the injection paths
- SQL: replace any
\SELECT ... ${value}`string with parameterized queries ($1,?`). - XSS: search for
dangerouslySetInnerHTMLandinnerHTMLon user data. Render as text or sanitize.
Step 7: Add the free wins
// next.config.js
async headers() {
return [{
source: "/:path*",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{ key: "Content-Security-Policy", value: "default-src 'self'" },
],
}];
}
And set HttpOnly; Secure; SameSite=Lax on auth cookies.
Step 8: Rate-limit the obvious targets
Login, signup, password reset, and anything that sends email or costs money. Even a simple per-IP limit stops the dumb attacks.
Step 9: Make the AI help you
Close the loop. Ask your agent to review its own work:
"Review this codebase against the OWASP Top 10. List every place with missing authorization, string-built SQL, unvalidated input, or hardcoded secrets, and propose fixes."
It's surprisingly good at finding what it broke — when you point it at the right checklist.
The 5-minute final sweep
grepfor secrets → clean- Logged-out read of protected data → denied
- Change an ID on someone else's record → 403
- Bad input to a form → 400, not a crash
curl -sIyour URL → headers present
Scan it with Troja
Troja automates this entire pass — secrets, RLS, IDOR, injection, headers — against your live app, and every finding ships with a prompt you can paste straight back into Cursor or Claude. Vibe-code the app; let Troja check the walls.
Run the scan this post is about.
Free, no signup. See what's hiding inside your walls in ~30 seconds.