dnr-respond

GitHub

用于安全事件响应,根据告警、IOC或线索分析攻击是否成功、量化影响范围、确认根因并制定恢复计划。

.claude/skills/dnr-respond/SKILL.md anthropics/defending-code-reference-harness

Trigger Scenarios

响应告警 处理入侵指标 评估攻击后果 生成应急响应方案

Install

npx skills add anthropics/defending-code-reference-harness --skill dnr-respond -g -y
More Options

Non-standard path

npx skills add https://github.com/anthropics/defending-code-reference-harness/tree/main/.claude/skills/dnr-respond -g -y

Use without installing

npx skills use anthropics/defending-code-reference-harness@dnr-respond

指定 Agent (Claude Code)

npx skills add anthropics/defending-code-reference-harness --skill dnr-respond -a claude-code -g -y

安装 repo 全部 skill

npx skills add anthropics/defending-code-reference-harness --all -g -y

预览 repo 内 skill

npx skills add anthropics/defending-code-reference-harness --list

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-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.

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

  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.

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/--freshreset and start at Phase 0; running with phase_done == N → read phase0.jsonphaseN.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

  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": [...]}

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_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": ...}

Phase 3 — Root cause and 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": [...]}

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:

  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

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

Same Skill Collection

.claude/skills/customize/SKILL.md
.claude/skills/dnr-hunt/SKILL.md
.claude/skills/quickstart/SKILL.md
.claude/skills/triage/SKILL.md
.claude/skills/verify/SKILL.md
.claude/skills/vuln-scan/SKILL.md
.claude/skills/patch/SKILL.md
.claude/skills/threat-model/SKILL.md

Metadata

Files
0
Version
6de8141
Hash
9027906e
Indexed
2026-07-19 22:58

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-03 15:13
浙ICP备14020137号-1 $방문자$