12 Critical Security Flaws in Vibe-Coded Apps (and How to Check for Each)
A practical vulnerability checklist across authentication, database RLS, secrets exposure, and client-side authorization in AI-generated prototypes.

Vibe-coded applications frequently contain critical security flaws because AI code generators optimize for local compilation and rapid visual demonstration rather than defensive trust boundaries. When an LLM generates full-stack code from natural language prompts, it routinely omits database row-level security, exposes secret keys through public client prefixes, trusts user-supplied roles in request bodies, and fails to authenticate server-side endpoints.
Securing a vibe-coded prototype requires an intentional verification pass across the authentication lifecycle, database access policies, environment variable scoping, and external webhook integrations before onboarding real users or sensitive data.
The short version
AI generators write code that works on the happy path, but production systems defend against hostile inputs. To secure a vibe-coded app, audit row-level security in your database, audit environment variables for exposed secrets, verify that server actions and API routes authenticate every request independently, sanitize database queries against mass assignment, validate external webhook signatures, and establish strict rate limiting on public endpoints.
Use this checklist alongside our AI-generated code audit checklist and sample AI code rescue audit report to evaluate launch readiness.
1. Unenforced or Permissive Row-Level Security (RLS)
AI assistants frequently generate database tables without enabling Row-Level Security, or they write placeholder policies such as USING (true) that allow any authenticated or anonymous user to read and mutate all tenant records.
The risk: Any user with a valid client token can query or delete rows belonging to other accounts simply by passing a different tenant_id or user_id to the database client.
How to check: Inspect your database migrations or query PostgreSQL directly:
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
If rowsecurity is false for any user-facing table, RLS is disabled. Next, inspect the active policies:
SELECT tablename, policyname, permissive, roles, qual, with_check
FROM pg_policies
WHERE schemaname = 'public';
Verify that every policy enforces tenancy, for example: auth.uid() = user_id.
2. Public Client Secret Leaking via Framework Prefixes
In frameworks like Next.js, Vite, and Nuxt, environment variables prefixed with NEXT_PUBLIC_, VITE_, or NUXT_PUBLIC_ are embedded directly into public JavaScript bundles delivered to the browser.
The risk: AI prompts like "connect Stripe" often lead the generator to place NEXT_PUBLIC_STRIPE_SECRET_KEY or NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY in the .env file, granting anyone who opens DevTools full administrative access to payment rails or database bypass permissions.
How to check: Search your repository for client-prefixed secrets:
git grep -E "NEXT_PUBLIC_.*(SECRET|KEY|TOKEN|PASSWORD|PRIVATE)"
git grep -E "VITE_.*(SECRET|KEY|TOKEN|PASSWORD|PRIVATE)"
Confirm that only publishable keys (such as NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) appear in client bundles.
3. Client-Controlled Authorization and Role Spoofing
AI code generators often store user roles in client-accessible state or trust request payloads that contain administrative flags.
The risk: An attacker modifies a profile update request from {"name": "Alice"} to {"name": "Alice", "role": "admin"}. If the backend handler blindly passes the payload to the ORM, the user escalates privileges instantly.
How to check:
Inspect your API route handlers and server actions. Ensure that role checks query the database or an immutable, cryptographically signed session token, never req.body.role or unverified client headers.
4. Mass Assignment in ORM and Database Mutations
Prototypes built with Prisma, Drizzle, or Supabase frequently pass unsanitized input objects directly into update operations:
// Vulnerable pattern generated by LLMs
await prisma.user.update({
where: { id: session.userId },
data: req.body, // Insecure: user can overwrite plan, role, or credit balance
});
The risk: Users can overwrite internal fields such as is_verified, billing_tier, or organization_id.
How to check: Verify that every database mutation uses an explicit schema parser (such as Zod) with strict allowlists:
const UpdateProfileSchema = z.object({
displayName: z.string().max(50),
avatarUrl: z.string().url().optional(),
}).strict();
const safeData = UpdateProfileSchema.parse(req.body);
5. Unauthenticated Server Actions and API Routes
A common pattern in vibe-coded Next.js apps is protecting pages with client-side redirects while leaving the underlying Server Actions or API routes unauthenticated.
The risk: Protecting /admin with a client-side component check does not stop an attacker from invoking POST /api/admin/users or executing the underlying server action directly with curl.
How to check:
Audit every file in app/api/ and every file containing "use server". Confirm that the first lines retrieve and validate the active session:
export async function deleteProjectAction(projectId: string) {
"use server";
const session = await getSession();
if (!session?.userId) {
throw new Error("Unauthorized");
}
// Enforce tenant boundary
await verifyProjectOwnership(session.userId, projectId);
// ... perform deletion
}
6. Missing Rate Limiting on Authentication and LLM Endpoints
AI code rarely includes rate limiting middleware because local developer environments do not experience credential stuffing or denial-of-wallet attacks.
The risk: Attackers can brute-force login screens, spam password reset emails, or flood expensive LLM completion endpoints, resulting in service outages and unexpected API bills.
How to check: Confirm that an edge rate limiter (such as Upstash Redis or Cloudflare WAF rules) guards:
/api/auth/*/api/loginand/api/register- Any endpoint invoking downstream LLMs or third-party paid APIs.
7. Unverified Webhook Signatures
When integrating Stripe, Lemon Squeezy, Clerk, or Resend, AI models often write handlers that parse the JSON payload without verifying the cryptographic signature header.
The risk: Anyone can send a forged HTTP POST to /api/webhooks/stripe containing {"type": "checkout.session.completed", "customer": "attacker"} and receive access to premium tiers for free.
How to check: Inspect webhook route handlers. Ensure they read the raw request body and verify the cryptographic signature:
const headerPayload = await headers();
const signature = headerPayload.get("stripe-signature");
const event = stripe.webhooks.constructEvent(
rawBody,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
8. Insecure Direct Object References (IDOR)
AI-generated endpoints frequently query resources by primary key alone, assuming that knowing an ID implies permission to view or edit it.
// Vulnerable IDOR pattern
export async function GET(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const invoice = await db.invoice.findUnique({ where: { id } });
return Response.json(invoice);
}
The risk: Users can view any other customer's invoices, private documents, or project notes simply by incrementing or guessing UUIDs.
How to check: Ensure all database lookups compound the target resource ID with the authenticated tenant:
const { id } = await params;
const invoice = await db.invoice.findFirst({
where: {
id,
organizationId: session.organizationId
},
});
if (!invoice) return new Response("Not Found", { status: 404 });
9. SSRF and Unrestricted LLM Tool Execution
When an app allows AI agents to fetch URLs, read files, or execute tools based on user prompts, the backend can become a proxy for internal network exploitation.
The risk: An attacker prompts the agent to "summarize http://169.254.169.254/latest/meta-data/", leaking cloud infrastructure credentials via Server-Side Request Forgery.
How to check:
- Restrict outbound HTTP tool calls to an explicit allowlist of public domain names.
- Block internal private IP ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, and cloud metadata addresses). - Never allow an LLM tool to execute arbitrary shell commands in an uncontained container.
10. Missing CORS and CSRF Protection on State-Changing Routes
AI generators often configure permissive CORS policies (Access-Control-Allow-Origin: *) to resolve browser cross-origin errors during local prototyping.
The risk: A malicious website visited by an authenticated user can make cross-origin requests to your API using ambient cookies.
How to check:
Review API route middleware and CORS configurations. State-altering routes (POST, PUT, DELETE, PATCH) must either require custom authentication headers (which cannot be sent cross-origin without preflight consent) or enforce strict SameSite cookie attributes.
11. Sensitive Stack Traces Leaking in Production
Default error handlers in AI-generated code often return full error objects to the client to assist debugging:
catch (error: any) {
return Response.json({ error: error.message, stack: error.stack }, { status: 500 });
}
The risk: Stack traces reveal file system paths, database table names, ORM queries, and internal library versions that attackers use to craft targeted exploits.
How to check: Verify that production error boundaries log the error internally to an observability platform and return only an opaque error ID and generic message to the client.
12. Hallucinated and Vulnerable Package Dependencies
LLMs occasionally invent npm package names or suggest outdated packages with known Common Vulnerabilities and Exposures (CVEs).
The risk: Threat actors register hallucinated package names on public registries (slopsquatting) containing malicious install scripts.
How to check: Run automated vulnerability audits and verify lockfiles before running builds:
pnpm audit
npm audit --omit=dev
Inspect package.json manually to ensure every dependency is actively maintained and serves an authentic architectural purpose.
Where to go next
Securing an AI-generated codebase is a methodical process. If your team is preparing to launch a vibe-coded MVP, read our step-by-step guide on how to clean up a vibe-coded app before launch, or consult our diagnostic guide for debugging a broken vibe-coded app. You can inspect common authorization flaws in our breakdown of Bolt, v0, and Replit auth and RLS mistakes, review our pre-launch checklist for Cursor-built codebases, evaluate prototypes using our rescue-vs-rewrite framework, or read our buyer's guide to vibe coding cleanup services to understand audit scopes and deliverables.
When launch blockers or security vulnerabilities require hands-on remediation, Aatvi's AI Code Rescue engagement audits the codebase, closes authorization holes, hardens data boundaries, and installs CI verification gates to prepare prototypes for production scale.
Source notes
- The OWASP Top 10 for Large Language Model Applications documents prompt injection, insecure output handling, and excessive agency risks.
- The NIST Secure Software Development Framework (SSDF) SP 800-218 outlines foundational practices for mitigating software vulnerabilities throughout the development lifecycle.
- Research from the Carnegie Mellon University Software Engineering Institute analyzes security pitfalls and validation gaps in automated code generation.
- Veracode's GenAI Code Security Report highlights that automated code generation introduces security flaws into a substantial percentage of generated modules when left uninspected.
Start here: AI Code Rescue audit and stabilization.
