dnr-respond

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dnr-respond

dnr-respond

Lead-in-hand incident response: take one concrete starting point and answer the four questions an incident commander needs, in order — what happened, did it succeed, how far did it go, and what do we do about it. The output is a verdict, a quantified blast radius, a root cause proven in source, and a response plan that is proposed, never executed.
Invoke with
/dnr-respond <lead> [--logs dir] [--repo path] [--fresh]
.
The lead (
$1
) can be:
  • an incident id from the hunt's
    INCIDENTS.json
    (e.g.
    INC-2
    ) — the
    /dnr-hunt
    handoff
  • an alert pasted from an alert queue (JSON line or prose)
  • an IOC: an IP, an account name, a route, a session identifier
  • free text ("a customer says their address was quoted in a phishing email")
Other arguments:
  • --logs
    = logs directory or dnr target directory (same resolution as
    /dnr-hunt
    ; defaults to
    targets/dnrcanary
    if it exists, else cwd).
  • --repo
    = application source (defaults to the target's
    app/
    ).
  • --fresh
    = ignore any checkpoint in the run dir's
    .dnr-respond-state/
    and mint a new run dir.
手中已有线索的事件响应:从一个具体的起点出发,按顺序回答事件指挥官需要的四个问题——发生了什么、是否成功、影响范围有多大、我们该怎么办。 输出是一份判定、量化的爆炸半径、在源码中证实的根因,以及一份建议性质、绝不执行的响应计划。
通过
/dnr-respond <lead> [--logs dir] [--repo path] [--fresh]
调用。
线索(
$1
)可以是:
  • 来自狩猎的
    INCIDENTS.json
    中的事件ID(如
    INC-2
    )——即
    /dnr-hunt
    的交接
  • 从告警队列粘贴的告警(JSON行或散文)
  • 一个IOC:IP、账户名、路由、会话标识符
  • 自由文本(“客户说他们的地址在一封钓鱼邮件中被引用”)
其他参数:
  • --logs
    = 日志目录或dnr目标目录(与
    /dnr-hunt
    相同的解析方式;如果存在则默认为
    targets/dnrcanary
    ,否则为当前工作目录)。
  • --repo
    = 应用源码(默认为目标的
    app/
    )。
  • --fresh
    = 忽略运行目录
    .dnr-respond-state/
    中已有的检查点,并创建新的运行目录。

Hard rules (shared with /dnr-hunt — the track's contract)

硬性规则(与 /dnr-hunt 共享——该流程的契约)

  1. Log evidence alone never confirms a vulnerability.
    confirmed_exploited
    requires log evidence + the flaw in source + a PoC fired locally this session. Otherwise the ceiling is
    suspected
    .
  2. Query, don't read. Never
    Read
    or
    cat
    a log file;
    wc
    /
    head
    /
    tail
    to profile,
    grep
    /
    awk
    /
    sort
    /
    uniq
    /
    python3
    to interrogate. Small files (alert queues, error logs under ~200 lines) may be read whole.
  3. Propose, never execute. No blocking, disabling, rotating, or config changes — in any environment, including the demo app. The plan names actions; humans execute them.
  4. Local only. PoCs fire at
    127.0.0.1
    against an instance you started. Never touch a remote host.
  5. Every number is a query result. "How many records" and "how many customers" get answered by pipelines run this session, never estimated.
  1. 仅凭日志证据永远不能确认漏洞。
    confirmed_exploited
    需要日志证据 + 源码中的缺陷 + 本次会话中在本地触发的PoC。否则最高只能判定为
    suspected
    。
  2. 查询,不要直接读取。 绝不要
    Read
    或
    cat
    日志文件;用
    wc
    /
    head
    /
    tail
    进行概要分析,用
    grep
    /
    awk
    /
    sort
    /
    uniq
    /
    python3
    进行查询。小文件(告警队列、约200行以下的错误日志)可以整体读取。
  3. 建议,绝不执行。 不进行阻止、禁用、轮换或配置更改——在任何环境中,包括演示应用。计划只列出行动;由人类执行。
  4. 仅限本地。 PoC 针对你启动的实例在
    127.0.0.1
    上触发。绝不接触远程主机。
  5. 每个数字都必须是查询结果。 “多少条记录”和“多少客户”由本次会话运行的流水线来回答,绝不估算。

Run directory and checkpointing

运行目录与检查点

First action — establish the run directory, the same way
/dnr-hunt
does, but as a consumer. Let
TARGET
be the basename of the resolved
--logs
/target path (default
targets/dnrcanary
) and
SCOPE
that path.
  • Lead is
    INC-n
    (the
    /dnr-hunt
    handoff, or a second lead from a hunt you already started responding to): work the response into the investigation that incident lives in.
    {RUN}
    is the newest
    results/TARGET/<timestamp>/
    directory that contains an
    INCIDENTS.json
    — find it with Bash, e.g.
    ls -1d results/TARGET/*/ | sort -r | while read d; do [ -f "$d/INCIDENTS.json" ] && echo "$d" && break; done
    . Do not call
    rundir
    for an
    INC-n
    lead —
    rundir
    mints a fresh empty dir once a prior
    /dnr-respond
    in that dir has completed, which would strand the incident's
    INCIDENTS.json
    in the old dir. Only if no run dir holds an
    INCIDENTS.json
    (you were handed
    INC-n
    but never ran the hunt) fall through to the
    rundir
    line below.
  • Any other lead (alert, IOC, free text): Bash
    python3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-respond-state --scope "SCOPE"
    (append
    --fresh
    if it's in
    $ARGUMENTS
    ). This adopts the newest run dir for the target if one exists, else mints a fresh
    results/TARGET/<timestamp>/
    .
That path is
{RUN}
; substitute the literal path everywhere below, and double-quote it (and
SCOPE
) in Bash.
Checkpoint mechanics are identical to
/dnr-hunt
, state dir
{RUN}/.dnr-respond-state/
, phases 0–4. When
{RUN}
already holds a completed
.dnr-respond-state
from an earlier lead,
load
reports
complete
and the rules below reset it cleanly — a second lead enriches the same
INCIDENTS.json
and writes its own
RESPONSE-<lead>.md
. On start:
python3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-respond-state
— absent/complete/
--fresh
→
reset
and start at Phase 0; running with
phase_done == N
→ read
phase0.json
…
phaseN.json
, resume at N+1. After each phase: Write payload to
{RUN}/.dnr-respond-state/_chunk.tmp
, then
checkpoint.py save {RUN}/.dnr-respond-state <N> <name> --from {RUN}/.dnr-respond-state/_chunk.tmp
. Never write
progress.json
directly; never pass payload via heredoc/stdin. After outputs:
checkpoint.py done {RUN}/.dnr-respond-state 4
.
第一步——建立运行目录,方式与
/dnr-hunt
相同,但是作为消费者。令
TARGET
为解析后的
--logs
/目标路径的基名(默认为
targets/dnrcanary
),
SCOPE
为该路径。
  • 线索是
    INC-n
    (
    /dnr-hunt
    的交接,或你已经开始响应的狩猎中的第二条线索):将该响应纳入该事件所在的调查中。
    {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 中对其(以及
SCOPE
)加双引号。
检查点机制与
/dnr-hunt
相同,状态目录为
{RUN}/.dnr-respond-state/
,阶段 0–4。当
{RUN}
已经持有先前某线索的已完成
.dnr-respond-state
时,
load
会报告
complete
,下面的规则会干净地重置它——第二条线索会丰富同一个
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
;绝不通过 heredoc/stdin 传递载荷。输出完成后:
checkpoint.py done {RUN}/.dnr-respond-state 4
。

Phase 0 — Parse the lead, locate inputs

阶段 0 — 解析线索,定位输入

  1. Resolve
    --logs
    (dnr target config or bare directory) and inventory log files (
    du
    ,
    wc -l
    ,
    head -2
    each — identify formats and field positions). The corpus is expected to exist already — the target README covers generating it.
  2. Parse the lead into starting entities:
    • INC-n
      → read that entry from
      {RUN}/INCIDENTS.json
      (the run dir adopted above); its IPs, accounts, routes, and timeline rows are the seed set.
    • alert line → source IP, rule, path, timestamp.
    • IOC → the entity itself.
    • free text → extract the observables (who reported, what data, when); the seed set may start as just a route or a table name.
  3. State the working question in one sentence (e.g. "did 203.0.113.66's activity against /login succeed, and if not, what did?"). Everything in Phase 1 serves that question.
Checkpoint
phase0.json
:
{"lead": ..., "lead_type": ..., "entities": {"ips": [], "accounts": [], "routes": [], "windows": []}, "question": ..., "logs_dir": ..., "repo": ..., "files": [...]}
  1. 解析
    --logs
    (dnr 目标配置或裸目录),并盘点日志文件(分别执行
    du
    、
    wc -l
    、
    head -2
    ——识别格式和字段位置)。语料库应当已经存在——目标 README 中说明了如何生成。
  2. 将线索解析为起始实体:
    • INC-n
      → 从
      {RUN}/INCIDENTS.json
      (上面采用的运行目录)读取该条目;其IP、账户、路由和时间线行即为种子集合。
    • 告警行 → 源IP、规则、路径、时间戳。
    • IOC → 实体本身。
    • 自由文本 → 提取可观察项(谁报告的、什么数据、何时);种子集合可以只从一个路由或表名开始。
  3. 用一句话陈述工作问题(例如:“203.0.113.66 针对 /login 的活动是否成功,如果没有,那是什么?”)。阶段1中的所有工作都服务于该问题。
检查点
phase0.json
:
{"lead": ..., "lead_type": ..., "entities": {"ips": [], "accounts": [], "routes": [], "windows": []}, "question": ..., "logs_dir": ..., "repo": ..., "files": [...]}

Phase 1 — Scope: pull everything the entities touched

阶段 1 — 界定范围:拉取实体触及的所有内容

For each seed entity, extract its complete activity, then expand one ring:
  • Per IP: every request — time-ordered, with route, status, size, UA, and authenticated user. Summarize phases of behavior, don't paste hundreds of lines.
  • Per account: every login (from which IPs), every authenticated request. Compare against that account's historical pattern in the same corpus — new IP, new UA, new volume, new hours are all signal. (A new IP alone is baseline noise — users roam; combine it with volume, cadence, or what the session did next before treating it as a lead.)
  • Per route: who else hit it the same way in the window? One attacker found via an alert may not be the only one.
  • Ring expansion (once): entities discovered above join the set — the account an IP logged into, the second IP that touched the same account, the route a session pivoted to. Expand once, deliberately; /dnr-hunt's open-ended loop is the tool when scope keeps growing.
  • Correlate the error log and alert queue rows for all entities into one timeline.
Checkpoint
phase1.json
:
{"activity": [{"entity", "summary", "first_seen", "last_seen", "requests", "key_lines": [...]}], "expanded_entities": [...], "timeline": [...]}
对每个种子实体,提取其完整活动,然后向外扩展一环:
  • 每个IP: 每个请求——按时间排序,包含路由、状态、大小、UA和认证用户。总结行为阶段,不要粘贴数百行。
  • 每个账户: 每次登录(来自哪些IP)、每个已认证请求。与该账户在同一语料库中的历史行为模式进行对比——新IP、新UA、新流量、新时段都是信号。(仅单独一个新IP属于基线噪音——用户会漫游;将其与流量、节奏或会话接下来做了什么结合起来,再视为线索。)
  • 每个路由: 在时间窗口内还有谁以相同方式访问过它?通过告警发现的一个攻击者可能不是唯一一个。
  • 环扩展(一次): 上面发现的实体加入集合——IP登录的账户、触及同一账户的第二个IP、会话转向的路由。审慎地扩展一次;当范围持续扩大时,应使用 /dnr-hunt 的开放式循环工具。
  • 关联错误日志和告警队列中所有实体的行,合并为一条时间线。
检查点
phase1.json
:
{"activity": [{"entity", "summary", "first_seen", "last_seen", "requests", "key_lines": [...]}], "expanded_entities": [...], "timeline": [...]}

Phase 2 — Verdict and blast radius

阶段 2 — 判定与爆炸半径

Answer the working question with evidence on both sides:
  • Did it succeed? Status codes alone don't answer this — a 401 wall says no; 200s with anomalous sizes, error-then-success progressions, and data-shaped responses say yes. State the verdict and the discriminating evidence.
  • Blast radius, quantified. What data was reached: which routes/tables, how many records, over what time span, belonging to how many distinct users/customers. Each figure comes from a pipeline (count the exfil-page sizes, count the distinct ids walked, join walked ids to owners via the app's own data if available locally — running the target's deterministic
    seed_command
    early, before Phase 3, is fine).
  • Containment status check (read-only). Is the activity ongoing at the end of the corpus window, or did it stop? When was the last attacker action?
  • If the verdict is "did not succeed", the workup doesn't end — say what the attacker was trying and why it failed; that feeds the plan's hardening section and the
    ruled_out
    record.
Checkpoint
phase2.json
:
{"verdict": ..., "succeeded": bool, "discriminators": [...], "blast_radius": {"data": ..., "records": N, "distinct_victims": N, "window": ...}, "ongoing": bool, "last_activity": ...}
用正反两方面的证据回答工作问题:
  • 是否成功? 仅凭状态码无法回答——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": ...}

Phase 3 — Root cause and PoC

阶段 3 — 根因与PoC

Same bar as
/dnr-hunt
Phase 3–4:
  1. Read the implicated handler(s) in
    --repo
    ; name the flaw (or the defense) at file/function precision; check sibling routes for the same flaw class.
  2. Seed and start the app locally per target config:
    seed_command
    /
    app_command
    are target-relative — run them with the target directory as cwd; launch in the background via a single command that prints the PID (the same
    python3 -c "...subprocess.Popen..."
    pattern /dnr-hunt Phase 4 shows, with the port override env var if the default port is busy). Fire the minimal PoC, record command + response excerpt, fire the negative PoC against defended routes, then
    kill <pid>
    (never by port or pattern).
  3. No runnable app → verdict ceiling is
    suspected
    , stated explicitly.
Checkpoint
phase3.json
:
{"root_cause": {"file", "function", "mechanism"}, "poc": {"command", "status", "response_excerpt"}, "negative_poc": {...}, "siblings_checked": [...]}
与
/dnr-hunt
阶段 3–4 相同的标准:
  1. 阅读
    --repo
    中涉及的处理器;以文件/函数精度指出缺陷(或防御);检查同级路由是否存在同类缺陷。
  2. 按目标配置在本地初始化并启动应用:
    seed_command
    /
    app_command
    是目标相对路径——以目标目录作为 cwd 运行它们;通过一条打印 PID 的命令在后台启动(与 /dnr-hunt 阶段4展示的
    python3 -c "...subprocess.Popen..."
    模式相同,如果默认端口被占用则使用端口覆盖环境变量)。触发最小PoC,记录命令 + 响应摘录,对受防御的路由触发反向PoC,然后
    kill <pid>
    (绝不按端口或模式 kill)。
  3. 没有可运行的应用 → 判定上限为
    suspected
    ,并明确说明。
检查点
phase3.json
:
{"root_cause": {"file", "function", "mechanism"}, "poc": {"command", "status", "response_excerpt"}, "negative_poc": {...}, "siblings_checked": [...]}

Phase 4 — The response plan (PROPOSED, NEVER EXECUTED) and outputs

阶段 4 — 响应计划(建议性质,绝不执行)与输出

Write the plan to
{RUN}/RESPONSE.md
— or
{RUN}/RESPONSE-<lead>.md
(e.g.
RESPONSE-INC-2.md
) if a
RESPONSE.md
from a different lead already exists in the run dir; never silently overwrite another incident's plan. Sections, every action carrying what / why / risk-if-wrong:
  1. Verdict & summary — three sentences: what happened, did it succeed, how far it went.
  2. Timeline — the merged entity timeline from Phase 1.
  3. Containment (proposed) — block/deny candidates (IPs, sessions, tokens), accounts to suspend, routes to rate-limit or disable. For each: the evidence line that justifies it and what breaks if the call is wrong (e.g. blocking a NAT IP locks out legitimate users behind it).
  4. Eradication & remediation (proposed) — the code fix, described at file/function level, handed off as a
    /triage
    -compatible vuln entry; interim mitigations if the fix will take time.
  5. Recovery (proposed) — exactly which credentials rotate (the queried list, not "all users"), which customers get notified (the distinct-victim list from Phase 2), what monitoring confirms the attacker is out.
  6. Detection engineering (proposed) — the rules that would have caught this earlier, derived from what you actually used to find it: the discriminating queries from Phase 2 turned into alert logic (e.g. error-rate per source on a route; sequential-id access cadence; account-from-new-IP at volume). Note which existing alerts fired, which were noise, and what that says about the queue.
  7. Open questions — what this corpus cannot answer (earlier history, other log sources, whether stolen data was used elsewhere).
Then update
{RUN}/INCIDENTS.json
(same schema as
/dnr-hunt
; create it if absent): add or enrich this incident — verdict, quantified impact, root cause, PoC, timeline. Extra keys are fine — graders match on content, not structure. Negative outcomes go to
ruled_out
.
Mark complete (
checkpoint.py done {RUN}/.dnr-respond-state 4
) and point the user onward (substitute the literal
{RUN}
path):
  • for the fix, from inside
    {RUN}
    :
    /triage INCIDENTS.json --repo <repo>
    then
    /patch TRIAGE.json --repo <repo>
    — both write to the directory they're invoked from, so running them there lands the verdicts and patches beside the incidents
  • on a dnr target:
    python <target>/grade.py {RUN}/INCIDENTS.json
    to self-score (the user runs this)
  • a human reviews and executes (or rejects) each proposed action; nothing in RESPONSE.md has been done
将计划写入
{RUN}/RESPONSE.md
——如果运行目录中已存在来自其他线索的
RESPONSE.md
,则写入
{RUN}/RESPONSE-<lead>.md
(例如
RESPONSE-INC-2.md
);绝不静默覆盖另一次事件的计划。各小节中,每个行动都带有做什么 / 为什么 / 判断错误的风险:
  1. 判定与摘要 — 三句话:发生了什么、是否成功、影响范围有多大。
  2. 时间线 — 阶段1合并后的实体时间线。
  3. 遏制(建议) — 要阻止/拒绝的候选(IP、会话、令牌)、要暂停的账户、要限流或禁用的路由。对每一项:说明其依据的证据行,以及如果判断错误会破坏什么(例如阻止一个 NAT IP 会将其后的合法用户拒之门外)。
  4. 根除与修复(建议) — 代码修复,以文件/函数级别描述,作为兼容
    /triage
    的漏洞条目交接;如果修复需要时间则提供临时缓解措施。
  5. 恢复(建议) — 具体轮换哪些凭据(查询出的列表,而不是“所有用户”)、通知哪些客户(阶段2中的不同受害者列表)、什么监控能确认攻击者已退出。
  6. 检测工程(建议) — 那些本可以更早发现此事的规则,源自你实际用于发现它的方法:将阶段2中的判别性查询转化为告警逻辑(例如路由上每个来源的错误率;顺序id访问节奏;来自新IP的大量账户活动)。说明哪些现有告警触发了、哪些是噪音,以及这对告警队列意味着什么。
  7. 待解决的问题 — 该语料库无法回答的问题(更早的历史、其他日志来源、被盗数据是否在别处被使用)。
然后更新
{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 中的任何内容都尚未执行

Customizing beyond the demo

在演示之外的自定义

  • Feed it your real alert queue one alert at a time — the lead parser takes any JSON or prose alert; the method doesn't change.
  • Auth logs, CDN/WAF exports, and EDR threat events slot into Phase 1 as additional per-entity activity sources; keep the single merged timeline.
  • The Phase 4 plan template is the part most worth adapting to your org: map containment to your actual controls (WAF rules, IdP session revocation, feature flags) — still as proposals routed to the owning team, never as actions the agent takes.
  • 将真实告警队列逐条喂给它——线索解析器接受任何 JSON 或散文告警;方法不变。
  • 认证日志、CDN/WAF 导出和 EDR 威胁事件可作为额外的按实体活动来源插入阶段1;保持单一合并时间线。
  • 阶段4的计划模板是最值得根据你的组织进行调整的部分:将遏制措施映射到你的实际控制手段(WAF规则、IdP会话撤销、功能开关)——仍然作为路由给负责团队的建议,绝不是由 Agent 执行的动作。