LLMs que generan security reports: corrí el mismo prompt sobre mi propio código
Cometí un error de arquitectura que tardé tres semanas en ver — y lo vi porque un LLM me lo señaló primero. No lo cuento para hacerme el humilde. Lo cuento porque ese mismo LLM ignoró una vulnerabilidad real que yo tenía expuesta en un endpoint de Railway desde hacía dos meses.
Ese contraste — ver algo menor, ignorar algo mayor — es exactamente el problema que quiero explosar hoy.
Hace unos días, Hacker News reportó algo que me frenó en seco: committers del kernel de Linux estaban recibiendo security reports generados por LLMs, con 115 puntos de upvotes y debate encendido. El tema del hilo era si esos reports eran ruido o señal. La mayoría se concentró en los falsos positivos. Nadie habló de los falsos negativos.
Ahí está la tesis que me importa.
LLM security reports en código real: el experimento que armé
Tomé el patrón exacto que describe el hilo de HN — un LLM actuando como security reviewer sobre un diff o un archivo de código — y lo apliqué sobre tres partes de mi propia infra en producción: un handler de webhooks en Next.js, un módulo de autenticación que escribí durante la pandemia cuando todavía estaba aprendiendo a programar en serio, y un wrapper de Railway que maneja variables de entorno.
El prompt que usé fue deliberadamente simple. No quería darle contexto extra ni ayudarlo. Quería ver qué veía solo:
# Prompt base para security review
PROMPT = """
Sos un security engineer revisando este código.
Listá vulnerabilidades reales, ordenadas por severidad.
No des contexto general. No expliques qué es SQL injection.
Solo listá lo que VES en este código específico.
"""
Lo corrí contra Claude Opus 4 y GPT-4o. Los resultados no fueron iguales, lo cual ya es información.
Lo que encontraron (real)
Claude me marcó tres cosas en el handler de webhooks:
- Ausencia de verificación de firma en el payload de entrada — REAL. Yo lo sabía pero lo había dejado "para después". Llevan dos meses de después.
- Un
console.log(req.body)que en alguna request podía loguear datos sensibles — REAL, y no lo había notado. - Un rate limiting implementado en memoria (sin Redis) que no sobrevive un restart del container — REAL.
GPT-4o encontró los mismos tres puntos, más uno que era ruido:
- Me alertó que estaba usando
Math.random()para generar IDs de sesión — FALSO POSITIVO. No estaba usando eso para sesiones, era para correlation IDs de logs internos. Irrelevante desde el punto de vista de seguridad.
Hasta ahí, el experimento parecía validar el proceso. Tres reales, un falso positivo. Razonable.
Lo que NO encontraron (y ahí está el problema)
El módulo de autenticación viejo — el que escribí en 2021 cuando pasé de infraestructura a desarrollo — tenía algo más profundo. Tenía una lógica de comparación de tokens que era vulnerable a timing attacks en ciertos paths de código:
// Esto parece inofensivo. No lo es.
// La comparación directa de strings es vulnerable a timing attacks
// porque JavaScript puede hacer short-circuit en el primer byte diferente
function validarToken(tokenRecibido: string, tokenEsperado: string): boolean {
// ❌ Vulnerable: comparación directa
return tokenRecibido === tokenEsperado;
// ✅ Correcto: comparación en tiempo constante
// return crypto.timingSafeEqual(
// Buffer.from(tokenRecibido),
// Buffer.from(tokenEsperado)
// );
}
Ninguno de los dos modelos lo marcó. Ninguno.
¿Por qué? Porque el código "parecía correcto". La función devuelve un boolean, compara dos strings, está nombrada claramente. Un revisor rápido — humano o LLM — lo pasa sin verlo.
El problema real: los falsos negativos te dan una excusa
Cuando el kernel de Linux empieza a recibir security reports de LLMs, el debate natural es "¿cuántos son falsos positivos?". Es una pregunta razonable. Pero es la pregunta equivocada.
La pregunta que importa es: ¿cuántas vulnerabilidades reales NO están apareciendo en esos reports?
Porque un falso positivo lo descartás. Te da bronca, perdés tiempo, pero no te hace daño. Un falso negativo — una vulnerabilidad que el LLM no vio — te da algo peor: la sensación de que ya revisaste. Que el código está limpio. Que podés deployar tranquilo.
Eso es exactamente lo que me pasó con el Vercel breach. No el breach en sí, sino la lógica mental que lo rodea: el incidente me rompió la infra, sí, pero sobre todo me rompió la excusa. La excusa de que "alguien ya revisó esto".
Cuando corrí el LLM sobre mi código y me devolvió tres hallazgos reales, mi primer instinto fue pensar: "bien, ya sé mis problemas". Pero el timing attack seguía ahí. Invisible. Con el sello implícito de "revisado por IA".
Mi tesis, dicha en limpio: el peligro de los LLM security reports no es que generen ruido. Es que generan confianza.
Qué clases de vulnerabilidades los LLMs ven mal
Después del experimento, fui metódico. Probé más código. Probé con distintos prompts. Probé dandole contexto, sin contexto, con chain-of-thought explícito. Acá el patrón que emergió:
Ven bien:
- Secretos hardcodeados en el código (API keys, passwords en texto plano)
- Ausencia de validación en inputs obvios
- SQL queries concatenadas con string interpolation
- Dependencias con CVEs conocidos (si los tienen en el training)
- Logs que exponen datos sensibles
Ven mal:
- Vulnerabilidades que dependen del contexto de ejecución (race conditions, timing attacks)
- Problemas de autorización que requieren entender el modelo de negocio
- Lógica de control de acceso implícita (lo que el código NO hace, no lo que hace)
- Vulnerabilidades en la interacción entre dos módulos que el LLM no ve juntos
Esa última categoría me parece la más peligrosa. Cuando usé el mismo patrón que apliqué en CrabTrap — un LLM como juez intermedio delante de mi agente — aprendí que los LLMs son buenos evaluando lo que tienen adelante. Son malos razonando sobre lo que falta o sobre comportamiento emergente de sistemas.
Un security review no es distinto.
Errores comunes cuando usás LLMs para revisar seguridad
1. Darle el archivo en lugar del sistema
El timing attack que los modelos no vieron estaba en un archivo revisado de forma aislada. Si le hubiera pasado el flujo completo — desde el endpoint hasta la validación — quizás lo hubiera detectado. Quizás.
2. Interpretar silencio como aprobación
"No encontró nada" no significa "no hay nada". Significa "no encontró nada en lo que procesó". La distinción importa.
3. No especificar el modelo de amenaza
Un LLM sin contexto asume un modelo de amenaza genérico. No sabe si el enemigo es un script kiddie o un equipo con tiempo y recursos. El prompt que armé era deliberadamente neutro — eso fue un error mío también.
4. Confiar en un solo modelo
GPT-4o y Claude encontraron cosas distintas. Eso ya te dice que ninguno tiene cobertura completa. Usarlos como consultas independientes y comparar outputs es más honesto que confiar en uno solo.
5. No iterar el prompt según el tipo de código
Un handler de webhooks necesita un prompt distinto a un módulo de autenticación. El contexto doméstico cambia qué vulnerabilidades son relevantes.
FAQ: LLM security reports y análisis de código
¿Pueden los LLMs reemplazar un pentest real?
No. Ni de cerca. Un LLM puede hacer una primera pasada sobre código estático y encontrar problemas evidentes. Un pentest implica contexto de ejecución, interacción real con el sistema, escalación de privilegios, análisis de comportamiento en runtime. Son herramientas distintas para momentos distintos. El LLM es útil antes del pentest, no en lugar de.
¿Qué tan confiables son los security reports generados por IA para código de producción?
Depende de qué esperás de ellos. Para encontrar secretos hardcodeados, inputs sin validar o patrones de inyección obvios: bastante confiables. Para encontrar vulnerabilidades lógicas, problemas de autorización implícita o bugs de timing: no los uses como única fuente. Mi experimento dio tres verdaderos positivos y un falso positivo — pero la vulnerabilidad más seria quedó fuera del report.
¿Tiene sentido enviar security reports generados por LLMs a proyectos open source como el kernel?
Es una pregunta que divide al ecosistema, y con razón. Si el report es verificado por un humano antes de enviarse y describe una vulnerabilidad real: sí, aporta valor. Si es un output crudo de LLM sin curaduría humana enviado a maintainers que ya tienen colas de trabajo: es ruido que tiene costo humano real. El problema no es que el LLM lo genere. El problema es cuándo ese paso de verificación humana desaparece de la cadena.
¿Qué prompt da mejores resultados para LLM security reviews?
En mis pruebas, los prompts más útiles tienen tres componentes: especificación del modelo de amenaza ("asumí que el atacante tiene acceso a los logs pero no al código fuente"), restricción de scope ("no me expliques conceptos generales, solo lo que ves en este código"), y pedido de evidencia ("para cada hallazgo, citá la línea exacta y explicá el vector de ataque concreto"). Sin eso, los outputs son genéricos y poco accionables.
¿Los LLMs ven mejor las vulnerabilidades que los linters de seguridad estáticos como Semgrep o Bandit?
Complementario, no superior. Semgrep y Bandit son deterministas: si definís una regla, la aplica siempre, sin alucinaciones. Los LLMs tienen más capacidad de razonamiento contextual pero son no-deterministas y pueden inventar problemas o ignorar patrones que no ven en el training. Mi stack actual los usa en paralelo: Semgrep en CI para cobertura automática, LLM para revisión contextual en PRs críticos.
¿Vale la pena automatizar LLM security reviews en el pipeline de CI/CD?
Con cuidado. El costo en tokens para revisar cada commit puede escalar rápido — tengo logs de lo que cuesta cada decisión de diseño en mi agente y la sorpresa fue mayúscula. Para CI automático, lo mejor es un trigger selectivo: archivos que tocan autenticación, manejo de secretos o validación de inputs. No todo el diff en cada push.
Lo que acepté, lo que no compro y el trade-off honesto
Acepté que los LLMs son útiles como primera capa de revisión. Son mejores que no revisar nada. Encontraron tres problemas reales en mi código que yo había dejado para después — y "después" llevaba dos meses.
Lo que no compro es la narrativa de que LLM-as-security-reviewer es suficiente. O peor: que es equivalente a revisión humana experta. Esa narrativa existe porque es conveniente — para los vendors, para los equipos con deadlines, para cualquiera que quiera tener la sensación de que el proceso de seguridad existe sin el costo de ejecutarlo bien.
El timing attack que los modelos ignoraron no era esotérico. Era un patrón conocido, documentado, con contramedida en una línea. Lo ignoraron porque estaba implícito en el comportamiento, no explícito en la sintaxis.
El trade-off honesto es este: un LLM security report te da cobertura sobre lo visible. Lo invisible sigue siendo invisible, y ahora viene con un sello de revisado.
Eso — el sello — es lo que me da bronca. Y lo que me obligó a armar este experimento en lugar de quedarme con la primera pasada tranquilizadora.
Si trabajás en algo que tiene superficie de ataque real — un endpoint público, manejo de tokens, datos de usuarios — no dejes que un security report de LLM sea el punto final del proceso. Usalo como punto de partida. La diferencia importa más de lo que parece.
Si querés ver cómo armé el sistema de evaluación intermedia que usa LLM-as-judge en mi agente de producción, el post de CrabTrap tiene los detalles técnicos completos. Y si el tema de costos de tokens en workflows de revisión automática te preocupa, los números que medí en mis propios logs dan contexto de por qué el trigger selectivo importa.
Este artículo fue publicado originalmente en juanchi.dev
Top comments (0)