pingedbrain
EN

7 min de lectura Read in English

Dejé Correr una IA por 3 Días. Encontró 102 Cosas que Podían Destruir Nuestro Sistema.

Codebot Workflow corrió una auditoría de seguridad completa en nuestro codebase de 14k líneas. Esto es lo que encontró, por qué importa, y por qué cada equipo debería hacer esto todos los días.

La semana pasada decidí hacer un stress test de mi propio sistema.

Codebot Workflow tiene dos modos. El modo normal maneja mejoras — actualizaciones de dependencias, ajustes de performance, limpiezas. Pero hay un segundo modo que construí y rara vez uso: security scan. Auditoría completa. Cada clase de vulnerabilidad, cada superficie de ataque.

Lo apunté a korz.sh y le dije que corra hasta que termine.

Tres días después: 102 problemas.

Pero esa fue solo la primera pasada. Cuando agregué judgment-day — un modo de revisión adversaria paralela que corre dos jueces de IA independientes sobre el mismo código, simultáneamente, y solo acepta findings que ambos jueces confirman — la lista se acortó y se volvió más seria.

Ahí es cuando las cosas se pusieron interesantes.

Qué es Codebot Workflow

Para los que no leyeron el post anterior: Codebot Workflow es un sistema multi-agente que construí y corre mientras duermo. Analiza código, identifica problemas, y propone soluciones. Los agentes están alimentados por Claude a través de un adapter que escribí.

Piénsalo como un equipo de seguridad que nunca se cansa, nunca pierde un edge case, y no asume que el código que escribiste es el código que quisiste escribir.

La adición de judgment-day

Los scans estándar son exhaustivos. Pero también son singulares — un agente, una perspectiva, una forma de mirar el código. Si el agente tiene un punto ciego, perdés lo que sea que esté ahí dentro.

judgment-day resuelve esto. Lanza dos jueces independientes — Judge A y Judge B — que revisan el mismo código simultáneamente, sin comunicación entre ellos. Un finding solo aparece si ambos jueces lo confirman independientemente. Es adversario por diseño: cada juez desafía los assumptions del otro, debate los findings, y no firma a menos que estén genuinamente seguros.

Pensá en esto como tener dos senior security engineers que no saben lo que el otro encontró, corriendo la auditoría al mismo tiempo. Solo escuchás las cosas en las que ambos están de acuerdo que son reales.

El taux de falsos positivos baja dramáticamente. El nivel de confianza sube. Y los findings que sobreviven este proceso son los que vale la pena discutir.

Los números

Esperaba como 20.

102 significa que aproximadamente 1 en 137 líneas de código tenía una vulnerabilidad crítica. Eso no es ruido. Es un patrón.

Esto es lo que 102 vulnerabilidades significaban en nuestro codebase:

Fallos de autenticación y autorización — 34 hallazgos. Estos son los peores. No porque el código sea malicioso, sino porque el sistema se confianza demasiado a sí mismo. Tokens que no verifican su propia firma. Endpoints que checkean si un usuario está logueado pero no si tiene permiso para hacer lo que pide. Brechas entre servicios donde una llamada interna comprometida parece idéntica a una autorizada.

Fallos criptográficos — 21 hallazgos. Firmas rotas, algoritmos equivocados, claves usadas en el contexto incorrecto. El tipo de cosa que funciona en testing y falla en producción, pero solo para un atacante.

Fallos de validación de input — 28 hallazgos. Datos que entran al sistema sin ser checkeados. El sistema asume que si los datos vinieron de una fuente confiable, deben ser seguros. No lo son.

Vectores de inyección — 12 hallazgos. Lugares donde input no confiable podría fluir hacia una query, un comando, un shell. Algunos teóricos, otros no tanto.

Filtraciones de información — 7 hallazgos. Mensajes de error que filtran stack traces. Respuestas que revelan más de lo que deberían. Logs que contienen datos sensibles que no deberían tener.

Después de que judgment-day corrió su revisión adversaria paralela, la lista confirmada era más corta. Pero la severidad de lo que sobrevivió subió.

Lo que las vulnerabilidades más serias tenían en común

Lo interesante no son los hallazgos individuales. Son los patrones.

Confiar en input controlado por el cliente. El sistema decidía quién eras basándose en valores que el cliente enviaba. El tenant ID venía de un header. El rol del usuario venía de un token que el sistema no verificaba correctamente. Una vez que entendías cuáles valores el sistema confiaba pero no debería, podías reescribir la realidad.

Operaciones de seguridad que fallan silenciosamente. Cuando algo iba mal en un check de seguridad — una revocación de token que fallaba, una comparación de hash que erroraba — el sistema seguía. Sin error, sin alerta, sin log. Solo aceptación silenciosa.

Autenticación sin autorización. El sistema checkeaba si tenías credenciales, pero no si tus credenciales permitían lo que estabas tratando de hacer. Cualquier usuario autenticado podía administrar cualquier API key. El mismo token que te dejaba leer tus propios datos te dejaba leer los de todos.

Multi-tenancy roto. La arquitectura soportaba múltiples clientes compartiendo la misma infraestructura. Pero los límites entre tenants se imponían por parámetros en requests, no por garantías duras. Cambiá el tenant ID, veías los datos de otro.

Estos no son vulnerabilidades exóticas. Son los mismos patrones que aparecen en breach reports año tras año.

Dos ejemplos que cualquiera puede entender

Hacémoslo concreto.

El Password Que Nunca Se Checkeó. Un endpoint aceptaba un token y lo confiaba completamente. Checkeaba si el token parecía válido, pero nunca verificaba si realmente lo emitimos nosotros. Cualquiera podía fabricar un token con cualquier identidad que quisiera. Creá una credencial que diga “Soy el superusuario” y el sistema diría “bienvenido, superusuario.”

El Tenant ID en la URL. El sistema decidía qué datos del cliente mostrar basándose en un valor que el navegador envía. Cambiá ?tenant=acme a ?tenant=competidor y veías los datos del competidor. No a través de un bug — a través del diseño. El sistema confiaba en un valor que un atacante controla.

Ambos fueron fija. Pero estuvieron en producción por meses.

Qué cambió judgment-day

Después del scan inicial, pasé los hallazgos por judgment-day. Dos jueces, revisión independiente, solo lo que ambos confirmaron.

El resultado: una lista más corta. Mayor confianza. Los split judgments — donde los jueces desacordaban — eran la parte más interesante. Cuando Judge A llama algo critical que Judge B llama teórico, significa que la severidad depende del contexto que tenés que entender, no solo del código que podés escanear.

Así es como la security review debería funcionar en la práctica. No un find-everything scan que te bury en ruido. Un output confidence-ranked donde lo de arriba es undeniable y lo de abajo es “maybe, investigá si tenés tiempo.”

Por qué los audits manuales lo pierden

He hecho security reviews. La mayoría de los ingenieros las han hecho. Leés el código, buscás problemas obvios, checkeás el OWASP top 10, te movés. Eso no es suficiente.

Context fatigue. Cuando escribís código, sabés lo que quisiste decir. Leés intent en el comportamiento. El código dice “decodear este token” y tu cerebro completa “seguramente.” No ves el check faltante porque asumís que está.

Coverage gaps. Un audit manual mira los critical paths. No hace crawl de cada fallback, cada error path, cada branch de “esto nunca debería pasar.”

Temporal blindness. El código cambia. La validación que era correcta en 2024 fue silenciosamente removida en 2025 durante un refactor. Nadie lo atrapó porque el security audit ya estaba hecho.

Los agentes no tienen context fatigue. Leen el código, no el intent. No asumen — verifican.

El setup adversario lo hace aún más difícil de perder. Cuando dos agentes están independently checking el trabajo del otro, los coverage gaps se achican más. Cada juez mira diferentes ángulos. Lo que uno se pierde, el otro encuentra.

Qué pasó después

Pasé dos días fixando los críticos. Codebot tenía los fixes listos — genera un patch junto con cada finding. Pero revisé cada uno manualmente. Algunos necesitaban ajuste para nuestra arquitectura específica.

El build pasó. Los tests pasaron. Estamos más seguros que antes.

Pero la pregunta que me mantiene despierto no es “cómo perdimos estos?” Es “cuántos otros equipos están sentados sobre los mismos patterns?”

Por qué esto importa para cada equipo

Toda empresa con código significativo tiene estos issues. Definitely.

La pregunta es si los estás encontrando antes que alguien más.

Un scan automático diario habría atrapado el JWT bypass en el commit que lo introdujo. En cambio, estuvo sentado en producción por meses.

Ahora corro el vulnerability scan todas las mañanas. La mayoría de los días, el reporte está vacío — el sistema está limpio.

Pero la semana pasada, encontró 102 cosas. Y después de que judgment-day terminó de argue con sí mismo, una lista más pequeña y más seria sobrevivió.

Prefiero tener el reporte.


Lecturas recomendadas

Si estás empezando con estos conceptos, acá hay una lista curada:


korz.sh — pingedbrain/korz Construyendo infraestructura que mejora mientras descansás