How to Secure a Vibe-Coded App Before Launch

You built the app in a week. Before your first real user finds the SQL injection you didn't know was there, here's the workflow that actually works.

AI coding tools ship features. They don't audit themselves.

Cursor, Claude Code, and GitHub Copilot are great at writing code that compiles and passes tests. They're less reliable at writing code that's secure. Not because they're bad at security (they know the patterns), but because they optimize for making the test green, not for hardening the trust boundary.

A 2025 study of Lovable apps in production found that 10.3% had exploitable vulnerabilities directly exposing user data. Most weren't theoretical weaknesses. They were SQL injections, unauthenticated admin routes, and path traversal bugs that any scanner could attempt, if someone pointed it at a live environment.

The gap isn't in the agent's knowledge. It's in what gets tested before launch.

The three bugs that show up most often in AI-generated apps

In our benchmark testing, three vulnerability classes account for the majority of confirmed findings in AI-generated apps.

SQL injection shows up because agents reach for template literals to build dynamic queries. The ORM wrapper isn't always there. A typical pattern: db.query(\SELECT * FROM users WHERE id = ${req.params.id}\). Syntactically correct, catastrophically exploitable. The agent knew about parameterized queries. It just didn't use one here.

Unauthenticated admin routes show up because agents scaffold admin endpoints first to make development faster, then auth middleware gets added later. Except sometimes it doesn't. A common finding: /api/admin/users returns the full user table with no authentication check.

Insecure file handling (path traversal and unsanitized upload destinations) shows up whenever file paths are constructed from user input without validation. fs.readFile('./uploads/' + req.params.filename) is the classic case.

Don't start from a checklist. Start from what's exploitable against your staging URL.

The instinct after reading the above is to open an OWASP checklist and work through it line by line. That's the wrong move. Checklists give you 40 things to worry about. Most of them are irrelevant to your specific stack.

Run exploit verification against your staging environment instead. Point it at your app and let it attempt the actual exploits (SQL payloads, auth bypass attempts, path traversal sequences) against your running code, with your actual middleware and config. You'll get back a short list: typically 1 to 5 findings that are confirmed exploitable in your environment.

This matters because a static analysis tool might flag a library function that your framework's middleware actually protects. The exploit verification knows whether that protection is in place because it tested it. A finding that comes back 'not exploitable' is real information, not a false negative.

What to hand your agent to get the fix right on the first pass

Generic fix instructions produce generic fixes. If you tell Claude Code 'fix the SQL injection in auth.ts', it might add input validation, switch to an ORM, or add a regex filter. Any of these might not actually close the vulnerability.

A verified finding gives you: exact file and line, the parameter that was exploited, the payload that worked, and the HTTP response that proved it. Hand that directly to your agent: 'SQL injection confirmed at src/api/auth.ts:40, parameter user_id, payload \' OR \'1\'=\'1\' --, 847 rows returned. Use a parameterized query.' That's an instruction it can act on correctly the first time.

The difference between a vague security warning and a structured finding with a payload isn't just convenience. It's the difference between a fix that addresses the symptom and one that closes the actual vulnerability.

Run Composed on your last 10 PRs.

Apply for design partner access and we’ll show which findings are real, exploitable, and worth fixing — free.

Run it on 10 PRs →