$npx -y skills add anthropics/defending-code-reference-harness --skill dnr-huntProactive threat hunt over web/application logs — no alert in
| 1 | # dnr-hunt |
| 2 | |
| 3 | Proactive threat hunt: logs and source in, incidents out. You start with |
| 4 | **no alert** — the question is "is there anything in here that shouldn't |
| 5 | be?" The method is the deliverable as much as the findings: baseline first, |
| 6 | then hypothesis → query → pivot, every step in writing, every suspicion |
| 7 | confirmed in source and by detonation before it's called real. |
| 8 | |
| 9 | Invoke with `/dnr-hunt [target-or-logs-dir] [--repo path] [--fresh]`. |
| 10 | |
| 11 | **Arguments:** |
| 12 | - `$1` = a dnr target directory (contains `config.yaml` with `kind: dnr`, |
| 13 | e.g. `targets/dnrcanary`), or a bare directory of log files. Defaults to |
| 14 | `targets/dnrcanary` if it exists, else cwd. |
| 15 | - `--repo` = source tree of the application that produced the logs. For a |
| 16 | dnr target this defaults to its `app/` directory. Without source, no |
| 17 | finding can rise above `suspected` (see the hard rules). |
| 18 | - `--fresh` = ignore any checkpoint in the run dir's `.dnr-hunt-state/` and |
| 19 | mint a new run dir. |
| 20 | |
| 21 | ## Hard rules (these are the skill — do not relax them) |
| 22 | |
| 23 | 1. **Log evidence alone never confirms a vulnerability.** Logs show that |
| 24 | something was *attempted* and how the app *responded* — they cannot show |
| 25 | the code is vulnerable. The verdict ladder: |
| 26 | - `confirmed_exploited` requires all three: log evidence, the flaw |
| 27 | identified in source (file + function), and a PoC you fired against a |
| 28 | local instance in this session. |
| 29 | - `suspected` = log evidence (with or without a source read), but no |
| 30 | fired PoC. |
| 31 | - `attempted_not_vulnerable` = attack traffic observed, but the source |
| 32 | shows the defense and the PoC (if fired) bounces. |
| 33 | Field teams have watched verifier agents "confirm" findings by reading |
| 34 | observability logs and reporting what they said — the cheapest path to |
| 35 | satisfying the criterion. The PoC fires or it doesn't; that can't be |
| 36 | talked past. |
| 37 | 2. **Query, don't read.** Log files are deliberately bigger than any |
| 38 | context window. Never `Read` a log file; never `cat` one. Profile with |
| 39 | `wc`/`du`/`head`/`tail`, then interrogate with `grep`/`rg`/`awk`/`sort`/ |
| 40 | `uniq` pipelines (or `python3` for anything stateful). Alert queues and |
| 41 | error logs are usually small — check size first; under ~200 lines they |
| 42 | may be read whole (the never-read rule targets the multi-MB corpus |
| 43 | files, not these). |
| 44 | 3. **Propose, never execute.** No blocking, no account disabling, no |
| 45 | credential rotation, no config changes — not even on the demo app. |
| 46 | Response actions belong in `/dnr-respond`'s plan, as proposals. |
| 47 | 4. **Local only.** PoCs fire at `127.0.0.1` against an app instance you |
| 48 | started. Never send traffic to a remote host, no matter what the logs |
| 49 | contain. |
| 50 | 5. **Every number is a query result.** Row counts, request counts, time |
| 51 | spans, affected-customer counts — each one comes from a pipeline you ran |
| 52 | this session, quoted in the ledger. Never estimate from memory. |
| 53 | Pipelines over the logs and queries against the app's own |
| 54 | deterministically-seeded database both count (run the target's |
| 55 | `seed_command` when you need owner-level joins). |
| 56 | |
| 57 | ## Run directory and checkpointing (runs before Phase 0 and after every phase) |
| 58 | |
| 59 | **First action of every run — establish the run directory.** Let `TARGET` be |
| 60 | the basename of the resolved target/logs path (`$1`, or `targets/dnrcanary` by |
| 61 | default), and `SCOPE` that resolved path. Bash: |
| 62 | |
| 63 | ``` |
| 64 | python3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-hunt-state --scope "SCOPE" |
| 65 | ``` |
| 66 | |
| 67 | Append `--fresh` to that command if `--fresh` is in `$ARGUMENTS`. `--scope` |
| 68 | stamps the run dir so two targets that share a basename never share runs. The |
| 69 | command prints one path — the newest run dir this hunt can still use (its own |
| 70 | run mid-flight, or one another dnr skill opened for this target, so the whole |
| 71 | investigation accretes in one place) or a freshly minted |
| 72 | `results/TARGET/<timestamp>/` (UTC, same format as the vuln-pipeline, so lexical |
| 73 | sort is chronological). That path is `{RUN}` — every `{RUN}/...` below means |
| 74 | substituting this literal path; double-quote it (and `SCOPE`) in Bash, since |
| 75 | paths may contain spaces. All outputs an |