dnr-respond
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesednr-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 () can be:
$1- an incident id from the hunt's (e.g.
INCIDENTS.json) — theINC-2handoff/dnr-hunt - 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 directory or dnr target directory (same resolution as
--logs; defaults to/dnr-huntif it exists, else cwd).targets/dnrcanary - = application source (defaults to the target's
--repo).app/ - = ignore any checkpoint in the run dir's
--freshand mint a new run dir..dnr-respond-state/
手中已有线索的事件响应:从一个具体的起点出发,按顺序回答事件指挥官需要的四个问题——发生了什么、是否成功、影响范围有多大、我们该怎么办。 输出是一份判定、量化的爆炸半径、在源码中证实的根因,以及一份建议性质、绝不执行的响应计划。
通过 调用。
/dnr-respond <lead> [--logs dir] [--repo path] [--fresh]线索()可以是:
$1- 来自狩猎的 中的事件ID(如
INCIDENTS.json)——即INC-2的交接/dnr-hunt - 从告警队列粘贴的告警(JSON行或散文)
- 一个IOC:IP、账户名、路由、会话标识符
- 自由文本(“客户说他们的地址在一封钓鱼邮件中被引用”)
其他参数:
- = 日志目录或dnr目标目录(与
--logs相同的解析方式;如果存在则默认为/dnr-hunt,否则为当前工作目录)。targets/dnrcanary - = 应用源码(默认为目标的
--repo)。app/ - = 忽略运行目录
--fresh中已有的检查点,并创建新的运行目录。.dnr-respond-state/
Hard rules (shared with /dnr-hunt — the track's contract)
硬性规则(与 /dnr-hunt 共享——该流程的契约)
- Log evidence alone never confirms a vulnerability.
requires log evidence + the flaw in source + a PoC fired locally this session. Otherwise the ceiling is
confirmed_exploited.suspected - Query, don't read. Never or
Reada log file;cat/wc/headto profile,tail/grep/awk/sort/uniqto interrogate. Small files (alert queues, error logs under ~200 lines) may be read whole.python3 - Propose, never execute. No blocking, disabling, rotating, or config changes — in any environment, including the demo app. The plan names actions; humans execute them.
- Local only. PoCs fire at against an instance you started. Never touch a remote host.
127.0.0.1 - Every number is a query result. "How many records" and "how many customers" get answered by pipelines run this session, never estimated.
- 仅凭日志证据永远不能确认漏洞。 需要日志证据 + 源码中的缺陷 + 本次会话中在本地触发的PoC。否则最高只能判定为
confirmed_exploited。suspected - 查询,不要直接读取。 绝不要 或
Read日志文件;用cat/wc/head进行概要分析,用tail/grep/awk/sort/uniq进行查询。小文件(告警队列、约200行以下的错误日志)可以整体读取。python3 - 建议,绝不执行。 不进行阻止、禁用、轮换或配置更改——在任何环境中,包括演示应用。计划只列出行动;由人类执行。
- 仅限本地。 PoC 针对你启动的实例在 上触发。绝不接触远程主机。
127.0.0.1 - 每个数字都必须是查询结果。 “多少条记录”和“多少客户”由本次会话运行的流水线来回答,绝不估算。
Run directory and checkpointing
运行目录与检查点
First action — establish the run directory, the same way
does, but as a consumer. Let be the basename of the resolved
/target path (default ) and that path.
/dnr-huntTARGET--logstargets/dnrcanarySCOPE- Lead is (the
INC-nhandoff, or a second lead from a hunt you already started responding to): work the response into the investigation that incident lives in./dnr-huntis the newest{RUN}directory that contains anresults/TARGET/<timestamp>/— find it with Bash, e.g.INCIDENTS.json. Do not callls -1d results/TARGET/*/ | sort -r | while read d; do [ -f "$d/INCIDENTS.json" ] && echo "$d" && break; donefor anrundirlead —INC-nmints a fresh empty dir once a priorrundirin that dir has completed, which would strand the incident's/dnr-respondin the old dir. Only if no run dir holds anINCIDENTS.json(you were handedINCIDENTS.jsonbut never ran the hunt) fall through to theINC-nline below.rundir - Any other lead (alert, IOC, free text): Bash
(append
python3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-respond-state --scope "SCOPE"if it's in--fresh). This adopts the newest run dir for the target if one exists, else mints a fresh$ARGUMENTS.results/TARGET/<timestamp>/
That path is ; substitute the literal path everywhere below, and
double-quote it (and ) in Bash.
{RUN}SCOPECheckpoint mechanics are identical to , state dir
, phases 0–4. When already holds a
completed from an earlier lead, reports
and the rules below reset it cleanly — a second lead enriches the
same and writes its own . On start:
—
absent/complete/ → and start at Phase 0; running with
→ read …, resume at N+1. After
each phase: Write payload to , then
.
Never write directly; never pass payload via heredoc/stdin.
After outputs: .
/dnr-hunt{RUN}/.dnr-respond-state/{RUN}.dnr-respond-stateloadcompleteINCIDENTS.jsonRESPONSE-<lead>.mdpython3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-respond-state--freshresetphase_done == Nphase0.jsonphaseN.json{RUN}/.dnr-respond-state/_chunk.tmpcheckpoint.py save {RUN}/.dnr-respond-state <N> <name> --from {RUN}/.dnr-respond-state/_chunk.tmpprogress.jsoncheckpoint.py done {RUN}/.dnr-respond-state 4第一步——建立运行目录,方式与 相同,但是作为消费者。令 为解析后的 /目标路径的基名(默认为 ), 为该路径。
/dnr-huntTARGET--logstargets/dnrcanarySCOPE- 线索是 (
INC-n的交接,或你已经开始响应的狩猎中的第二条线索):将该响应纳入该事件所在的调查中。/dnr-hunt是包含{RUN}的最新INCIDENTS.json目录——用 Bash 找到它,例如results/TARGET/<timestamp>/。对于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>/
该路径即为 ;在下面所有地方替换为字面路径,并在 Bash 中对其(以及 )加双引号。
{RUN}SCOPE检查点机制与 相同,状态目录为 ,阶段 0–4。当 已经持有先前某线索的已完成 时, 会报告 ,下面的规则会干净地重置它——第二条线索会丰富同一个 ,并写入自己的 。开始时: —— 缺失/完成/ → 并从阶段0开始;运行中 → 读取 …,从 N+1 继续。每个阶段之后:将载荷写入 ,然后 。绝不直接写 ;绝不通过 heredoc/stdin 传递载荷。输出完成后:。
/dnr-hunt{RUN}/.dnr-respond-state/{RUN}.dnr-respond-stateloadcompleteINCIDENTS.jsonRESPONSE-<lead>.mdpython3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-respond-state--freshresetphase_done == Nphase0.jsonphaseN.json{RUN}/.dnr-respond-state/_chunk.tmpcheckpoint.py save {RUN}/.dnr-respond-state <N> <name> --from {RUN}/.dnr-respond-state/_chunk.tmpprogress.jsoncheckpoint.py done {RUN}/.dnr-respond-state 4Phase 0 — Parse the lead, locate inputs
阶段 0 — 解析线索,定位输入
- Resolve (dnr target config or bare directory) and inventory log files (
--logs,du,wc -leach — identify formats and field positions). The corpus is expected to exist already — the target README covers generating it.head -2 - Parse the lead into starting entities:
- → read that entry from
INC-n(the run dir adopted above); its IPs, accounts, routes, and timeline rows are the seed set.{RUN}/INCIDENTS.json - 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.
- 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": [...]}- 解析 (dnr 目标配置或裸目录),并盘点日志文件(分别执行
--logs、du、wc -l——识别格式和字段位置)。语料库应当已经存在——目标 README 中说明了如何生成。head -2 - 将线索解析为起始实体:
- → 从
INC-n(上面采用的运行目录)读取该条目;其IP、账户、路由和时间线行即为种子集合。{RUN}/INCIDENTS.json - 告警行 → 源IP、规则、路径、时间戳。
- IOC → 实体本身。
- 自由文本 → 提取可观察项(谁报告的、什么数据、何时);种子集合可以只从一个路由或表名开始。
- 用一句话陈述工作问题(例如:“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 early, before Phase 3, is fine).
seed_command - 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 record.
ruled_out
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 Phase 3–4:
/dnr-hunt- Read the implicated handler(s) in ; name the flaw (or the defense) at file/function precision; check sibling routes for the same flaw class.
--repo - Seed and start the app locally per target config: /
seed_commandare target-relative — run them with the target directory as cwd; launch in the background via a single command that prints the PID (the sameapp_commandpattern /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, thenpython3 -c "...subprocess.Popen..."(never by port or pattern).kill <pid> - No runnable app → verdict ceiling is , stated explicitly.
suspected
Checkpoint :
phase3.json{"root_cause": {"file", "function", "mechanism"}, "poc": {"command", "status", "response_excerpt"}, "negative_poc": {...}, "siblings_checked": [...]}与 阶段 3–4 相同的标准:
/dnr-hunt- 阅读 中涉及的处理器;以文件/函数精度指出缺陷(或防御);检查同级路由是否存在同类缺陷。
--repo - 按目标配置在本地初始化并启动应用:/
seed_command是目标相对路径——以目标目录作为 cwd 运行它们;通过一条打印 PID 的命令在后台启动(与 /dnr-hunt 阶段4展示的app_command模式相同,如果默认端口被占用则使用端口覆盖环境变量)。触发最小PoC,记录命令 + 响应摘录,对受防御的路由触发反向PoC,然后python3 -c "...subprocess.Popen..."(绝不按端口或模式 kill)。kill <pid> - 没有可运行的应用 → 判定上限为 ,并明确说明。
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 — or (e.g.
) if a 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:
{RUN}/RESPONSE.md{RUN}/RESPONSE-<lead>.mdRESPONSE-INC-2.mdRESPONSE.md- Verdict & summary — three sentences: what happened, did it succeed, how far it went.
- Timeline — the merged entity timeline from Phase 1.
- 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).
- Eradication & remediation (proposed) — the code fix, described at
file/function level, handed off as a -compatible vuln entry; interim mitigations if the fix will take time.
/triage - 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.
- 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.
- Open questions — what this corpus cannot answer (earlier history, other log sources, whether stolen data was used elsewhere).
Then update (same schema as ; 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 .
{RUN}/INCIDENTS.json/dnr-huntruled_outMark complete () and point the
user onward (substitute the literal path):
checkpoint.py done {RUN}/.dnr-respond-state 4{RUN}- for the fix, from inside :
{RUN}then/triage INCIDENTS.json --repo <repo>— both write to the directory they're invoked from, so running them there lands the verdicts and patches beside the incidents/patch TRIAGE.json --repo <repo> - on a dnr target: to self-score (the user runs this)
python <target>/grade.py {RUN}/INCIDENTS.json - a human reviews and executes (or rejects) each proposed action; nothing in RESPONSE.md has been done
将计划写入 ——如果运行目录中已存在来自其他线索的 ,则写入 (例如 );绝不静默覆盖另一次事件的计划。各小节中,每个行动都带有做什么 / 为什么 / 判断错误的风险:
{RUN}/RESPONSE.mdRESPONSE.md{RUN}/RESPONSE-<lead>.mdRESPONSE-INC-2.md- 判定与摘要 — 三句话:发生了什么、是否成功、影响范围有多大。
- 时间线 — 阶段1合并后的实体时间线。
- 遏制(建议) — 要阻止/拒绝的候选(IP、会话、令牌)、要暂停的账户、要限流或禁用的路由。对每一项:说明其依据的证据行,以及如果判断错误会破坏什么(例如阻止一个 NAT IP 会将其后的合法用户拒之门外)。
- 根除与修复(建议) — 代码修复,以文件/函数级别描述,作为兼容 的漏洞条目交接;如果修复需要时间则提供临时缓解措施。
/triage - 恢复(建议) — 具体轮换哪些凭据(查询出的列表,而不是“所有用户”)、通知哪些客户(阶段2中的不同受害者列表)、什么监控能确认攻击者已退出。
- 检测工程(建议) — 那些本可以更早发现此事的规则,源自你实际用于发现它的方法:将阶段2中的判别性查询转化为告警逻辑(例如路由上每个来源的错误率;顺序id访问节奏;来自新IP的大量账户活动)。说明哪些现有告警触发了、哪些是噪音,以及这对告警队列意味着什么。
- 待解决的问题 — 该语料库无法回答的问题(更早的历史、其他日志来源、被盗数据是否在别处被使用)。
然后更新 (与 相同的模式;如果不存在则创建):添加或丰富该事件——判定、量化影响、根因、PoC、时间线。额外的键没有问题——评分者按内容匹配,而不是结构。负面结果放入 。
{RUN}/INCIDENTS.json/dnr-huntruled_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 执行的动作。