
Code-Canaries zur schnellen Triagierung halluzinierter ('Slop') Schwachstellenberichte
honeyslop besteht aus Code-Canaries, also Ködern, für Open-Source-Projekte, die in KI-halluzinierten („Slop“) und unverifizierten Schwachstellenmeldungen ertrinken. Durch diese adversariale Rausch-Injektion liest ein Slop-Scanner die Canary ein und erzeugt darauf basierend einen Schwachstellen-„Report“. Der Report identifiziert sich damit selbst als Slop. Ein einziger grep genügt, um ihn zu schließen.
Dies ist eine schnelle PoC, die als Scherz vibe-coded wurde (nicht produktionsreif), weil wir selbst bei raptor, einem autonomen Angriffs-/Verteidigungsagenten auf Basis von Claude Code, einen Slop-Report erhalten haben. Sollte Spaß machen!
Code-Canaries wandeln vertraute Triage-Signale (z. B. Befunde in Testdateien, Beispiel-Secrets, nicht existierende Pfade) in bewusst platzierte Marker bzw. Köder um. In Tests funktionieren diese Canaries gut genug, um Slop zu markieren, aber sie lassen sich weiter verbessern (eingebettet in echten Code, weniger verräterische Funktions-/Datei-/Verzeichnisnamen, regelmäßig als neuer Code regeneriert usw.).
Geschrieben von: Gadi Evron (@gadievron), John Cartwright (@grokjc), Daniel Cuthbert (@danielcuthbert) und Michal Kamensky (@kamenskymic, mit Props für die Namensgebung des Projekts).
Nutzung auf eigene Gefahr. Wenn du das in die Produktion einfügst, ist das dein Problem. Siehe Haftungsausschluss unten.
Für jeden eingehenden Report, in dieser Reihenfolge:
zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus; außerdem handle_*_request, falls du F+G privat übernommen hast) → schließen. (Rust verwendet denselben zqx_tarnish_v3-snake_case-Namen wie Python. Go verwendet zqxTarnishV3, passend zum JS-Namen.)CVE-2025-99919 (fiktiv) → schließen.Zwei Kategorien von Canaries:
| Stufe | Datei(en) | Form |
|---|---|---|
| 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 CWE-Sinks + Fake-Secrets + Shibboleths |
| B | c/buffer_ops.c, rust/buffer_ops.rs, go/buffer_ops.go | 4 memcpy-/memmove-Formen (CWE-120/121/787/170) |
| C | in A zusammengeführt | Erweiterte CWE-Ausbeute |
| 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 | Heartbleed-Silhouette |
| E | python/regex_validator.py, js/regex_validator.js, rust/regex_validator.rs, go/regex_validator.go | Regex mit katastrophalem Backtracking + fiktives CVE-2025-99919 |
| F+G | private/fractal_dag/ (nicht in diesem Repo) | Stufe-A-Sinks über einen 12-Knoten-DAG aus handle_*_request-Einträgen |
Siehe Sicherheitsmodell für die Erklärung, warum jede Stufe inert bleibt, obwohl sie verwundbar aussieht.
Die Canary-Codedateien lesen sich bewusst wie plausible, veraltete Module — in Kommentaren oder Bezeichnern kommt kein „canary“, „honeypot“ oder „tripwire“ vor. Das verhindert, dass sich die Dateien gegenüber Scannern selbst verraten, bedeutet aber auch, dass die Dokumentation zum Warum diese Dateien sicher sind hier lebt und nicht in den Docstrings der einzelnen Dateien. Beim Überprüfen oder Rotieren einer Canary ist zu prüfen, dass jede der folgenden Ebenen noch intakt ist.
Diese Dateien sind verwundbar geformter Code — und einige (z. B. pickle.loads aus python/session_restore.py, das unbegrenzte memcpy aus c/tls_heartbeat.c) wären bei Erreichbarkeit tatsächlich ausnutzbar. Das ist das Design. Dass Scanner die Sinks melden, ist der eigentliche Zweck; die Ebenen unten blockieren die Ausführung, nicht das Scanner-Signal. Musterbasierte Scanner (grep, semgrep, LLM-basierte Slop-Pipelines — das primäre Bedrohungsmodell) lesen Quelltext als Text und melden Befunde unabhängig von der Laufzeiterreichbarkeit. Build-integrierte C-Scanner (CodeQL-Standard, clang-static-analyzer) sehen nur kompilierte Dateien, daher sind ungebaute C-Canaries für sie unsichtbar — ein akzeptierter Kompromiss, da Slop-Generatoren überwiegend Quelltext lesen und keine Builds.
Fünf unabhängige Ebenen halten diese Dateien inert:
raise ImportError / throw new Error auf oberster Ebene — ein schlichtes import / require bricht ab, bevor irgendeine Definition gebunden wird.if False: / if (false) — Namen gelangen nie in den Laufzeit-Namespace, selbst wenn Ebene 1 umgangen wird.__all__: list[str] = [] (Star-Import exportiert nichts). JS: module.exports = {} (CommonJS-Konsumenten erhalten ein leeres Objekt).zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus). Jeder Report, der eine davon zitiert, identifiziert sich selbst als Slop.SECURITY.md.template und Ausprobieren).Stufe E fügt eine sechste Ebene hinzu: Die Regex mit katastrophalem Backtracking wird nur als String-Literal gespeichert und auf Modulebene nicht an re.compile / new RegExp übergeben. Selbst ein Harness, das Ebene 1 entfernt, kann keine kompilierte Backtracking-Engine auslösen.
Scanner laufen im AST an raise / throw vorbei in den toten Block hinein, sodass die Sinks weiterhin als Befunde auftauchen — das ist das beabsichtigte Verhalten.
buffer_ops.c)Die Sicherheit ist strukturell — zu jeder Form gehört ein Beweis:
bufops_copy_banner — src ist ein String-Literal, n = sizeof(literal), _Static_assert fixiert es auf die Zielgröße.bufops_copy_bounded — if (n > dst_cap) n = dst_cap; direkt vor dem memcpy macht die CWE-787-Behauptung unmöglich. Kurzschluss bei n == 0, um das C17-UB von memcpy(dst, NULL, 0) zu vermeiden.bufops_copy_truncating — n <= dst_cap - 1, dst[n] erreicht höchstens dst_cap - 1; frühzeitiges return bei dst_cap == 0.bufops_shift — sowohl i + n als auch j + n sind auf cap begrenzt; memmove unterstützt Überlappungen ausdrücklich.Zusätzliche Isolation: Alle Funktionen sind static (keine externe Bindung) und die Datei ist keinem Build-Target hinzugefügt.