dnr-respond について
dnr-respondは、アラート、IOC、インシデント記録、自由記述を起点に調査するインシデント対応スキルです。ログとソースコードを照合して影響を定量化し、実行せずに対応計画を提案します。
主な機能
- 01アラート、IOC、記録の解析
- 02エンティティ別ログ照合と時系列化
- 03判定と影響範囲の定量化
- 04ソースコードに基づく原因分析
- 05ローカルでのPoC検証
- 06実行せずに対応計画を提案
活用シーン
- 01セキュリティアラートやIOCの調査
- 02不審な活動の成否判定
- 03影響を受けた記録とユーザーの集計
- 04修正と検知の提案作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ anthropics/ defending-code-reference-harness --skill dnr-respondインストール後、Claude Code で/dnr-respondを入力して読み込まれたか確認します。
同梱ファイルREADME.md
詳細
dnr-respond
リードから始めるインシデント対応です。具体的な手掛かりを1つ起点として、インシデントコマンダーが必要とする4つの問いに順番に答えます。**何が起きたのか、成功したのか、どこまで影響が及んだのか、どう対処するのか。**出力は、判定、定量化された影響範囲、ソースコードで立証された根本原因、そして提案のみで、決して実行しない対応計画です。
/dnr-respond <lead> [--logs dir] [--repo path] [--fresh] で呼び出します。
リード($1)には次のものを指定できます。
- ハントの
INCIDENTS.jsonにあるインシデント ID(例:INC-2)—/dnr-huntからの引き継ぎ - アラートキューから貼り付けたアラート(JSON 1行または文章)
- IOC:IP アドレス、アカウント名、ルート、セッション識別子
- 自由記述(「顧客から、フィッシングメールに自分の住所が記載されていたと連絡があった」など)
その他の引数:
--logs= ログディレクトリまたは dnr ターゲットディレクトリ(/dnr-huntと同じ方法で解決します。デフォルトは、targets/dnrcanaryが存在する場合はそこ、そうでなければ現在の作業ディレクトリです)。--repo= アプリケーションのソースコード(デフォルトはターゲットのapp/です)。--fresh= 実行ディレクトリの.dnr-respond-state/にあるチェックポイントを無視し、新しい実行ディレクトリを作成します。
厳守事項(/dnr-hunt と共通 — このトラックの契約)
- ログの証拠だけでは、脆弱性が確認されたことにはなりません。
confirmed_exploitedと判定するには、ログの証拠、ソースコードに存在する欠陥、そしてこのセッション中にローカルで実行した PoC がすべて必要です。それ以外の場合、判定の上限はsuspectedです。 - 読み込むのではなく、クエリを実行します。 ログファイルを
Readまたはcatで読み込んではいけません。プロファイルにはwc/head/tailを、調査にはgrep/awk/sort/uniq/python3を使います。小さなファイル(アラートキュー、約200行未満のエラーログ)は全体を読み込んでもかまいません。 - 提案のみとし、実行してはいけません。 デモアプリを含む、どの環境でも、ブロック、無効化、ローテーション、設定変更を行ってはいけません。計画に対応策を記載し、実行は人間が行います。
- ローカル環境のみを使用します。 PoC は、自分で起動したインスタンスに対して
127.0.0.1で実行します。リモートホストには決してアクセスしません。 - 数値はすべてクエリの結果です。 「レコード数はいくつか」「顧客数はいくつか」への回答には、推定値ではなく、このセッション中に実行したパイプラインの結果を使います。
実行ディレクトリとチェックポイント
最初のアクション — 実行ディレクトリを確定します。/dnr-hunt と同じ方法ですが、コンシューマーとして行います。TARGET は解決済みの --logs/ターゲットパスのベース名(デフォルトは targets/dnrcanary)、SCOPE はそのパスとします。
- リードが
INC-nの場合(/dnr-huntからの引き継ぎ、またはすでに対応を開始したハントからの2つ目のリード):そのインシデントが存在する調査に対応内容を組み込みます。{RUN}はINCIDENTS.jsonを含む、最新のresults/TARGET/<timestamp>/ディレクトリです。Bash で次のように探します。例:ls -1d results/TARGET/*/ | sort -r | while read d; do [ -f "$d/INCIDENTS.json" ] && echo "$d" && break; done。INC-nのリードではrundirを呼び出さないでください。そのディレクトリで以前の/dnr-respondが完了した後にrundirを呼ぶと、新しい空のディレクトリが作成され、インシデントのINCIDENTS.jsonが古いディレクトリに取り残されます。INCIDENTS.jsonを含む実行ディレクトリがない場合(INC-nを渡されたものの、ハントを実行していない場合)に限り、下のrundirの行に進みます。 - それ以外のリード(アラート、IOC、自由記述):Bash で
python3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-respond-state --scope "SCOPE"を実行します($ARGUMENTSに--freshが含まれている場合は追加します)。ターゲットの実行ディレクトリが存在する場合は最新のものを使用し、存在しない場合は新しいresults/TARGET/<timestamp>/を作成します。
このパスを {RUN} とします。以下では、出現箇所すべてを実際のパスに置き換え、Bash では {RUN} と SCOPE を二重引用符で囲みます。
チェックポイントの仕組みは /dnr-hunt と同じで、状態ディレクトリは {RUN}/.dnr-respond-state/、フェーズは 0~4 です。{RUN} に以前のリードの完了済み .dnr-respond-state がすでにある場合、load は complete を返し、以下のルールに従って状態をリセットします。2つ目のリードは同じ INCIDENTS.json に情報を追加し、独自の RESPONSE-<lead>.md を書き込みます。開始時に次を実行します。
python3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-respond-state —
状態がない/完了済み/--fresh の場合は reset してフェーズ 0 から開始します。実行中で phase_done == N の場合は、phase0.json~phaseN.json を読み込み、N+1 から再開します。各フェーズの後に、ペイロードを {RUN}/.dnr-respond-state/_chunk.tmp に書き込み、次を実行します。
checkpoint.py save {RUN}/.dnr-respond-state <N> <name> --from {RUN}/.dnr-respond-state/_chunk.tmp。
progress.json に直接書き込まないでください。ヒアドキュメントや標準入力でペイロードを渡さないでください。出力後に次を実行します。
checkpoint.py done {RUN}/.dnr-respond-state 4
フェーズ 0 — リードを解析し、入力を特定する
--logs(dnr ターゲット設定またはディレクトリそのもの)を解決し、ログファイルを一覧にします(du、wc -l、各ファイルのhead -2を実行し、形式とフィールドの位置を特定します)。コーパスはすでに存在している想定です。生成方法についてはターゲットの README を参照してください。- リードを解析して、調査の起点となるエンティティを抽出します。
INC-n→{RUN}/INCIDENTS.json(上記で採用した実行ディレクトリ)から該当エントリを読み込みます。そのエントリの IP、アカウント、ルート、タイムライン行が起点となります。- アラート行 → 送信元 IP、ルール、パス、タイムスタンプ。
- IOC → そのエンティティ自体。
- 自由記述 → 観測可能な情報(報告者、データの内容、時期)を抽出します。起点となる情報がルートまたはテーブル名だけの場合もあります。
- 調査上の問いを1文で示します(例:「203.0.113.66 による /login へのアクセスは成功したのか。失敗した場合、何が起きたのか。」)。フェーズ 1 のすべての作業は、この問いに答えるためのものです。
チェックポイント phase0.json:
{"lead": ..., "lead_type": ..., "entities": {"ips": [], "accounts": [], "routes": [], "windows": []}, "question": ..., "logs_dir": ..., "repo": ..., "files": [...]}
フェーズ 1 — 範囲:対象エンティティが関与したすべての情報を抽出する
各シードエンティティについて、そのアクティビティをすべて抽出し、さらに1段階だけ範囲を広げます。
- IPごと: すべてのリクエストを、時系列順に、ルート、ステータス、サイズ、UA、認証済みユーザーとともに記録します。数百行をそのまま貼り付けず、行動のフェーズを要約します。
- アカウントごと: すべてのログイン(接続元IPを含む)と、認証済みのすべてのリクエストを記録します。同じコーパス内にある、そのアカウントの過去のパターンと比較します。新しいIP、新しいUA、新しい量、新しい時間帯は、いずれもシグナルです。(新しいIP だけ なら、通常の変動としてノイズ扱いします。ユーザーは移動するため、手がかりとして扱う前に、量、頻度、またはセッションで次に行われたことと組み合わせます。)
- ルートごと: 調査対象の時間枠内で、同じ方法でそのルートにアクセスした他のユーザーは誰ですか?アラートから見つかった攻撃者が唯一とは限りません。
- 範囲の拡張(1回のみ): 上で発見したエンティティを対象に加えます。IPがログインしたアカウント、同じアカウントにアクセスした2つ目のIP、セッションが移動した先のルートなどです。意図的に1回だけ範囲を広げます。範囲が拡大し続ける場合は、終わりのないループを扱える
/dnr-huntを使用します。 - すべてのエンティティについて、エラーログとアラートキューの行を相関させ、1つのタイムラインにまとめます。
チェックポイント phase1.json:
{"activity": [{"entity", "summary", "first_seen", "last_seen", "requests", "key_lines": [...]}], "expanded_entities": [...], "timeline": [...]}
フェーズ 2 — 判定と被害範囲
両面の証拠を示し、調査上の問いに答えます。
- 攻撃は成功しましたか? ステータスコードだけでは判断できません。401で遮断されていれば失敗です。一方、異常なサイズの200、エラーの後に成功する進行、データらしいレスポンスがあれば成功を示します。判定と、その判定を左右する証拠を明示します。
- 被害範囲を定量化します。 どのデータに到達したか:どのルート/テーブルか、レコード数はいくつか、どの期間にわたるか、何人の異なるユーザー/顧客に属するか。各数値はパイプラインを用いて算出します(持ち出しページのサイズを集計し、たどられた異なるIDを数え、ローカルで利用できる場合はアプリ独自のデータを使って、たどられたIDと所有者を結合します。フェーズ3の前に対象の決定的な
seed_commandを実行してもかまいません)。 - 封じ込め状況を確認します(読み取り専用)。 コーパスの時間枠の終わりにアクティビティは継続していますか、それとも停止しましたか?攻撃者の最後の操作はいつですか?
- 判定が「成功しなかった」の場合でも、調査はそこで終わりません。攻撃者が何を 試みていた のか、なぜ失敗したのかを説明します。それが計画の強化セクションと
ruled_outレコードに反映されます。
チェックポイント phase2.json:
{"verdict": ..., "succeeded": bool, "discriminators": [...], "blast_radius": {"data": ..., "records": N, "distinct_victims": N, "window": ...}, "ongoing": bool, "last_activity": ...}
フェーズ 3 — 根本原因とPoC
/dnr-hunt のフェーズ3〜4と同じ基準です。
--repo内で該当するハンドラーを読み、欠陥(または防御策)をファイル/関数レベルで特定します。同じ種類の欠陥がないか、関連ルートも確認します。- 対象の設定に従って、ローカルでアプリをシードし、起動します。
seed_command/app_commandは対象ディレクトリ基準です。対象ディレクトリをcwdとして実行し、PIDを出力する単一のコマンドを使ってバックグラウンドで起動します(python3 -c "...subprocess.Popen..."の同じパターンを使用します。ポートが使用中ならポート上書き用の環境変数を設定します)。最小限の PoCを実行して、コマンドとレスポンスの抜粋を記録します。防御されたルートに対して陰性PoCを実行した後、kill <pid>で停止します(ポートやパターンを指定した停止は行わないでください)。 - 実行可能なアプリがない場合、判定の上限は
suspectedであることを明示します。
チェックポイント phase3.json:
{"root_cause": {"file", "function", "mechanism"}, "poc": {"command", "status", "response_excerpt"}, "negative_poc": {...}, "siblings_checked": [...]}
フェーズ 4 — 対応計画(提案、未実行)と出力
計画を {RUN}/RESPONSE.md に書き込みます。実行ディレクトリに別のリード担当者による RESPONSE.md がすでに存在する場合は、{RUN}/RESPONSE-<lead>.md(例:RESPONSE-INC-2.md)に書き込みます。別のインシデントの計画を黙って上書きしてはいけません。各アクションには必ず 内容/理由/誤っていた場合のリスク を記載し、以下のセクションを含めます。
- 判定と概要 — 何が起きたのか、成功したのか、どこまで進んだのかを3文で記述します。
- タイムライン — フェーズ 1 で統合したエンティティのタイムラインです。
- 封じ込め(提案) — ブロック/拒否の候補(IP、セッション、トークン)、停止すべきアカウント、レート制限または無効化すべきルートを記載します。それぞれについて、根拠となる証拠の行と、判断が誤っていた場合に何が損なわれるか(例:NAT IP のブロックにより、その背後にいる正規ユーザーが締め出される)を示します。
- 根絶と修復(提案) — ファイル/関数のレベルでコード修正を説明し、
/triage互換の脆弱性エントリとして引き継ぎます。修正に時間がかかる場合の暫定的な緩和策も記載します。 - 復旧(提案) — ローテーションする認証情報(「全ユーザー」ではなく、照会したリストを正確に記載)、通知する顧客(フェーズ 2 の重複を除いた被害者リスト)、攻撃者が排除されたことを確認するための監視内容を記載します。
- 検知エンジニアリング(提案) — 実際にこの問題の発見に使った方法をもとに、もっと早期に検知できたルールを記載します。フェーズ 2 の識別力の高いクエリをアラートロジックに変換します(例:ルートごとの送信元別エラー率、連続する ID へのアクセス頻度、新しい IP からの大量のアカウントアクセス)。既存のアラートのうち、発報したもの、ノイズだったもの、およびそれがアラートキューについて何を示すかを記載します。
- 未解決の質問 — このコーパスからは回答できないこと(過去の履歴、他のログソース、盗まれたデータが他の場所で使用されたかどうか)を記載します。
次に {RUN}/INCIDENTS.json を更新します(/dnr-hunt と同じスキーマ。存在しない場合は作成します)。このインシデントを追加または充実させ、判定、定量化した影響、根本原因、PoC、タイムラインを記載します。追加のキーを使ってもかまいません。採点側は構造ではなく内容を照合します。否定的な結果は ruled_out に記載します。
完了としてマークし(checkpoint.py done {RUN}/.dnr-respond-state 4)、ユーザーに次の手順を案内します({RUN} は実際のパスに置き換えます)。
- 修正については、
{RUN}内から/triage INCIDENTS.json --repo <repo>を実行し、続けて/patch TRIAGE.json --repo <repo>を実行します。どちらも実行されたディレクトリに書き込むため、そこで実行すれば、判定とパッチはインシデントの隣に配置されます。 - dnr の対象では、
python <target>/grade.py {RUN}/INCIDENTS.jsonを実行して自己採点します(ユーザーが実行します)。 - 人間が各提案アクションをレビューし、実行または却下します。
RESPONSE.mdに記載された内容は、何も実行されていません。
デモを超えたカスタマイズ
- 実際のアラートキューから、アラートを一度に1件ずつ入力します。リード解析器は JSON または文章のアラートを受け付けるため、手法は変わりません。
- 認証ログ、CDN/WAF エクスポート、EDR の脅威イベントは、エンティティごとの追加アクティビティソースとしてフェーズ 1 に組み込みます。統合した単一のタイムラインを維持してください。
- フェーズ 4 の計画テンプレートは、組織に合わせた調整の効果が特に高い部分です。封じ込めを実際の制御(WAF ルール、IdP セッションの取り消し、機能フラグ)に対応付けます。ただし、引き続き担当チームに回す提案として扱い、エージェントが実行するアクションにはしません。