dnr-respond
GitHub用于安全事件响应,根据告警、IOC或线索分析攻击是否成功、量化影响范围、确认根因并制定恢复计划。
Trigger Scenarios
Install
npx skills add anthropics/defending-code-reference-harness --skill dnr-respond -g -y
SKILL.md
Frontmatter
{
"name": "dnr-respond",
"description": "Incident response workup for a lead in hand — an alert, an IOC, or a \/dnr-hunt finding. Scopes the lead across the logs, verdicts whether the attack succeeded, quantifies blast radius, confirms root cause in source with a local PoC, and writes a proposed (never executed) containment\/remediation\/recovery plan. Use when asked to \"respond to this alert\", \"work this incident\", \"how bad is this\", or \"run dnr-respond\". \/dnr-hunt is the no-alert entry to the same track.",
"allowed-tools": [
"Read",
"Glob",
"Grep",
"Write",
"Bash(rg:*)",
"Bash(grep:*)",
"Bash(awk:*)",
"Bash(sed:*)",
"Bash(sort:*)",
"Bash(uniq:*)",
"Bash(cut:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(wc:*)",
"Bash(ls:*)",
"Bash(du:*)",
"Bash(file:*)",
"Bash(jq:*)",
"Bash(python3:*)",
"Bash(curl:*)",
"Bash(kill:*)"
],
"argument-hint": "<lead> [--logs dir] [--repo path] [--fresh]"
}
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-hunthandoff - 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 totargets/dnrcanaryif it exists, else cwd).--repo= application source (defaults to the target'sapp/).--fresh= ignore any checkpoint in the run dir's.dnr-respond-state/and mint a new run dir.
Hard rules (shared with /dnr-hunt — the track's contract)
- Log evidence alone never confirms a vulnerability.
confirmed_exploitedrequires log evidence + the flaw in source + a PoC fired locally this session. Otherwise the ceiling issuspected. - Query, don't read. Never
Readorcata log file;wc/head/tailto profile,grep/awk/sort/uniq/python3to interrogate. Small files (alert queues, error logs under ~200 lines) may be read whole. - 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
127.0.0.1against an instance you started. Never touch a remote host. - Every number is a query result. "How many records" and "how many customers" get answered by pipelines run this session, never estimated.
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-hunthandoff, 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 newestresults/TARGET/<timestamp>/directory that contains anINCIDENTS.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 callrundirfor anINC-nlead —rundirmints a fresh empty dir once a prior/dnr-respondin that dir has completed, which would strand the incident'sINCIDENTS.jsonin the old dir. Only if no run dir holds anINCIDENTS.json(you were handedINC-nbut never ran the hunt) fall through to therundirline 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--freshif it's in$ARGUMENTS). This adopts the newest run dir for the target if one exists, else mints a freshresults/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.
Phase 0 — Parse the lead, locate inputs
- Resolve
--logs(dnr target config or bare directory) and inventory log files (du,wc -l,head -2each — identify formats and field positions). The corpus is expected to exist already — the target README covers generating it. - 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.
- 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": [...]}
Phase 1 — Scope: pull everything the entities touched
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": [...]}
Phase 2 — Verdict and blast radius
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_commandearly, 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_outrecord.
Checkpoint 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
Same bar as /dnr-hunt Phase 3–4:
- 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. - Seed and start the app locally per target config:
seed_command/app_commandare target-relative — run them with the target directory as cwd; launch in the background via a single command that prints the PID (the samepython3 -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, thenkill <pid>(never by port or pattern). - 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": [...]}
Phase 4 — The response plan (PROPOSED, NEVER EXECUTED) and outputs
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:
- 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
/triage-compatible vuln entry; interim mitigations if the fix will take time. - 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 {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.jsonto self-score (the user runs this) - a human reviews and executes (or rejects) each proposed action; nothing in RESPONSE.md has been done
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.
Version History
- 6de8141 Current 2026-07-19 22:58


