Vibe code, big risks: how to fix common security holes

Jun 07, 2025

Vibe coding (relying on ai tools to generate code snippets, components, or entire modules) can dramatically speed up development. but it also introduces subtle security risks. ai-generated code often lacks context around your app's specific security requirements, leading to gaps that bad actors can exploit. in this post, we'll explore the most common vulnerabilities introduced by ai-assisted development, demonstrate examples in react/next.js, and show you exactly how to patch them (these fixes can be applied to any framework).

Vibe coding bugs tendencyPhoto by Can Vardar

by the end, you'll understand:

  • why ai-generated code can be insecure
  • the top security holes to watch for
  • concrete react/next.js examples of "bad" ai code
  • how to fix them in

let's dive in.

Why ai-generated code can be risky

AI coding assistants (cursor, lovable...) excel at filling in boilerplate and scaffolding. but they don't automatically think about:

  • the performance and security problems that their generated code can cause
  • treating your application as if you were not going to release it to production
  • leaving your endpoints free of rate limits or your keys exposed
  • organizational policies or compliance (gdpr, hipaa, etc.)

Let's look at common examples that developers don't usually care about that expose their apps, as well as their solutions. We will also add common generations of code that can make your app's performance worse.

1. Lack of rate limiting

AI code assistants will generate your endpoints but won't add rate limiting by default, leaving your APIs vulnerable to abuse. Let's look at examples of how to properly secure these endpoints.

Vulnerable endpoint (AI-generated)

This is a typical endpoint generated by AI assistants like Cursor. It implements basic authentication functionality but lacks any protection against brute force attacks or DoS attempts. An attacker could make unlimited login attempts, potentially compromising user accounts or overloading your server.

// app/api/your-endpoint/route.ts
// nextjs endpoint but can be applied to any framework
export async function POST(request: Request) {
  // example user logic
  const userData = await request.json();
  const { username } = userData;

  // handler logic
  return Response.json({ username, status: 'success' })
}

Secure endpoint (with rate limiting)

Here's the same endpoint properly secured using Upstash Redis for rate limiting. We implement a sliding window rate limiter that allows 10 requests per minute per IP address. This modern approach is perfect for serverless environments and provides precise control over API access patterns. The sliding window mechanism is more accurate than traditional rate limiting and helps prevent request bursts at window boundaries.

// app/api/your-endpoint/route.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
import requestIp from 'request-ip'

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
})

// Create a sliding-window rate limiter (10 reqs/minute)
const rateLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(10, '1m'),
})

export async function POST(request: Request) {
  const ip = requestIp.getClientIp(request) || 'anonymous'
  const { success } = await rateLimiter.limit(`ip:${ip}`)
  const userData = await request.json();
  const { username } = userData;

  if (!success) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429 }
    )
  }

  // handler logic
  return Response.json({ username, status: 'success' })
}

2. Input validation / sanitization

To be continued...