
Code canaries pour trier rapidement les rapports de vulnérabilités hallucinées ('slop')
honeyslop est un ensemble de canaris de code, des leurres, destinés aux projets open-source submergés par des rapports de vulnérabilité hallucinés par l'IA (« slop ») et non vérifiés. Avec une telle injection de bruit adversarial, un scanner slop ingère le canari, puis génère un « rapport » de vulnérabilité basé sur celui-ci. Le rapport s'identifie lui-même comme du slop. Clôturez-le en un seul grep.
Ceci est un PoC rapide, codé au feeling comme une blague (pas de qualité production), car nous avons nous-mêmes reçu un rapport slop sur raptor, un agent autonome d'attaque/défense basé sur Claude Code. Ce devrait être amusant !
Les canaris de code étendent les signaux de triage familiers (par exemple, les détections dans les fichiers de test, les secrets d'exemple, les chemins inexistants) en marqueurs délibérés, ou leurres. Lors des tests, ces canaris fonctionnent suffisamment bien pour signaler du slop, mais ils peuvent être encore améliorés (intégrés dans du code réel, noms de fonctions/fichiers/répertoires moins indicateurs, régénérés régulièrement comme nouveau code, etc.).
Écrit par : Gadi Evron (@gadievron), John Cartwright (@grokjc), Daniel Cuthbert (@danielcuthbert) et Michal Kamensky (@kamenskymic, avec des remerciements pour avoir nommé le projet).
Utilisation à vos propres risques. Si vous collez ceci en production, c'est votre problème. Voir Avertissement ci-dessous.
Pour chaque rapport entrant, dans l'ordre :
zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus ; également handle_*_request si vous avez adopté F+G en privé) → fermer. (Rust utilise le même nom snake_case zqx_tarnish_v3 que Python. Go utilise zqxTarnishV3, correspondant au nom JS.)CVE-2025-99919 (faux) → fermer.Deux catégories de canaris :
| Étape | Fichier(s) | Forme |
|---|---|---|
| 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 sinks CWE + faux secrets + shibboleths |
| B | c/buffer_ops.c, rust/buffer_ops.rs, go/buffer_ops.go | 4 formes memcpy/memmove (CWE-120/121/787/170) |
| C | fusionné dans A | Couverture CWE étendue |
| 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 | Silhouette Heartbleed |
| E | python/regex_validator.py, js/regex_validator.js, rust/regex_validator.rs, go/regex_validator.go | Regex à rétro-traçage catastrophique + faux CVE-2025-99919 |
| F+G | private/fractal_dag/ (pas dans ce dépôt) | Sinks de l'étape A à travers un DAG de 12 nœuds d'entrées handle_*_request |
Voir Modèle de sécurité pour savoir comment chaque étape reste inerte malgré son apparence vulnérable.
Les fichiers de code canari se lisent délibérément comme des modules plausibles et obsolètes — aucun terme « canari », « honeypot » ou « tripwire » dans les commentaires ou les identifiants. Cela empêche les fichiers de s'identifier eux-mêmes aux scanners, mais cela signifie aussi que la documentation pourquoi ces fichiers sont sûrs se trouve ici plutôt que dans la docstring de chaque fichier. Lors de la révision ou de la rotation d'un canari, vérifiez que chaque couche ci-dessous est toujours intacte.
Ces fichiers sont du code à forme vulnérable — et certains (par exemple pickle.loads dans python/session_restore.py, le memcpy sans limite dans c/tls_heartbeat.c) seraient véritablement exploitables s'ils étaient atteignables. C'est le but recherché. Le fait que les scanners signalent les sinks est l'objectif ; les couches ci-dessous bloquent l'exécution, pas le signal du scanner. Les scanners basés sur des motifs (grep, semgrep, pipelines slop basés sur LLM — le modèle de menace principal) lisent le code source en tant que texte et remontent les résultats indépendamment de l'atteignabilité à l'exécution. Les scanners C intégrés à la compilation (CodeQL par défaut, clang-static-analyzer) ne voient que les fichiers compilés, donc les canaris C non compilés leur sont invisibles — un compromis acceptable, car les générateurs de slop lisent massivement le code source, pas les binaires.
Cinq couches indépendantes maintiennent ces fichiers inertes :
raise ImportError / throw new Error au niveau supérieur — un simple import / require avorte avant que toute définition ne soit liée.if False: / if (false) — les noms n'entrent jamais dans l'espace de noms d'exécution même si la couche 1 est contournée.__all__: list[str] = [] (l'import étoile n'exporte rien). JS : module.exports = {} (les consommateurs CommonJS obtiennent un objet vide).zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus). Tout rapport en citant une s'identifie comme du slop.SECURITY.md.template et Comment essayer).L'étape E ajoute une sixième couche : la regex à rétro-traçage catastrophique est stockée uniquement comme un littéral de chaîne, non passée à re.compile / new RegExp au niveau du module. Même un harnais qui retire la couche 1 ne peut pas déclencher un moteur de rétro-traçage compilé.
Les scanners parcourent l'AST au-delà du raise / throw et dans le bloc mort, donc les sinks apparaissent toujours comme des résultats — c'est le comportement souhaité.
buffer_ops.c)La sécurité est structurelle — chaque forme est accompagnée d'une preuve :