Punto ciego de seguridad.
Los asistentes de IA generan código funcional que omite en silencio los controles de seguridad que una persona añadiría por costumbre —validación de entradas, autorización, codificación de salidas, gestión de secretos— porque «compila y devuelve 200» parece estar terminado.
##Signos y síntomas
Quien revisa detecta un Punto ciego de seguridad cuando el código funciona en el camino feliz pero los controles que un humano consciente de la seguridad añade por reflejo sencillamente no están. El modelo demuestra la funcionalidad, no la defensa. Señales reveladoras:
- Consultas/comandos construidos por concatenación de cadenas en lugar de parametrizados (puntos de inyección SQL/comandos/LDAP).
- La entrada del usuario fluye a la salida sin codificación (XSS reflejado/almacenado) o a
fs/child_process/fetchsin lista de permitidos (path traversal, SSRF). - El endpoint autentica pero nunca autoriza: comprueba quién eres, nunca si puedes tocar este registro (IDOR / autorización a nivel de objeto rota).
- Secretos incrustados: claves de API, tokens, contraseñas de BD en línea "para que arranque."
- Controles ausentes por omisión: sin token CSRF, sin cabeceras de seguridad, sin límite de tasa, criptografía débil/heredada (MD5, SHA-1,
Math.random()para tokens). - Teatro de comentarios: un comentario
// validate inputo// TODO: add authocupa el lugar donde debería estar la comprobación real. OX Security encontró comentarios en línea excesivos ("Comments Everywhere") en el 90–100% del código generado por IA que estudió; en este olor, ese comentario suele sustituir a la lógica que falta.
// Manejador de Express generado por IA — parece completo, lleva cuatro agujeros
app.get("/api/orders/:id", authMiddleware, async (req, res) => {
const apiKey = "sk_live_8f3b2a91c7d44e0f"; // 1. secreto incrustado
const { rows: [order] } = await db.query(
`SELECT * FROM orders WHERE id = ${req.params.id}` // 2. inyección SQL
);
res.send(`<h1>Order for ${order.customerName}</h1>`); // 3. XSS reflejado
// 4. IDOR: authMiddleware prueba el login, nunca comprueba order.userId === req.user.id
});
Cuatro vulnerabilidades distintas, cero tests que fallen: el olor es lo que no está ahí.
##Causas del problema
Por qué los modelos lo producen
- El objetivo del entrenamiento es la verosimilitud, no la seguridad. La predicción del siguiente token optimiza para la forma más común de escribir código, y los corpus públicos están saturados de fragmentos de tutorial con consultas concatenadas y sin autorización. El artículo de OX Security sobre la seguridad del vibe coding lo dice sin rodeos: "La concatenación de cadenas es lo que aparece con más frecuencia en los datos de entrenamiento". En el benchmark SusVibes de Carnegie Mellon, con 200 tareas reales de desarrollo de funcionalidades, SWE-Agent con Claude 4 Sonnet produjo soluciones funcionalmente correctas en torno al 61% de las veces, pero seguras solo en torno al 10,5%.
- "Terminado" se define por el camino feliz. Un LLM optimiza para que la petición visible funcione. Los controles de seguridad no producen ninguna señal positiva en una ejecución rápida —una comprobación ausente no hace fallar ningún test ni rompe ninguna demo—, así que se descartan.
- Sin contexto del repositorio. El modelo no puede ver tu función auxiliar
authorize(), tu esquema de validación ni tu gestor de secretos, así que incrusta una clave literal o se salta la comprobación en lugar de reutilizar un control existente. - Complacencia + velocidad. Devuelve exactamente lo que pediste ("añade un endpoint de pedidos"), no el modelo de amenazas que lo rodea, y los cuellos de botella como la revisión y el modelado de amenazas son justo lo que elimina el desarrollo a velocidad de IA. La conclusión central de OX Security es que el código generado por IA no contiene más vulnerabilidades por línea que el escrito por personas; lo que lleva código vulnerable a producción es la velocidad, porque "una revisión de código adecuada simplemente no puede escalar al ritmo de la nueva velocidad de producción". OX advierte además de aplicaciones "inseguras por torpeza" (insecure by dumbness): creadas con IA y desplegadas por usuarios no técnicos sin la experiencia en seguridad necesaria para notar lo que falta.
- Obsolescencia por fecha de corte del entrenamiento. Los modelos recurren a patrones de criptografía y de bibliotecas que estaban bien hace años (MD5, SHA-1, opciones de TLS obsoletas, flujos de autenticación antiguos).
Por qué hace daño
- Explotabilidad directa. El 2025 GenAI Code Security Report de Veracode (80 tareas de programación, más de 100 LLM) encontró fallos de seguridad en el 45% de los casos, y los modelos no lograron protegerse contra el cross-site scripting (CWE-80) en el 86% de los casos. CodeRabbit encontró vulnerabilidades XSS 2,74 veces más a menudo en pull requests coescritos con IA que en los escritos solo por personas; Tenzai comprobó que ninguna de las 15 aplicaciones creadas por cinco agentes de programación tenía protección CSRF ni configuraba cabeceras de seguridad; Escape.tech halló más de 400 secretos expuestos en más de 5.600 aplicaciones hechas con vibe coding.
- Invisible para los tests y para quien lee. Una comprobación de autorización ausente no tiene ningún test que falle ni ningún diff al que señalar, así que pasa la revisión sin problemas, y el volumen de salida de la IA significa que la revisión no puede escalar para atraparla.
- Deuda técnica que se acumula. El análisis de GitClear de 2025 sobre 211 millones de líneas modificadas halló que las líneas asociadas a refactorización ("movidas") cayeron del 25% de las líneas cambiadas en 2021 a menos del 10% en 2024, mientras que las copiadas y pegadas subieron del 8,3% al 12,3% y en 2024 superaron por primera vez a las movidas; los bloques con cinco o más líneas duplicadas se multiplicaron aproximadamente por ocho durante 2024. El mismo patrón inseguro se clona por todo el código y multiplica el radio de impacto de cada punto ciego.
##Tratamiento
Tácticas de revisión y de prompts
- Nombra el modelo de amenazas en el prompt. "Escribe este endpoint asumiendo que el
idestá controlado por un atacante; aplica autorización a nivel de objeto, parametriza todas las consultas, codifica toda la salida y lee los secretos desdeprocess.env." Especificar al adversario invierte el comportamiento por defecto. - Fuerza la reutilización de los controles existentes. "Usa nuestra función auxiliar
authorize(user, 'order', id)y el validadororderSchema: no escribas validación nueva." Esto contrarresta directamente la causa de la falta de contexto del repositorio (y el olor relacionado de Código Duplicado). - Haz que el modelo ejecute las herramientas. Exige que ejecute
semgrep --config p/owasp-top-ten,gitleaks detect,npm audity tu configuración deeslint-plugin-security, y que arregle lo que marquen, antes de declararlo terminado. - Pide los negativos de forma explícita. "Enumera las cuestiones de autenticación, autorización, validación de entradas, codificación de salidas y secretos de este manejador, y muestra dónde se aplica cada una." Un hueco en esa lista es el fallo.
- Modela las amenazas del diff, no de la línea. Los agujeros peligrosos son omisiones, así que revisa preguntando "¿qué control debería estar aquí y no está?" en lugar de buscar líneas malas.
La refactorización: parametriza la consulta (elimina la inyección), codifica la salida (elimina el XSS), añade una comprobación de autorización a nivel de objeto (elimina el IDOR) y lleva el secreto incrustado a la configuración:
// DESPUÉS
app.get("/api/orders/:id",
authMiddleware,
validate(orderParamsSchema), // validación de entradas
async (req, res) => {
const { rows: [order] } = await db.query(
"SELECT * FROM orders WHERE id = $1", [req.params.id] // parametrizada
);
if (!order || order.userId !== req.user.id) { // autorización a nivel de objeto
return res.sendStatus(404);
}
res.json({ customerName: order.customerName }); // salida estructurada, codificada automáticamente
}
);
// secreto: process.env.STRIPE_API_KEY, cargado una vez al arrancar, nunca en línea
Allí donde el olor se haya propagado por clonación, corrígelo una vez y aplica Extraer método al control (requireOrderOwnership, escapeHtml) para que cada punto de llamada comparta una única implementación auditada en lugar de N copias.
##Detectado por
- Gitleaks generic-api-key — Secretos incrustados en el código / generic-api-key
- Semgrep p/owasp-top-ten — p/owasp-top-ten (por ejemplo, sql-injection, xss, receptores de cadenas de formato peligrosos)
- GitHub CodeQL js/sql-injection, js/xss, js/hardcoded-credentials, js/path-injection
- eslint-plugin-security detect-non-literal-fs-filename, detect-child-process, detect-eval-with-expression, detect-object-injection
- Bandit (Python) B105 / B608 / B324 — B105 hardcoded_password_string, B608 hardcoded_sql_expressions, B324 débil hashlib
- SonarQube / SonarSource Security Hotspots / Vulnerability — Reglas de revisión de seguridad y vulnerabilidades (inyección, criptografía débil, credenciales incrustadas en el código)
- npm audit / OWASP Dependency-Check npm audit / OWASP Dependency-Check — Detección de dependencias vulnerables conocidas