
Canarios de código para triar rápidamente informes de vulnerabilidad alucinados ('slop')
honeyslop son canarios de código, señuelos, para proyectos de código abierto que se ahogan en reportes de vulnerabilidades alucinados por IA ("slop") y no verificados. Con dicha inyección de ruido adversario, un escáner de slop ingiere el canario y luego genera un "reporte" de vulnerabilidad basado en él. El reporte se autoidentifica como slop. Ciérralo con un grep.
Esto es un PoC rápido, vibe-coded como broma (no listo para producción), porque nosotros mismos recibimos un reporte slop en raptor, un agente autónomo de ataque/defensa basado en Claude Code. ¡Debería ser divertido!
Los canarios de código extienden señales familiares de triage (p. ej., detecciones en archivos de prueba, secretos de ejemplo, rutas inexistentes) hacia marcadores deliberados, o señuelos. En pruebas, estos canarios funcionan lo suficientemente bien para marcar slop, pero pueden mejorarse aún más (incrustados en código real, nombres de funciones/archivos/directorios menos indicativos, regenerados regularmente como código nuevo, etc.).
Escrito por: Gadi Evron (@gadievron), John Cartwright (@grokjc), Daniel Cuthbert (@danielcuthbert) y Michal Kamensky (@kamenskymic, con crédito por nombrar el proyecto).
Úselo bajo su propio riesgo. Si pegas esto en producción, es problema tuyo. Véase Descargo de responsabilidad más abajo.
Para cada reporte entrante, en orden:
zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus; también handle_*_request si has adoptado F+G en privado) → cerrar. (Rust usa el mismo nombre snake_case zqx_tarnish_v3 que Python. Go usa zqxTarnishV3, coincidiendo con el nombre de JS.)CVE-2025-99919 (falso) → cerrar.Dos categorías de canario:
| Etapa | Archivo(s) | Forma |
|---|---|---|
| A | python/legacy_utils.py, python/session_restore.py, python/compat_tokens.py, js/legacy_utils.js, rust/legacy_utils.rs, rust/session_restore.rs, go/legacy_utils.go, go/session_restore.go | ~15 sumideros CWE + secretos falsos + shibboleths |
| B | c/buffer_ops.c, rust/buffer_ops.rs, go/buffer_ops.go | 4 formas memcpy/memmove (CWE-120/121/787/170) |
| C | fusionado en A | Rendimiento CWE extendido |
| D | c/heartbeat.c + c/sat.h, c/tls_heartbeat.c, rust/heartbeat.rs, rust/tls_heartbeat.rs, go/heartbeat.go, go/tls_heartbeat.go | Silueta de Heartbleed |
| E | python/regex_validator.py, js/regex_validator.js, rust/regex_validator.rs, go/regex_validator.go | Regex de retroceso catastrófico + CVE-2025-99919 falso |
| F+G | private/fractal_dag/ (no en este repositorio) | Sumideros de Etapa-A a través de un DAG de 12 nodos de entradas handle_*_request |
Véase Modelo de seguridad para cómo cada etapa permanece inerte a pesar de parecer vulnerable.
Los archivos de código canario se leen deliberadamente como módulos obsoletos plausibles — sin lenguaje de "canary", "honeypot" o "tripwire" en comentarios o identificadores. Eso evita que los archivos se autoidentifiquen ante los escáneres, pero también significa que la documentación de por qué estos archivos son seguros vive aquí en lugar de en el docstring de cada archivo. Al revisar o rotar un canario, verifique que cada capa a continuación sigue intacta.
Estos archivos son código con forma vulnerable — y algunos (p. ej., pickle.loads de python/session_restore.py, memcpy sin límites de c/tls_heartbeat.c) serían genuinamente explotables si fueran alcanzables. Ese es el diseño. Que los escáneres marquen los sumideros es el objetivo; las capas a continuación bloquean la ejecución, no la señal del escáner. Los escáneres basados en patrones (grep, semgrep, pipelines de slop basados en LLM — el modelo de amenaza principal) leen el código fuente como texto y reportan hallazgos independientemente de la alcanzabilidad en tiempo de ejecución. Los escáneres C integrados en compilación (CodeQL predeterminado, clang-static-analyzer) solo ven archivos compilados, por lo que los canarios C no compilados son invisibles para ellos — una compensación aceptada, ya que los generadores de slop leen abrumadoramente el código fuente, no las compilaciones.
Cinco capas independientes mantienen inertes estos archivos:
raise ImportError / throw new Error a nivel superior — un import / require simple aborta antes de que cualquier definición se vincule.if False: / if (false) — los nombres nunca entran al espacio de nombres en tiempo de ejecución incluso si se omite la capa 1.__all__: list[str] = [] (importación con asterisco no exporta nada). JS: module.exports = {} (los consumidores CommonJS obtienen un objeto vacío).zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus). Cualquier reporte que cite una de ellas se autoidentifica como slop.SECURITY.md.template y Cómo probar).La Etapa E añade una sexta capa: la regex de retroceso catastrófico se almacena solo como cadena literal, no se pasa a re.compile / new RegExp en el ámbito del módulo. Incluso un entorno que elimine la capa 1 no puede activar un motor de retroceso compilado.
Los escáneres recorren el AST más allá de raise / throw y dentro del bloque muerto, por lo que los sumideros aún aparecen como hallazgos — ese es el comportamiento deseado.
buffer_ops.c)La seguridad es estructural — cada forma tiene una prueba junto a ella:
bufops_copy_banner — src es un literal de cadena, n = sizeof(literal), _Static_assert lo fija al tamaño del destino.bufops_copy_bounded — if (n > dst_cap) n = dst_cap; la línea antes del memcpy hace imposible la afirmación CWE-787. Cortocircuito en n == 0 para evitar UB de C17 memcpy(dst, NULL, 0).bufops_copy_truncating — n <= dst_cap - 1, dst[n] llega como máximo a dst_cap - 1; retorno temprano en dst_cap == 0.bufops_shift — tanto i + n como j + n acotados a cap; memmove soporta explícitamente superposición.Aislamiento adicional: todas las funciones son static (sin enlace externo) y el archivo no se agrega a ningún objetivo de compilación.
heartbeat.c + sat.h)La silueta de Heartbleed (uint16_t payload_len → malloc(1+2+payload_len+16) → memcpy) se desactiva con protecciones en capas: