dnr-respond
Incident response workup for a lead in hand — an alert, an IOC,
Works with
---
name: dnr-respond
description: Incident response workup for a lead in hand — an alert, an IOC,
license: Apache-2.0
---
# 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/`--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
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.More Observability skills
google-agents-cli-observability
google/agents-cli
>
azure-observability
microsoft/azure-skills
Azure Observability Services including Azure Monitor, Application Insights, Log Analytics, Alerts, and Workbooks. Provides metrics, APM, distributed tracing, KQL queries, and interactive reports. USE FOR: Azure Monitor, Application Insights, Log Analytics, Alerts, Workbooks, metrics, APM, distributed tracing, KQL queries, interactive reports, observability, monitoring dashboards. DO NOT USE FOR: instrumenting apps with App Insights SDK (use appinsights-instrumentation), querying Kusto/ADX clusters (use azure-kusto), cost analysis (use azure-cost-optimization).
workers-best-practices
cloudflare/skills
Reviews and authors Cloudflare Workers code against production best practices. Load when writing new Workers, reviewing Worker code, configuring wrangler.jsonc, or checking for common Workers anti-patterns (streaming, floating promises, global state, secrets, bindings, observability). Biases towards retrieval from Cloudflare docs over pre-trained knowledge.

