What is a vibe coding security audit?
A vibe coding security audit is a structured technical review of apps built primarily with AI code assistants — tools like Cursor, Bolt, Replit Agent, Lovable, or Claude Artifacts. The term "vibe coding" was coined by Andrej Karpathy in early 2025 to describe the practice of building software by describing intent to an AI rather than writing every line by hand. By mid-2026, an estimated 40% of indie SaaS apps and side projects in production were built this way.
The mechanics are different from what a traditional code review assumes. Vibe-coded apps tend to:
- Lack explicit auth token management (the AI handled it, somehow)
- Use Row Level Security in name only, without proper policy validation
- Expose environment variables or API keys in client-side bundles
- Have no error monitoring beyond a catch block that console.logs
- Miss retry logic, transaction boundaries, and graceful degradation entirely
None of these are catastrophic on their own the day you launch. They become catastrophic the day someone finds them — and in 2026, automated scanners find them within hours of a domain going live.
A vibe coding security audit doesn't assume malice or incompetence. It assumes the AI did what AIs do: it solved the problem you described, and nothing more. Your job — or an auditor's job — is to find what you didn't think to describe.
What AI code generators miss
This is the core question, and it's worth being specific. AI assistants like Claude, GPT-4o, and Gemini are trained on enormous corpora of code. They know what good auth looks like. They can write a JWT verification function correctly if you ask for one. The problem isn't knowledge — it's scope.
When you describe a feature to an AI, it builds that feature. It builds the happy path. It rarely builds the failure mode, the edge case, the configuration that makes the happy path safe in production, or the monitoring that tells you when the happy path breaks.
Here's what consistently shows up in audits of vibe-coded apps:
1. Hardcoded secrets
The AI needed an API key to make your Stripe integration work, so it put one in the code. That key is now committed to your repository, possibly a public one. This is the single most common critical finding in vibe-coded apps — and one of the most dangerous. Attackers scrape GitHub for API key patterns continuously. A live Stripe secret key in a public repo has a median time-to-exploit of under 4 minutes.
Finding: STRIPE_SECRET_KEY is hardcoded as a string literal and checked into version control. If this repository is or ever becomes public, this key is compromised.
Fix: Remove the key from source, add to .env (add .env to .gitignore), rotate the key in your Stripe dashboard immediately, and access via process.env.STRIPE_SECRET_KEY.
2. Missing or misconfigured Row Level Security
Supabase is the dominant database layer in vibe-coded apps. Supabase's default is to give the anon key access to everything — which is fine for a prototype, catastrophic for a production app. The AI may have generated RLS policies, but whether those policies actually work requires testing them, not reading them. We've seen policies that look correct but evaluate to permissive because of a subtle policy ordering issue or because the auth.uid() check was written in a way that never fires.
3. Unauthenticated admin routes
You asked the AI for an admin dashboard. It built one. You didn't ask it to protect that dashboard, so it didn't. Or it added a comment: // TODO: add auth here. Routes like /admin, /api/admin/*, /api/users, and /api/metrics that return sensitive data without authentication checks are a standard finding. Automated scanners enumerate these within minutes of launch.
4. Injection vectors
SQL injection, NoSQL injection, and command injection vulnerabilities appear in AI-generated code when dynamic values are concatenated into queries or shell commands rather than parameterized. The AI knows parameterized queries are correct — but when it's generating code quickly, it sometimes takes the shortcut. A proper audit runs the OWASP Top 10 injection checks against every user-controlled input.
5. No error monitoring
Your app will break in production. The question is whether you find out from a user complaint or from your own observability stack. Vibe-coded apps almost universally lack Sentry, Datadog, or equivalent error tracking. When something goes wrong, there's no trace, no alert, no way to reproduce the issue.
The silent failure problem. In vibe-coded apps, errors that don't bubble to the UI often go undetected for weeks. A broken webhook handler, a failed background job, a misconfigured email integration — none of these show up in the browser console. You only find out when a customer complains they didn't get an email they were expecting, and you have no logs to diagnose why.
The four audit dimensions
A comprehensive vibe coding security audit covers four dimensions. Each is distinct. Missing one creates a class of vulnerabilities the others can't catch.
| Dimension | What it checks | Primary tools |
|---|---|---|
| Security Security |
OWASP Top 10, CWE Top 25, auth flaws, secrets in code, vulnerable dependencies, injection vectors | Semgrep, gitleaks, Snyk, Trivy |
| Reliability Reliability |
Error handling, race conditions, transaction boundaries, retry logic, graceful degradation | Custom AST checks, ESLint rules, ruff |
| Performance Performance |
N+1 queries, missing indexes, bundle bloat, synchronous blocking, cache strategy | Lighthouse, k6, EXPLAIN ANALYZE, bundle analyzers |
| Monitoring Monitoring |
Error tracking presence, alerting gaps, logging quality, uptime check coverage | Custom probes for Sentry/Datadog presence, log pattern analysis |
Most "security scanners" only cover the first dimension — and often incompletely. A founder who runs npm audit and sees zero vulnerabilities believes their app is safe. npm audit checks whether your declared npm dependencies have known CVEs. It says nothing about your auth logic, your Supabase policies, your error handling, or whether you'll know when your payment webhook stops working.
Security: OWASP and beyond
The OWASP Top 10 remains the definitive list of the most critical web application security risks. In vibe-coded apps, certain categories appear with notably higher frequency than in traditionally-engineered software.
A01: Broken access control
This is the most common finding category. In vibe-coded apps, it manifests as: API routes that don't verify the requesting user owns the resource they're querying; admin endpoints with no authentication; row-level filtering that's applied in the application layer but not enforced at the database layer (meaning a clever API call bypasses it entirely).
Real example (anonymized). A SaaS app's GET /api/documents/:id endpoint returned the document if the ID was valid, regardless of which user was authenticated. Every document in the system was readable by any authenticated user — including competitor accounts. The AI had generated the authentication middleware but forgot to apply it to the documents router.
A02: Cryptographic failures
Secrets management is the primary finding here. Beyond hardcoded keys, look for: secrets passed as URL parameters (end up in server logs); JWT secrets that are too short or too predictable; session tokens stored in localStorage instead of httpOnly cookies; passwords stored as MD5 or SHA-1 hashes.
A03: Injection
Modern ORMs and query builders reduce this risk significantly — but vibe-coded apps often mix abstractions. An AI that starts with Prisma might add a raw query for performance, and that raw query might concatenate a user-supplied value. The audit needs to find every path from user input to a query or command execution.
A05: Security misconfiguration
This is where the AI's tendency to solve the immediate problem shows up most clearly. Supabase RLS disabled. CORS set to *. Default credentials left on admin interfaces. Security headers missing. The configuration that makes a feature work in development but exposes production is almost never something the AI flagged as wrong — because in development, it wasn't.
A06: Vulnerable and outdated components
AI assistants generate code against their training data, which has a cutoff date. An AI trained on data from 2024 will confidently use a library version from 2024 that has since had three critical CVEs published. Dependency scanning is non-negotiable.
Quick win. Run npm audit --audit-level=high or pip-audit right now and patch everything at high severity or above. This takes 15 minutes and closes the most obvious attack surface. Then run a full audit to find everything the package manager doesn't know about.
Reliability gaps
Reliability findings don't get you breached. They get you woken up at 3am by a user reporting they lost data, a payment that processed twice, or a feature that silently stopped working three weeks ago.
Missing error handling
The AI generates the success path beautifully. It often forgets the failure path. Functions that can throw — API calls, database queries, file operations — need try/catch blocks that log the error, surface it appropriately to the user, and fail in a predictable way. An unhandled promise rejection in a Node.js application will crash the process in Node 15+. A silent catch that swallows the error leaves you with no way to diagnose what went wrong.
Race conditions
Concurrent operations on shared state without proper locking are a common source of data corruption in vibe-coded apps. A user double-submitting a form. A webhook arriving at the same time as a UI action. Two async functions writing to the same record. The AI generates each function in isolation; it doesn't reason about what happens when they run simultaneously.
Missing transaction boundaries
If your app does two related database writes — create an order, then update inventory — and the second write fails, you need the first write to roll back. Without explicit database transactions, you get partial state: an order that exists but inventory that wasn't decremented, or vice versa. Finding these multi-step operations and wrapping them in transactions is a reliability audit's core job.
Finding: The Stripe webhook handler processes events synchronously and returns a 200 only after completing all downstream operations. If the database write on line 89 fails (network blip, lock timeout), the handler returns 500. Stripe will retry the event up to 3 times over 24 hours, but your handler has no idempotency check — the retry will double-process the payment.
Fix: Add an idempotency key check at the top of the handler (store the Stripe event ID, reject duplicates). Move the database write inside a transaction. Return 200 immediately after validating the signature, then process async.
No retry on transient failures
External API calls fail. Networks hiccup. Third-party services have outages. Code that makes a single attempt and surfaces the error to the user — instead of retrying with exponential backoff — creates a brittle user experience and inflated support tickets. The audit should identify every external call and verify it has appropriate retry logic.
Performance debt
Performance findings are often invisible until they aren't. A query that runs in 50ms with 100 rows takes 8 seconds with 100,000. The AI tested it with synthetic data; production has real data.
N+1 queries
The most common performance finding in vibe-coded apps. A list endpoint fetches 50 records, then makes 50 individual database calls to enrich each one — 51 queries total instead of one query with a join. At launch this is imperceptible. At scale it kills your database. Every list endpoint in a vibe-coded app should be reviewed for N+1 patterns.
// Fetches projects, then makes one DB call per project
const projects = await db.project.findMany();
for (const project of projects) {
// This is one query per project — a classic N+1
project.owner = await db.user.findUnique({
where: { id: project.ownerId }
});
}
// Fix: use include/join at the first query
const projects = await db.project.findMany({
include: { owner: true } // one query, not N+1
});
Missing database indexes
The AI creates tables with primary keys. It rarely creates the indexes on foreign keys and frequently-queried fields that make those tables usable at scale. Running EXPLAIN ANALYZE on your most common queries will surface sequential scans that should be index scans. An index on a 100,000-row table can turn a 2-second query into a 3ms one.
Bundle bloat
AI-generated frontend code often imports entire libraries for single utility functions. Moment.js (67kb gzipped) for date formatting. Lodash (71kb) for one array method. A Lighthouse audit of the final bundle consistently reveals 30–60% of the JavaScript payload as unused code. Every 100kb of JavaScript is roughly 1 second of time-to-interactive on a mobile connection.
Synchronous blocking on the main thread
Node.js is single-threaded. Any synchronous CPU-intensive operation blocks all other requests until it completes. Image processing, PDF generation, large CSV parsing — all of these need to be offloaded to a worker thread or a background job. An audit checks for synchronous operations in request handlers that should be async.
The monitoring blind spot
Monitoring is the dimension most consistently absent in vibe-coded apps — and the one that makes the other three dimensions matter less than they should. If you have security vulnerabilities but no monitoring, you won't know when they're exploited. If you have reliability gaps but no error tracking, you won't know when they cause failures. If you have performance issues but no metrics, you won't know until users start complaining.
Error tracking
Sentry is the standard for vibe-coded apps. It's free for reasonable volume, has SDKs for every platform, and takes about 20 minutes to install. Yet the majority of vibe-coded apps shipped to production in 2026 have no Sentry integration — or they have it installed but not configured to capture the errors that actually matter (unhandled promise rejections, API failures, database errors).
The monitoring audit check. We don't just look for a Sentry import. We probe whether unhandled errors actually get captured — by triggering a controlled error through a test endpoint and confirming it appears in the Sentry dashboard. An import that's installed but never reached is the same as no monitoring.
Uptime monitoring
Your app being down is the most visible reliability failure — and the one most easily caught with external monitoring. UptimeRobot, Cronitor, and similar services check your app from external IPs every 1–5 minutes and alert you immediately when it goes down. Without this, you find out when a user tweets about it. This check takes 5 minutes to set up and is free for basic coverage.
Alerting gaps
Many apps have monitoring in place for the application layer but no alerts on infrastructure metrics — memory usage, disk space, database connection pool exhaustion. A Node.js process that hits an out-of-memory error simply restarts; if it's restarting 40 times per hour, you won't know unless you're watching process restarts. The monitoring audit maps every failure mode to an alert.
Logging quality
AI-generated code often uses console.log() for debugging, which creates noisy, unstructured logs that are impossible to query in production. A proper logging strategy uses structured JSON logs (timestamp, level, request ID, user ID where appropriate, message) that can be parsed and searched. The audit checks whether your logs are useful when something goes wrong — not just that they exist.
Supabase RLS: the most common P0 in vibe-coded apps
Supabase deserves its own section because it's the most popular database layer in vibe-coded apps, and its Row Level Security system is the source of more P0 security findings than any other single component we audit.
Supabase's anon key is designed to be used in client-side code. It gives public access to your database — controlled by RLS policies. The model is elegant: your policies enforce access control at the database layer, so even if your application code has bugs, the database itself enforces who can see what.
The problem is that this model only works if the policies are correct. And verifying that a policy is correct requires testing it — not reading it.
The five most common Supabase RLS failures
- RLS enabled but no policies defined. When RLS is enabled with no policies, the default behavior is to deny all access. This sounds safe — but if you're using the service role key in your application code to work around this, you've now disabled all access control entirely.
- Permissive policy with no predicate.
CREATE POLICY "allow all" ON documents FOR SELECT USING (true);This enables RLS (which looks correct) but allows any authenticated user to read every document. The AI generated this when you asked it to "set up RLS". - Policy that references auth.uid() incorrectly. A policy that reads
USING (user_id = auth.uid())will correctly filter rows ifuser_idis a UUID. But ifuser_idis stored as text, the comparison silently fails for all rows. - Missing policy for write operations. A SELECT policy doesn't protect INSERT, UPDATE, or DELETE. Apps frequently have correct read policies and no write policies, allowing any authenticated user to modify or delete records they don't own.
- Service role key in client-side code. The service role key bypasses RLS entirely. It should never be in client-side JavaScript. We find it in client-side code in roughly one in five Supabase audits.
How to test RLS, not just read it. Create two test users. Log in as user A. Create a resource (document, order, profile) as user A. Log in as user B. Attempt to read, modify, and delete user A's resource using the anon key directly against the Supabase REST API. If you can, your RLS policies have a gap. This test takes 10 minutes and should be run before every production deployment.
The free RLS checker at launchreadycode.com/tools/rls-checker.html automates this test for any public Supabase project. Paste your project URL and anon key and we'll probe 12 common RLS vulnerability patterns in under 60 seconds.
Tool-by-tool breakdown: Lovable, Bolt.new, Cursor, and Claude Code
The four major vibe coding platforms each have a distinct security fingerprint — characteristic gaps that appear consistently across the apps built with them. Knowing which platform built the app tells you where to look first.
Lovable
Lovable's dominant security risk is the CVE-2025-48757 class: Supabase Row Level Security disabled at the table level while the anon key is publicly embedded in the client bundle. Because Lovable scaffolds the Supabase schema without enabling RLS, every table is fully readable and writable by any request carrying the anon key — no authentication required. The second most common Lovable finding is the service_role key appearing in client-side JavaScript, which bypasses RLS entirely even when it is enabled. Before shipping any Lovable app: run SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; in your Supabase SQL editor and confirm zero false values. Then grep your compiled frontend bundle for service_role. See: Is Lovable safe? Our full security breakdown.
Bolt.new
Bolt.new apps share the Supabase RLS gap where Supabase is used, but the platform's primary distinctive finding is environment variable exposure in the generated frontend. Bolt frequently inlines secrets into import.meta.env references without guarding them behind server-side calls — meaning sensitive API keys end up in the compiled JavaScript bundle served to every visitor. The second common gap is absent CSRF protection on form submissions: Bolt generates functional forms that POST data without CSRF tokens, leaving them open to cross-site request forgery attacks. Audit check: open DevTools → Network → reload the page → search response bodies for any string matching sk_, _secret, or _key.
Cursor
Cursor produces higher-quality code than the no-code platforms — but its security gaps are more subtle and therefore easier to miss. The most consistent Cursor finding is insecure default CORS configuration: generated Express or Hono backends frequently include cors({ origin: '*' }), which allows any domain to make credentialed requests to your API. The second pattern is missing auth guards on newly generated routes: Cursor adds new API routes to existing files and frequently omits the authentication middleware that protects adjacent routes, because the middleware is applied in a different file the model didn't see. Third: generated Dockerfiles and CI/CD configs (GitHub Actions) regularly expose secrets as ENV lines or unmasked environment variables in workflow logs.
Claude Code
Claude Code generates the strongest baseline security posture of the four platforms — auth guards are more consistently applied, RLS is more likely to be enabled, and secrets handling is generally cleaner. The primary risks appear in infrastructure-as-code and deployment configuration rather than application code: generated Dockerfiles sometimes bake secrets into image layers via ENV or ARG instructions; generated Terraform and Pulumi configs may create overly permissive IAM roles; generated GitHub Actions workflows occasionally expose secrets in run: steps that print to logs. The audit for Claude Code apps should focus more heavily on infrastructure security and less on application-layer auth than audits for other platforms.
Pre-launch security checklist for vibe-coded apps
This checklist covers the findings that appear most frequently in vibe coding security audits. Work through it before your first paying customer signs up.
Secrets management
- Every secret is in an environment variable, not in source code
.envis in.gitignoreand not committed to your repository- You've searched your git history for secrets:
git log -p | grep -E "(API|SECRET|KEY|TOKEN|PASSWORD)" - Your Supabase service role key is only used server-side, never in client code
- All production secrets are rotated from any key that was ever committed
Authentication & access control
- Every API route that returns user data requires authentication
- Every API route verifies the authenticated user owns the requested resource
- Admin routes are behind a separate, strong authentication check
- Supabase RLS is enabled and policies are tested (not just reviewed)
- JWT tokens have a reasonable expiry (24 hours or less for access tokens)
- Password reset and magic-link flows can't be replayed or enumerated
Dependencies
npm auditshows zero high or critical vulnerabilities- Dependencies are pinned to specific versions in package-lock.json
- You have a process for checking for new CVEs (Snyk free tier, Dependabot)
Reliability
- Every external API call is wrapped in try/catch with appropriate error handling
- Payment webhook handlers have idempotency checks
- Multi-step database operations are wrapped in transactions
- Your app starts cleanly if an external dependency is unavailable at startup
Performance
- Your most-called API endpoints have been tested with 1,000+ records in the database
- Lighthouse score on your main page is 80+ on mobile
- No synchronous file system or crypto operations in API request handlers
- Database indexes exist on foreign keys and all fields in WHERE clauses
Monitoring
- Sentry (or equivalent) is installed and capturing unhandled errors in production
- Uptime monitoring is active on your main domain and your API base URL
- You have an alert for when your Stripe webhook stops receiving events
- Application logs are structured (JSON) and captured in a searchable store
- You know what a "normal" error rate looks like and you'll be alerted if it spikes
Want someone to run this audit for you?
We check every item on this list — and 200 more — across all four dimensions. Every finding comes with the exact file, line number, and fix. Delivered in under 2 minutes.
When to run a vibe coding security audit
The honest answer: before you launch. But that's not always how it goes. Here's how to think about timing based on where you are.
Before your first paying customer
This is the highest-value moment for an audit. You have no real user data at risk. You have time to fix things. And the cost of remediating a finding before launch is a fraction of what it costs after — in engineering time, in customer trust, in potential liability. Run the full audit, fix everything P0 and P1, ship.
Before a public launch or Product Hunt
A Product Hunt launch drives traffic from an adversarial population — not adversarial intentionally, but technically sophisticated people who will notice exposed API routes, try edge cases your QA didn't cover, and sometimes just poke around because it's interesting. The 48 hours after a public launch are when your weakest points are most likely to be found. Audit before you go public.
Before raising funding
Due diligence for seed and Series A now routinely includes a technical review. Investors have been burned by portfolio companies with obvious security gaps that emerged post-investment. Having an independent audit report with a clean score — or a clear remediation plan — answers the technical risk question before it's asked.
Before handling sensitive data
If your app is about to start storing health information, financial data, or any PII from users in regulated jurisdictions, the security bar rises significantly. An audit at this inflection point ensures your baseline is sound before the compliance obligations kick in.
Continuously, as you ship
The real answer for shipping teams is continuous monitoring. Every new feature is a potential new vulnerability. Every dependency update is a potential new CVE. Daily automated scans with alerts on new P0 findings — rather than periodic manual audits — is the posture that keeps a shipping team secure. This is what the Continuous Monitoring plans at Launch Ready Code automate.
The re-scan rule. Every time you ship a significant feature, run a scan. Every time you add a new npm dependency, run npm audit. Every time you change your Supabase schema or policies, run the RLS test. These three habits, consistently applied, catch 80% of the issues that would otherwise become incidents.
Closing thoughts
Vibe coding has genuinely changed what's possible for a solo founder or small team. A technically capable person can ship a production SaaS in a weekend. The underlying models are good enough that the code mostly works, mostly securely, most of the time.
"Mostly" is the problem. The gaps AI code generators leave are predictable — they're the same gaps in almost every app we audit. Secrets management. Supabase RLS. Auth on admin routes. Error monitoring. These aren't exotic vulnerabilities. They're the defaults the AI didn't think to question because you didn't ask it to.
A vibe coding security audit isn't a statement about the quality of your work. It's an acknowledgment that the tool you used to build fast has a specific set of blind spots — and they can be identified and mapped in under 2 minutes.
The free scan at launchreadycode.com tells you your Launch Readiness Score in 60 seconds. If you want the full picture — every finding, every fix, a branded PDF report and prioritized roadmap — that's the $499 Launch Readiness Audit Report. Either way, you'll know where you stand before your users find out for you.