Seguridad en código IA

Las 5 vulnerabilidades más comunes en código generado por Cursor

9 de febrero de 2026
Vibe2Prod
8 min

He auditado más de 30 apps construidas con Cursor. Estas son las 5 vulnerabilidades de seguridad que aparecen en casi todas ellas, y cómo corregirlas antes de lanzar.

Cuando usas Cursor para generar código, ganas velocidad. Pero a veces, pierdes seguridad sin darte cuenta.

He auditado más de 30 aplicaciones construidas con Cursor en los últimos 6 meses. Aquí están los 5 problemas de seguridad que aparecen en prácticamente todas ellas.


1. SQL Injection por falta de parametrización

Este es probablemente el más común. Cursor genera queries concatenadas en lugar de parametrizadas.

❌ Código vulnerable (lo que Cursor a menudo genera):

const userId = req.query.id;
const query = `SELECT * FROM users WHERE id = ${userId}`;
const result = await db.query(query);

Un atacante puede pasar id = 1 OR 1=1 y acceder a todos los usuarios.

✅ Código seguro:

const userId = req.query.id;
const query = 'SELECT * FROM users WHERE id = ?';
const result = await db.query(query, [userId]);

O con un ORM como Prisma:

const user = await prisma.user.findUnique({
  where: { id: parseInt(req.query.id) }
});

Por qué pasa: Cursor asume que el desarrollador validará después. Pero si no lo haces, quedas expuesto.

Cómo prevenirlo: Usa siempre consultas parametrizadas o un ORM. Nunca concatenes variables directamente en SQL.


2. Secrets hardcodeados en el repositorio

Este error es tan común que duele. He visto API keys, contraseñas de base de datos y tokens de autenticación committeados al repositorio público.

❌ Código vulnerable:

// config.js
const API_KEY = "sk_live_51234567890abcdef";
const DB_PASSWORD = "SuperSecret123!";
const STRIPE_KEY = "sk_test_xyz";

Luego esto se commitea a GitHub y está ahí para siempre en el historial.

✅ Código seguro:

// config.js
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;
const STRIPE_KEY = process.env.STRIPE_KEY;

.env (local, NO en repositorio):

API_KEY=sk_live_51234567890abcdef
DB_PASSWORD=SuperSecret123!
STRIPE_KEY=sk_test_xyz

.gitignore:

.env
.env.local
.env.*.local

Por qué pasa: Cursor no sabe dónde pones tus secretos. Generas rápido, comitteas, y listo.

Cómo prevenirlo:

  1. Nunca commitees archivos .env
  2. Asegúrate de que .gitignore esté configurado ANTES del primer commit
  3. Si ya lo hiciste, usa BFG Repo-Cleaner para eliminarlo del historial
  4. Rota los secrets inmediatamente si quedaron expuestos

3. XSS (Cross-Site Scripting) por falta de sanitización

Cuando muestras contenido generado por el usuario sin validarlo, abres la puerta al XSS.

❌ Código vulnerable (React):

export default function UserProfile({ username, bio }) {
  return (
    <div>
      <h1>{username}</h1>
      <div dangerouslySetInnerHTML={{ __html: bio }} />
    </div>
  );
}

// Alguien pasa: bio = "<img src=x onerror='alert(document.cookie)' />"

Esto ejecutará JavaScript en el navegador de otros usuarios.

✅ Código seguro:

import DOMPurify from 'dompurify';

export default function UserProfile({ username, bio }) {
  const cleanBio = DOMPurify.sanitize(bio);
  
  return (
    <div>
      <h1>{username}</h1>
      <div dangerouslySetInnerHTML={{ __html: cleanBio }} />
    </div>
  );
}

O mejor: si el contenido es solo texto, no uses dangerouslySetInnerHTML:

export default function UserProfile({ username, bio }) {
  return (
    <div>
      <h1>{username}</h1>
      <p>{bio}</p>  {/* React escapa automáticamente */}
    </div>
  );
}

Por qué pasa: Cursor genera código que funciona rápido pero no siempre seguro. El dangerouslySetInnerHTML es útil en casos reales, pero requiere sanitización explícita.

Cómo prevenirlo:

  1. Evita dangerouslySetInnerHTML a menos que sea absolutamente necesario
  2. Si lo usas, sanitiza siempre con DOMPurify
  3. En Node/Express, usa xss o sanitize-html

4. CORS mal configurado (o no configurado)

Cursor a menudo genera APIs sin configurar CORS, o lo hace de forma insegura.

❌ Código vulnerable (Express):

app.use(cors());  // Permite requests desde CUALQUIER origen

// O peor:
app.use(cors({
  origin: '*',  // Mismo efecto
  credentials: true
}));

Cualquier sitio web malicioso puede hacer requests a tu API en nombre de tus usuarios.

✅ Código seguro:

const cors = require('cors');

const allowedOrigins = ['https://tudominio.com', 'https://app.tudominio.com'];

app.use(cors({
  origin: function(origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('No permitido por CORS'));
    }
  },
  credentials: true,
  optionsSuccessStatus: 200
}));

Por qué pasa: Cursor genera rápido para hacer funcionar demos. La seguridad CORS viene después... y a veces nunca llega.

Cómo prevenirlo:

  1. Define explícitamente qué orígenes están permitidos
  2. Nunca uses origin: '*' junto con credentials: true
  3. En desarrollo: localhost:3000. En producción: tus dominios reales

5. Falta de validación de inputs en el backend

El formulario está validado en el frontend... pero Cursor olvida validar en el backend.

❌ Código vulnerable:

// Backend sin validación
app.post('/api/users', async (req, res) => {
  const { email, age, password } = req.body;
  
  // Directamente a la BD, sin verificar nada
  const user = await User.create({ email, age, password });
  res.json(user);
});

Un atacante puede:

  • Enviar age: "abc" (debería ser número)
  • Enviar email inválido
  • Enviar password de un solo carácter
  • Inyectar campos adicionales que no debería

✅ Código seguro (con Zod):

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Email inválido'),
  age: z.number().int().min(18).max(120),
  password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/),
});

app.post('/api/users', async (req, res) => {
  try {
    const validData = userSchema.parse(req.body);
    const user = await User.create(validData);
    res.json(user);
  } catch (error) {
    res.status(400).json({ error: error.errors });
  }
});

Por qué pasa: Es fácil olvidarlo. El frontend valida, funcionan los tests, y se ve bien. Pero basta un request manual para romper todo.

Cómo prevenirlo:

  1. Valida SIEMPRE en el backend, aunque ya valides en frontend
  2. Usa librerías como Zod, Joi o Yup
  3. Nunca confíes en datos del cliente

Resumen

Cursor es una herramienta excelente para ir rápido. Pero rápido no es lo mismo que seguro.

Antes de lanzar a producción:

✅ Revisa todas las queries SQL
✅ Busca secrets en el código
✅ Valida todos los inputs en el backend
✅ Sanitiza contenido de usuarios
✅ Configura CORS correctamente

Si necesitas una revisión completa de seguridad, puedo hacerla en 5-7 días. Agenda una auditoría.


Referencias

[1] Veracode. (2025). AI-Generated Code Security Risks. https://www.veracode.com/blog/ai-generated-code-security-risks/

[2] OWASP. (2024). OWASP Top 10 2024. https://owasp.org/www-project-top-ten/

¿Necesitas ayuda con tu app?

Si quieres revisar código, configuración o despliegue antes de abrir producción, vemos tu caso en una llamada de 30 minutos.

Agenda una llamada