← Blog
·8 min read

Los Errores de Seguridad que Cursor Pone en tu Código (y Cómo Evitarlos)

Cursor genera código funcional pero con patrones de seguridad inseguros. Estos son los errores más comunes que aparecen en repos reales y cómo prevenirlos.

Rod

Founder & Developer

Cursor es una herramienta excelente para programar rápido. También tiene patrones de errores de seguridad que aparecen en repos reales una y otra vez. No porque Cursor sea mala herramienta — sino porque ninguna herramienta de IA agrega seguridad a menos que se lo pidas explícitamente.

Al escanear repos con Data Hogo, los proyectos generados principalmente con herramientas de IA muestran los mismos cinco patrones inseguros. Los repasamos todos, con el código vulnerable y el fix.


Error 1: Autenticación omitida en API routes

Este es el más frecuente y el más peligroso. Cursor genera el endpoint que pediste. Si no le dijiste "verifica que el usuario esté autenticado", no lo hace.

// MAL: lo que Cursor genera cuando pides "un endpoint para obtener los usuarios"
// src/app/api/users/route.ts
export async function GET() {
  const users = await prisma.user.findMany();
  return Response.json(users);
}
 
// BIEN: con verificación de autenticación
import { createClient } from "@/lib/supabase/server";
 
export async function GET() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
 
  if (!user) {
    return Response.json({ error: "No autorizado" }, { status: 401 });
  }
 
  // Solo el perfil del usuario autenticado, no todos los usuarios
  const userData = await prisma.user.findUnique({
    where: { id: user.id },
    select: { id: true, email: true, name: true }, // solo los campos necesarios
  });
 
  return Response.json(userData);
}

El patrón correcto para cualquier API route que devuelva datos sensibles: la primera línea verifica la sesión. Si no hay sesión, retorna 401. Nada más corre.

Broken Access Control es el #1 del OWASP Top 10. Cursor no lo resuelve por defecto.


Error 2: Secretos y API keys inline

Cursor inlinea credenciales cuando tú se las das en el prompt o cuando el contexto del proyecto las tiene visibles. El resultado es código que funciona pero con tu key de Stripe o de OpenAI en texto plano.

// MAL: la key quedó directamente en el código
const stripe = new Stripe("sk_live_xxxxxxxxxxxxxxxxxxx");
const openai = new OpenAI({ apiKey: "sk-proj-xxxxxxxxx" });
 
// MAL también: en un objeto de configuración
const config = {
  database: {
    password: "mi_password_de_produccion",
    host: "db.supabase.co"
  }
};
 
// BIEN: siempre desde variables de entorno
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

Si tu repo es público, una key inline tarda segundos en ser encontrada por bots. Las consecuencias van desde una factura inesperada de cinco cifras hasta pérdida directa de dinero.

Cómo prevenirlo con Cursor: agrega un archivo .cursorrules en la raíz del proyecto:

# .cursorrules
Reglas de seguridad:
- NUNCA inlines API keys, tokens o contraseñas en el código
- SIEMPRE usa process.env.NOMBRE_VARIABLE para credenciales
- Si necesitas mostrar un ejemplo, usa process.env.EXAMPLE_KEY como placeholder

Con esas instrucciones en contexto, Cursor respeta el patrón de variables de entorno.


Error 3: Validación de input ausente o incompleta

Cursor genera handlers que asumen que el cliente manda datos válidos. En la realidad, cualquier campo puede ser manipulado — tipos incorrectos, valores fuera de rango, SQL injection.

// MAL: Cursor genera esto cuando pides "procesa el pago"
export async function POST(req: Request) {
  const { userId, amount, description } = await req.json();
  await createPayment(userId, amount, description);
}
 
// BIEN: valida antes de tocar la lógica de negocio
import { z } from "zod";
 
const paymentSchema = z.object({
  userId: z.string().uuid(),
  amount: z.number().int().positive().max(100000), // centavos, máximo $1000
  description: z.string().min(1).max(200).trim(),
});
 
export async function POST(req: Request) {
  const body = await req.json();
  const result = paymentSchema.safeParse(body);
 
  if (!result.success) {
    return Response.json(
      { error: "Datos inválidos", details: result.error.flatten() },
      { status: 400 }
    );
  }
 
  const { userId, amount, description } = result.data;
  await createPayment(userId, amount, description);
}

El patrón de Zod es el estándar en proyectos TypeScript modernos. Valida tipo, formato y restricciones de negocio en una sola llamada. Cursor sabe usarlo — solo hay que pedírselo.


Error 4: Server Actions sin protección

Los Server Actions de Next.js App Router son el punto ciego más nuevo. Cursor los genera como funciones TypeScript normales — pero son endpoints HTTP reales que cualquiera puede llamar directamente.

// MAL: Server Action sin verificación de sesión
"use server";
 
export async function updateProfile(formData: FormData) {
  const name = formData.get("name") as string;
  const userId = formData.get("userId") as string;
 
  // Cualquiera puede llamar esto con cualquier userId
  await db.users.update({
    where: { id: userId },
    data: { name },
  });
}
 
// BIEN: verifica sesión y no confíes en el userId del cliente
"use server";
 
import { createClient } from "@/lib/supabase/server";
 
export async function updateProfile(formData: FormData) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
 
  if (!user) throw new Error("No autorizado");
 
  const name = formData.get("name") as string;
  if (!name || name.trim().length === 0) throw new Error("Nombre requerido");
 
  // Usa el ID de la sesión, no el que mandó el cliente
  await db.users.update({
    where: { id: user.id },
    data: { name: name.trim() },
  });
}

La regla de oro: nunca confíes en el userId que manda el cliente. Usa siempre el ID de la sesión verificada del servidor.


Error 5: Security headers no configurados

Cursor raramente configura los security headers HTTP porque no son visibles en el comportamiento funcional de la app. Generan código que funciona en local, sin los headers que los navegadores usan para activar protecciones adicionales.

// next.config.ts — Cursor raramente genera esto
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "X-Content-Type-Options", value: "nosniff" },
          { key: "X-Frame-Options", value: "DENY" },
          {
            key: "Strict-Transport-Security",
            value: "max-age=63072000; includeSubDomains; preload"
          },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
          { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
        ],
      },
    ];
  },
};

Puedes verificar que tu app deployada los tiene configurados con el verificador de cabeceras de seguridad gratis.


Cursor vs Claude Code: ¿alguno es más seguro?

La comparación honesta: ninguna herramienta de IA genera código seguro por defecto. La diferencia está en el comportamiento cuando hay ambigüedad.

Cursor (basado en los modelos de Anthropic y OpenAI según tu configuración) tiende a generar el código más directo que satisface el prompt. Si el prompt no menciona seguridad, la seguridad no está.

Claude Code (el modo autónomo con el SDK de Anthropic) tiende a ser más conservador en operaciones con consecuencias irreversibles y a pedir confirmación antes de hacer cambios destructivos. Pero también genera código sin autenticación si no se lo pides explícitamente.

La conclusión práctica: la herramienta no importa tanto como el prompt y la revisión. Un .cursorrules bien redactado con patrones de seguridad produce mejor output que el mismo modelo sin contexto.

Y ninguna herramienta reemplaza un escaneo automatizado del código generado.


Cómo reducir los errores de seguridad en Cursor

Tres cambios concretos:

1. Agrega contexto de seguridad en .cursorrules:

# .cursorrules
Seguridad:
- SIEMPRE verifica autenticación al inicio de cada API route y Server Action
- NUNCA inlines secretos — usa siempre process.env
- SIEMPRE valida inputs con Zod antes de la lógica de negocio
- Usa el ID de sesión del servidor, nunca el userId del cliente

2. Escanea el repo después de cada sesión de vibe coding:

El código generado cambia más rápido de lo que puedes revisarlo manualmente. Un scan automático cierra la brecha.

3. Revisa los endpoints nuevos antes de hacer merge:

Pregunta para cada endpoint nuevo: ¿verifica autenticación? ¿valida el input? ¿qué pasa si alguien llama esto directamente?

Escanea tu repo gratis y encuentra los errores que Cursor dejó →


Preguntas frecuentes

¿Cursor genera código inseguro por defecto?

Cursor genera código funcional — que puede tener o no vulnerabilidades dependiendo de cómo lo promutas. Los errores más comunes no son fallos del modelo sino ausencias: Cursor no agrega autenticación, validación ni security headers a menos que se los pidas explícitamente. El código funciona pero le faltan las capas de seguridad.

¿Claude Code es más seguro que Cursor?

Depende del contexto y el prompt. Claude Code tiende a ser más conservador con patrones de seguridad y a pedir confirmación en operaciones irreversibles. Pero ninguna herramienta de IA garantiza código seguro por defecto — todas requieren que el developer revise activamente.

¿Cómo le digo a Cursor que genere código seguro?

La forma más efectiva es agregar contexto de seguridad en tu prompt o en el archivo .cursorrules del proyecto. Por ejemplo: "Siempre verifica autenticación al inicio de cada API route. Nunca inlines secretos en el código. Valida todos los inputs con Zod." Con esas instrucciones, el output mejora significativamente.

¿Qué tan comunes son los errores de seguridad en repos generados con Cursor?

Al escanear repos con Data Hogo, los proyectos identificados como generados principalmente con IA muestran vulnerabilidades en más del 60% de los casos. Los más frecuentes son API routes sin autenticación y dependencias con CVEs activos.

¿Debo revisar manualmente el código que genera Cursor o puedo automatizarlo?

Ambas cosas. La revisión manual es importante para entender qué genera la herramienta. Pero para la cobertura sistemática — secretos, dependencias, patrones inseguros en todo el codebase — necesitas un scanner automatizado. Data Hogo revisa tu repo completo en menos de 5 minutos, incluyendo el código generado por IA.

cursorvibe codingseguridadvulnerabilidadesclaude codecódigo IAerrores comunes