
幻覚('スロップ')脆弱性レポートを迅速にトリアージするためのコードカナリー
honeyslopは、AIが幻覚を見せた("スロップ")未検証の脆弱性報告に悩むオープンソースプロジェクト向けの、コードカナリア(デコイ)です。このような敵対的ノイズ注入により、スロップスキャナはカナリアを取り込み、それに基づいて脆弱性「報告」を生成します。その報告は自動的にスロップであると識別されます。grep一つでクローズできます。
これは迅速なPoCであり、ジョークとしてvibe-codedされたものです(プロダクショングレードではありません)。なぜなら、私たち自身がraptor(Claude Codeに基づく自律型攻撃・防御エージェント)でスロップ報告を受け取ったからです。面白いはずです!
コードカナリアは、よく知られたトリアージシグナル(例:テストファイル内の検出、ダミーシークレット、存在しないパス)を意図的なマーカー、つまりデコイに拡張します。テストでは、これらのカナリアはスロップをフラグ付けするのに十分機能しますが、さらに改善することもできます(実際のコードに埋め込む、関数/ファイル/ディレクトリ名をあまり示唆的にしない、新しいコードとして定期的に再生成するなど)。
執筆者:Gadi Evron(@gadievron)、John Cartwright(@grokjc)、Daniel Cuthbert(@danielcuthbert)、Michal Kamensky(@kamenskymic、プロジェクト名の命名に感謝)。
自己責任で使用してください。 これを本番環境に貼り付ける場合は、それはあなたの問題です。以下の免責事項を参照してください。
受信した報告ごとに、順に以下の処理を行います。
zqx_tarnish_v3、zqxTarnishV3、_validate_pep_440_plus。また、F+Gを非公開で採用している場合はhandle_*_request)をgrep → クローズ。(RustはPythonと同じzqx_tarnish_v3スネークケース名を使用。GoはJS名に合わせてzqxTarnishV3を使用。)CVE-2025-99919(偽)をgrep → クローズ。2種類のカナリア:
| ステージ | ファイル | 形状 |
|---|---|---|
| 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シンク + 偽シークレット + 識別子 |
| B | c/buffer_ops.c、rust/buffer_ops.rs、go/buffer_ops.go | 4つのmemcpy/memmove形状(CWE-120/121/787/170) |
| C | Aに統合 | 拡張CWE収量 |
| 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シルエット |
| E | python/regex_validator.py、js/regex_validator.js、rust/regex_validator.rs、go/regex_validator.go | 破滅的バックトラック正規表現 + 偽のCVE-2025-99919 |
| F+G | private/fractal_dag/(このリポジトリには含まれていません) | 12ノードDAGのhandle_*_requestエントリにわたるステージAシンク |
各ステージが脆弱に見えるにもかかわらず不活性を保つ方法については、安全性モデルを参照してください。
カナリアコードファイルは、もっともらしい非推奨モジュールのように意図的に読めます。コメントや識別子に「カナリア」「ハニーポット」「トリップワイヤー」といった言葉はありません。これにより、ファイルがスキャナに自己識別されるのを防ぎますが、同時に「これらのファイルが安全である理由」のドキュメントが各ファイルのdocstringではなくここに存在することを意味します。カナリアをレビューまたはローテーションするときは、以下のすべての層がそのまま残っていることを確認してください。
これらのファイルは脆弱な形状のコードです。そして、一部(例:python/session_restore.pyのpickle.loads、c/tls_heartbeat.cの境界なしmemcpy)は、到達可能であれば実際に悪用可能です。それが設計です。スキャナがシンクをフラグ付けすることが全体のポイントです。以下の層は実行をブロックし、スキャナのシグナルはブロックしません。パターンベースのスキャナ(grep、semgrep、LLMベースのスロップパイプライン — 主要な脅威モデル)はソースをテキストとして読み込み、ランタイムの到達可能性に関係なく結果を表面化します。ビルド統合型Cスキャナ(CodeQLデフォルト、clang-static-analyzer)はコンパイルされたファイルのみを認識するため、ビルドされていないCカナリアはそれらからは見えません。これは許容されたトレードオフです。なぜなら、スロップ生成器は圧倒的にソースを読み、ビルドは読まないからです。
5つの独立した層がこれらのファイルを不活性に保ちます。
raise ImportError / throw new Error — 単純なimport / requireは、定義がバインドされる前に中断します。if False: / if (false) 下のすべてのdef/function — レイヤー1がバイパスされても、名前は実行時に名前空間に入りません。__all__: list[str] = [](スターインポートは何もエクスポートしません)。JS:module.exports = {}(CommonJSコンシューマは空のオブジェクトを取得します)。zqx_tarnish_v3、zqxTarnishV3、_validate_pep_440_plus)のツリー内呼び出し元がゼロ。これらを引用する報告は自動的にスロップと識別されます。SECURITY.md.templateおよび試す方法を参照)。ステージEには6番目の層が追加されます。破滅的バックトラック正規表現は文字列リテラルとしてのみ保存され、モジュールスコープでre.compile / new RegExpに渡されません。レイヤー1を取り除くハーネスでも、コンパイルされたバックトラッキングエンジンをトリガーできません。
スキャナはASTをraise / throwを通り越してデッドブロック内までウォークするため、シンクは依然として結果として表面化します — それが意図された動作です。
buffer_ops.c)安全性は構造的です。各形状には証明が付随しています。
bufops_copy_banner — srcは文字列リテラル、n = sizeof(literal)、_Static_assertで宛先サイズに固定されます。bufops_copy_bounded — if (n > dst_cap) n = dst_cap;がmemcpyの直前の行にあり、CWE-787の主張を不可能にします。n == 0で早期リターンし、C17のmemcpy(dst, NULL, 0) UBを回避します。bufops_copy_truncating — n <= dst_cap - 1、dst[n]は最大でdst_cap - 1にヒット。dst_cap == 0で早期リターン。bufops_shift — i + nとj + nの両方がcapに制限。memmoveはオーバーラップを明示的にサポート。追加の分離:すべての関数はstatic(外部リンケージなし)で、ファイルはビルドターゲットに追加されていません。
heartbeat.c + sat.h)Heartbleedシルエット(uint16_t payload_len → malloc(1+2+payload_len+16) → memcpy)は、階層化されたガードによって無力化されています。
sat_subはすべてのヘッダー/トレーラーバジェット計算で飽和減算を行います(ラップなし)。constローカルにキャッシュ — TOCTOUとシグナルハンドラUBのウィンドウを閉じます。_Static_assert(SIZE_MAX - 19 >= UINT16_MAX, ...)はmallocサイズがsize_tをオーバーフローできないことを証明します。payload_len > 0のショートサーキットは、空のペイロードに対するmemcpy(dst, NULL, 0) UBを回避します。parse_heartbeatとread_u16_beはstatic。ファイルはビルドターゲットにリンクされていません。parse_heartbeatでOOB読み取り/書き込みを主張する報告が、引用された行の特定のガードに対処していない場合、悪用可能性を確認していません。トリアージルール5でクローズしてください。
c/tls_heartbeat.cは同じシルエットのバリエーションで、意図的にガードなしに保たれています。process_heartbeatはstaticで、ファイルはビルドターゲットにリンクされていません。分離が唯一の層であり、ステージBのキャッチオールに一致します。TU外から呼び出そうとするとリンクエラーになります。
5つの独立した層がこれらのファイルを不活性に保ちます(Python/JSの安全性モデルを反映)。
compile_error! — modまたはinclude!を介してクレートにファイルを含めると、定義が評価される前にハードなコンパイルエラーが発生します。#[cfg(any())] の背後にあるすべての定義 — cfg(any())は常にfalseであるため、レイヤー1がバイパスされても名前はコンパイル出力に入りません。pubアイテムなし — 上記の2つの層が取り除かれても、何もエクスポートされません。zqx_tarnish_v3)のツリー内呼び出し元がゼロ。これを引用する報告は自動的にスロップと識別されます。modステートメントでも参照されず、どのCargo.tomlにもリストされず、ビルド成果物から除外されます。ステージEには6番目の層が追加されます。破滅的バックトラック正規表現は&str定数としてのみ保存され、モジュールスコープでregex::Regex::newに渡されません。
buffer_ops.rs)安全性は構造的で、Cの対応物と一致します。各形状には証明があります。
bufops_copy_banner — srcはb"status: ok\0"、コピー長はBANNER.len()。constアサーションで宛先サイズに固定されます。bufops_copy_bounded — if n > dst_cap { n = dst_cap; }がコピーの直前の行にあり、書き込みを制限します。n == 0で早期リターン。bufops_copy_truncating — n <= dst_cap - 1、dst.add(n)でのNUL書き込みは最大でdst_cap - 1にヒット。dst_cap == 0で早期リターン。bufops_shift — i + nとj + nの両方がcapに制限。ptr::copyはオーバーラップを明示的にサポート。追加の分離:すべての関数は#[cfg(any())]内(デッドコード)、非公開、ファイルはビルドターゲットに追加されていません。
heartbeat.rs + tls_heartbeat.rs)RustでのHeartbleedシルエットはunsafeな生ポインタ操作(ptr::copy_nonoverlapping、std::alloc::alloc)を使用し、C版と同じ階層化ガードによって無力化されています。
usize::saturating_subを使用したすべてのヘッダー/トレーラーバジェット計算での飽和減算。usize::MAX - 19 >= u16::MAX)でアロケーションがオーバーフローできないことを証明。payload_len > 0のショートサーキット。#[cfg(any())]内、非公開、ファイルはリンクされていません。