Las 5 vulnerabilidades más comunes en código generado por Cursor
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.
Tabla de contenidos
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:
- Nunca commitees archivos
.env - Asegúrate de que
.gitignoreesté configurado ANTES del primer commit - Si ya lo hiciste, usa
BFG Repo-Cleanerpara eliminarlo del historial - 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:
- Evita
dangerouslySetInnerHTMLa menos que sea absolutamente necesario - Si lo usas, sanitiza siempre con
DOMPurify - En Node/Express, usa
xssosanitize-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:
- Define explícitamente qué orígenes están permitidos
- Nunca uses
origin: '*'junto concredentials: true - 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:
- Valida SIEMPRE en el backend, aunque ya valides en frontend
- Usa librerías como
Zod,JoioYup - 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