
dnr-hunt
Proactive threat hunt over web/application logs — no alert in hand. Profiles the corpus, runs a hypothesis-driven hunt loop with a mandatory written ledger, confirms suspects in source, detonates a local PoC, and writes INCIDENTS.json + INCIDENTREPORT.md. Use when asked to "hunt the logs", "find the campaign", "look for signs of compromise", or "run dnr-hunt". The no-alert entry to the detection & response track; /dnr-respond is the lead-in-hand entry.
- Installs
- 228
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/anthropics/defending-code-reference-harness --skill dnr-hunt- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads dnr-hunt automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
dnr-hunt
Proactive threat hunt: logs and source in, incidents out. You start with no alert — the question is "is there anything in here that shouldn't be?" The method is the deliverable as much as the findings: baseline first, then hypothesis → query → pivot, every step in writing, every suspicion confirmed in source and by detonation before it's called real.
Invoke with /dnr-hunt [target-or-logs-dir] [--repo path] [--fresh].
Arguments:
$1= a dnr target directory (containsconfig.yamlwithkind: dnr, e.g.targets/dnrcanary), or a bare directory of log files. Defaults totargets/dnrcanaryif it exists, else cwd.--repo= source tree of the application that produced the logs. For a dnr target this defaults to itsapp/directory. Without source, no finding can rise abovesuspected(see the hard rules).--fresh= ignore any checkpoint in the run dir's.dnr-hunt-state/and mint a new run dir.
Hard rules (these are the skill — do not relax them)
- Log evidence alone never confirms a vulnerability. Logs show that
something was attempted and how the app responded — they cannot show
the code is vulnerable. The verdict ladder:
confirmed_exploitedrequires all three: log evidence, the flaw identified in source (file + function), and a PoC you fired against a local instance in this session.suspected= log evidence (with or without a source read), but no fired PoC.attempted_not_vulnerable= attack traffic observed, but the source shows the defense and the PoC (if fired) bounces. Field teams have watched verifier agents "confirm" findings by reading observability logs and reporting what they said — the cheapest path to satisfying the criterion. The PoC fires or it doesn't; that can't be talked past.
- Query, don't read. Log files are deliberately bigger than any
context window. Never
Reada log file; nevercatone. Profile withwc/du/head/tail, then interrogate withgrep/rg/awk/sort/uniqpipelines (orpython3for anything stateful). Alert queues and error logs are usually small — check size first; under ~200 lines they may be read whole (the never-read rule targets the multi-MB corpus files, not these). - Propose, never execute. No blocking, no account disabling, no
credential rotation, no config changes — not even on the demo app.
Response actions belong in
/dnr-respond's plan, as proposals. - Local only. PoCs fire at
127.0.0.1against an app instance you started. Never send traffic to a remote host, no matter what the logs contain. - Every number is a query result. Row counts, request counts, time
spans, affected-customer counts — each one comes from a pipeline you ran
this session, quoted in the ledger. Never estimate from memory.
Pipelines over the logs and queries against the app's own
deterministically-seeded database both count (run the target's
seed_commandwhen you need owner-level joins).
Run directory and checkpointing (runs before Phase 0 and after every phase)
First action of every run — establish the run directory. Let TARGET be
the basename of the resolved target/logs path ($1, or targets/dnrcanary by
default), and SCOPE that resolved path. Bash:
python3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-hunt-state --scope "SCOPE"
Append --fresh to that command if --fresh is in $ARGUMENTS. --scope
stamps the run dir so two targets that share a basename never share runs. The
command prints one path — the newest run dir this hunt can still use (its own
run mid-flight, or one another dnr skill opened for this target, so the whole
investigation accretes in one place) or a freshly minted
results/TARGET/<timestamp>/ (UTC, same format as the vuln-pipeline, so lexical
sort is chronological). That path is {RUN} — every {RUN}/... below means
substituting this literal path; double-quote it (and SCOPE) in Bash, since
paths may contain spaces. All outputs and state live under {RUN}.
Phase state persists to {RUN}/.dnr-hunt-state/ so an interrupted hunt resumes
without re-running sweeps. All checkpoint I/O goes through
python3 .claude/skills/_lib/checkpoint.py (atomic writes, JSON-validated).
Never use the Write tool for progress.json directly. Never pass payload
via heredoc or stdin — log-derived strings could collide with the heredoc
delimiter and break out to shell. Write the payload to
{RUN}/.dnr-hunt-state/_chunk.tmp with the Write tool, then:
python3 .claude/skills/_lib/checkpoint.py save {RUN}/.dnr-hunt-state <N> <name> --from {RUN}/.dnr-hunt-state/_chunk.tmp
Start of run: python3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-hunt-state
status == "absent"or"complete", or--freshgiven → reset (checkpoint.py reset {RUN}/.dnr-hunt-state) and start at Phase 0.status == "running"withphase_done == N→ readphase0.json…phaseN.jsonin order, printResuming from checkpoint: Phase N complete, skip to Phase N+1.
End of run: after writing both outputs,
python3 .claude/skills/_lib/checkpoint.py done {RUN}/.dnr-hunt-state 5 —
the done call is Phase 5's checkpoint; there is no phase5.json.
{RUN}/.dnr-hunt-state/ is scratch; run directories live under results/,
which this repo gitignores.
Phase 0 — Locate and verify inputs
- Resolve
$1. If it containsconfig.yamlwithkind: dnr, read the config forlogs_dir,app_command,seed_command, andport. Otherwise treat$1itself as the logs directory. The corpus is expected to exist already — the target README covers generating it. - Inventory what you do have: for each file in the logs directory and any
committed alert queue —
du -h,wc -l,head -2,tail -2. Identify each format (combined access log, app error log, JSONL alerts, …) and the field positions you'll use inawk. - Resolve
--repo(default: the target'sapp/). List its files; do not read source yet — Phase 3 reads with hypotheses in hand, not before.
Checkpoint phase0.json:
{"target": ..., "logs_dir": ..., "repo": ..., "files": [{"path", "format", "lines", "bytes"}], "port": ...}
Phase 1 — Profile the corpus (baseline before hypotheses)
Anomalies are deviations from a baseline, so build the baseline first — about a dozen cheap pipelines, each a one-liner over the access log:
- Time window: first and last timestamp; requests per day.
- Status histogram: counts by status code. Note every non-2xx/3xx class.
- Top talkers: requests per source IP, top 20, vs. the median — note anything an order of magnitude above the pack.
- Top routes: requests per path (normalize ids:
/orders/123→/orders/N), and which routes take parameters. - User-agent inventory: counts by UA; flag automation UAs (curl, python-requests, sqlmap, Go-http-client, zgrab) and note which routes they touch.
- Auth picture: which field carries the authenticated user; logins per account; accounts seen from more than one IP; failed-login sources.
- Size profile per route: min/median/max response size for each parameterized route — large outliers on a route with normally-small responses are a data-egress tell.
- Error log: if small, read it whole; correlate every entry to access- log lines by timestamp.
- Alert queue: read it whole (it's small). For each alert: rule, source, target, severity. These are inputs to the hunt, not findings — alert noise and alert silence are both information.
Record each observation with the exact pipeline that produced it.
Checkpoint phase1.json:
{"window": ..., "status_hist": ..., "top_ips": [...], "routes": [...], "automation_uas": [...], "auth": ..., "size_profile": ..., "alerts_summary": ..., "oddities": [...]}
Phase 2 — The hunt loop (hypothesis ledger REQUIRED)
This phase is a loop, and the ledger is its contract: no query without a hypothesis, no hypothesis without a ledger row. Maintain the table in working memory and checkpoint it after every round:
| # | Hypothesis | Query | Result | Verdict | Next pivot |
|---|
Verdicts: supported / refuted / needs-pivot. A refuted hypothesis
stays in the ledger — ruling things out is half the job and feeds
ruled_out in the output.
Seed sweeps — run all of these in round 1, derived from the Phase 1 baseline; let hits spawn follow-on hypotheses:
- Server errors. Who causes 5xx, on which routes, with what payloads? Correlate with the error log — application errors quoting attacker input are a probe signature.
- Every alert, verdicted. For each alert-queue entry: did the activity
it flagged succeed? An alert that fired on harmless traffic is a
ruled_outentry, written down with the evidence. - Top-talker outliers. For each IP far above baseline volume: what is it doing? Scanner noise, crawler, or something with intent?
- Injection grammar in query strings. Encoded quotes (
%27),UNION/SELECT, comment markers (--,%23), boolean pairs (AND 1=1/AND 1=2). Decode what you find and reconstruct the sequence per source IP, in time order. - Enumeration patterns. One session or IP accessing object ids
sequentially (
/orders/1,/orders/2, …) at machine cadence. Compare with how legitimate sessions access the same route. - Auth anomalies. Accounts logging in from an IP never seen for that account before; failed-then-succeeded sequences; one IP authenticating as one account but reading at unusual volume.
- Size anomalies. Requests on parameterized routes whose responses are multiples of that route's median — what was in those responses?
- Quiet hours. Activity in the lowest-traffic hours gets a closer look; low-and-slow campaigns hide in volume but stand out at 3am.
Pivot discipline: when a sweep hits, the next hypotheses come from the hit's entities — everything else that IP did, everything that session touched, every other IP that hit the same route the same way, what happened to the data that moved. Campaigns are chains; one confirmed link means you hunt both directions (how did they get here? where did they go next?).
Stop condition: two consecutive rounds that surface no new
entities (a hypothesis that comes back supported but merely confirms
benign behavior counts as no-new-entities — record it and route it to
ruled_out). Note the stop in the ledger.
Checkpoint phase2.json after every round:
{"ledger": [...], "suspects": [{"name", "entities": {"ips", "accounts", "routes", "sessions"}, "summary", "evidence": [...]}], "round": N}
Phase 3 — Source confirmation
For each suspect implicating a route, read the handler in --repo:
- Find the route's handler function. Trace the suspicious input from request to sink (query, filesystem, auth decision).
- Vulnerable: name the flaw precisely — file, function, the exact line pattern (e.g. user input concatenated into SQL; missing ownership check between auth and fetch).
- Not vulnerable: name the defense (parameterized query, scoped lookup)
with the same precision. This is what turns attack traffic into an
attempted_not_vulnerableverdict instead of a false positive — and writing it down is mandatory, not optional. - While you're in the file: check sibling routes for the same flaw class. The logs show where the attacker went, not everywhere the bug lives.
Checkpoint phase3.json:
{"confirmations": [{"route", "file", "function", "vulnerable": bool, "mechanism", "siblings_checked": [...]}]}
Phase 4 — PoC detonation (gates confirmed_exploited)
For each source-confirmed vulnerability:
-
Start the app locally per the target config: its
seed_commandandapp_commandare target-relative, so run them with the target directory as cwd (dnrcanary's app also resolves its own data paths, so the repo-root form happens to work there — don't rely on that for other targets). Seed first, then launch the app in the background with a single command that prints the PID, e.g.:python3 -c "import os,subprocess; p=subprocess.Popen(['python3','app/app.py'],cwd='targets/dnrcanary',env={**os.environ,'DNRCANARY_PORT':'5252'}); print(p.pid)"Poll
curl -s http://127.0.0.1:<port>/healthzuntil it answers. If the target's default port is busy, pick another via the documented override (dnrcanary:DNRCANARY_PORT, as above) and record the port you used. -
Fire the minimal PoC with
curl— the smallest request that demonstrates the flaw (one row of data, one foreign object), not a re-run of the attacker's full campaign. -
Record the exact command and the relevant slice of the response.
-
Also fire the negative PoC where one exists: the same technique against the route you verdicted not-vulnerable, to show it bounces.
-
Stop the app with the captured PID (
kill <pid>— never kill by port or pattern).
If the app cannot run in this environment, say so explicitly; affected
verdicts stay suspected and the report says what to run where.
Checkpoint phase4.json:
{"detonations": [{"vuln", "command", "status", "response_excerpt", "verdict"}]}
Phase 5 — Outputs
Write {RUN}/INCIDENTS.json:
{
"target": "<target name or logs dir>",
"incidents": [
{
"id": "INC-1",
"title": "<one line: what happened>",
"verdict": "confirmed_exploited | suspected",
"attacker_ips": ["..."],
"accounts_involved": ["..."],
"vuln": {"type": "...", "route": "...", "file": "...", "function": "..."},
"timeline": [{"ts": "...", "event": "..."}],
"impact": "<what data / how much / whose — every number from a query>",
"evidence": ["<log file>: <pipeline> → <result>", "..."],
"poc": {"command": "...", "verified": true}
}
],
"ruled_out": [
{"subject": "...", "verdict": "attempted_not_vulnerable | benign",
"reason": "<the defense, with file:function, or the benign explanation>"}
]
}
Routing rule: incidents holds what happened (confirmed_exploited) or
might have (suspected); anything verdicted attempted_not_vulnerable or
benign goes in ruled_out, never in incidents.
Write {RUN}/INCIDENT_REPORT.md: executive summary (3 sentences max), unified
campaign timeline across all incidents, one section per incident (evidence,
source confirmation, PoC transcript, impact), a "Ruled out" section, and
the full hypothesis ledger as an appendix — the ledger is the audit trail
that makes the hunt reviewable.
Then mark complete (checkpoint.py done {RUN}/.dnr-hunt-state 5) and tell the
user their next moves (substitute the literal {RUN} path):
/dnr-respond INC-<n>— scope, blast radius, and a proposed response plan for a specific incident; it picks up this same run dir automatically- on a dnr target:
python <target>/grade.py {RUN}/INCIDENTS.jsonto self-score (the user runs this) - from inside
{RUN}:/triage INCIDENTS.json --repo <repo>then/patch TRIAGE.json --repo <repo>— both skills write to the directory they're invoked from, so running them in the run dir keepsTRIAGE.jsonandPATCHES/beside the incidents, and patch consumes triage's true-positive verdicts rather than raw incidents; the vuln entries carryfile/function/type, which is what those skills ingest
Customizing beyond the demo
- Your own logs: point
$1at any directory of access/app/auth logs. Phase 0 adapts to what it finds — the method (baseline → ledger → source → detonation) is format-agnostic. Nginx/Apache combined logs, JSON-lines app logs, CDN/WAF exports, and auth logs all work; identify field positions in Phase 0 and adjust the awk pipelines. - EDR / endpoint telemetry: export threat events to CSV/JSON and hunt them the same way — the entities become process/host/user instead of IP/session/route, and "source confirmation" becomes configuration or binary review. Keep the verdict ladder: telemetry alone never confirms.
- Scale: for corpora where grep passes are too slow, load into DuckDB
(
python3 -c "import duckdb; ...") and run the same sweeps as SQL. - Detection engineering follow-on: every confirmed incident should end
as a detection rule proposal in
/dnr-respond's plan — the hunt that doesn't improve the alert queue will be re-run by hand forever.
FAQ
What does the dnr-hunt skill do?
Proactive threat hunt over web/application logs — no alert in hand. Profiles the corpus, runs a hypothesis-driven hunt loop with a mandatory written ledger, confirms suspects in source, detonates a local PoC, and writes INCIDENTS.json + INCIDENTREPORT.md. Use when asked to "hunt the logs", "find the campaign", "look for signs of compromise", or "run dnr-hunt". The no-alert entry to the detection & response track; /dnr-respond is the lead-in-hand entry.
How do I install dnr-hunt?
Run npx skills add https://github.com/anthropics/defending-code-reference-harness --skill dnr-hunt in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/dnr-hunt/ (personal) or .claude/skills/dnr-hunt/ (project).
Which AI agents can use dnr-hunt?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is dnr-hunt safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains dnr-hunt?
It is published by anthropics in anthropics/defending-code-reference-harness. It has 228 recorded installs.
More from anthropics/defending-code-reference-harness
threat-model
anthropics/defending-code-reference-harness
Build a threat model for a target codebase. Three modes: "interview" walks an application owner through the four-question framework and produces a threat model from their answers; "bootstrap" derives a threat model from the code plus past vulnerabilities (CVEs, git history, pentest reports) when no owner is available; "bootstrap-then-interview" chains the two when both owner and codebase are present. All write THREATMODEL.md in a shared schema. Use when asked to "threat model", "build a threat model", "map the attack surface", or "what should we be worried about in this codebase".
vuln-scan
anthropics/defending-code-reference-harness
Static source-code vulnerability scan. Reads a target directory (and THREATMODEL.md if present), spawns parallel review subagents per focus area, and writes VULN-FINDINGS.json + .md for /triage to consume. Read-only — no building, running, or network. For execution-verified crashes, use vuln-pipeline instead. Use when asked to "scan for vulns", "review this code for security issues", "find bugs in ", or as the step between /threat-model and /triage.
triage
anthropics/defending-code-reference-harness
Triage a batch of raw security findings. Verify each is real, collapse duplicates, re-rank by derived exploitability, and tag with an owner. Takes a directory or file of scanner output and writes TRIAGE.json + TRIAGE.md sorted by what actually needs engineering attention. Use when asked to "triage findings", "validate scanner output", "prioritize vulns", or "review the backlog". Runs interactively by default; pass --auto to skip the interview.
patch
anthropics/defending-code-reference-harness
Generate candidate fixes for verified security findings. Consumes TRIAGE.json (preferred), VULN-FINDINGS.json, INCIDENTS.json, or a vuln-pipeline results directory. Pipeline input is delegated to the execution-verified vuln-pipeline patch ladder; static-analysis input gets a per-finding patch subagent + independent reviewer and is written as inert diffs for human review. Writes PATCHES/bugNN/{patch.diff,patchresult.json}, PATCHES.md, and PATCHES.json. Use when asked to "fix the findings", "patch these vulns", "generate fixes", or "close the loop on triage".
customize
anthropics/defending-code-reference-harness
Adapt this C/C++ ASAN vulnerability pipeline to a different vulnerability class, target shape, language, or detection mechanism. Use when the user wants to port, migrate, retarget, customize, or fork the pipeline for something other than C/C++ memory-safety bugs — web apps, smart contracts, deserialization, ML systems, or any other domain.
quickstart
anthropics/defending-code-reference-harness
The front door for this repo. With no argument: a 30-second intro, then an offer to walk you through your first run on the canary target. With a question: answers it from this repo's own docs and source, cites where it looked, and hands you the next command. Use for "how do I…", "why does…", "where is…", "can this…", or just "/quickstart" to get oriented.
Popular security skills
azure-compliance
microsoft/azure-skills
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
minimal-run-and-audit
lllllllama/rigorpilot-skills
Rigor Run skill for README-first deep learning repo reproduction. Use when the task is specifically to capture or normalize evidence from the selected smoke test or documented inference or evaluation command and write standardized reprooutputs/ files, including patch notes when repository files changed. Do not use for training execution, initial repo intake, generic environment setup, paper lookup, target selection, hidden scientific-meaning changes, or end-to-end orchestration by itself.
seo-audit
coreyhaines31/marketingskills
When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO audit," "technical SEO," "why am I not ranking," "SEO issues," "on-page SEO," "meta tags review," "SEO health check," "my traffic dropped," "lost rankings," "not showing up in Google," "site isn't ranking," "Google update hit me," "page speed," "core web vitals," "crawl errors," "indexing issues," "stuck on page 2," "striking distance keywords," "title tag rewrite," "low CTR," "local SEO," "Google Business Profile," or "map pack." Use this even if the user just says something vague like "my SEO is bad" or "help with SEO" — start with an audit. For building pages at scale to target keywords, see programmatic-seo. For adding structured data, see schema. For AI search optimization, see ai-seo.
firebase-security-rules-auditor
firebase/agent-skills
Audits Firebase (Firestore, Cloud Storage) security rules for vulnerabilities, privilege escalation, role bypasses, create vs update inconsistencies, resource exhaustion, type safety, size limits, and hasOnly ownership checks. Use when auditing/reviewing rules, running red-team rule assessments, or scoring against auditor checklists. Don't use for Firebase CLI (login, deploy), Auth, Crashlytics, Remote Config, or database queries.
audit
pbakaus/impeccable
Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review.
competitors
coreyhaines31/marketingskills
When the user wants to create competitor comparison or alternative pages for SEO and buyer-facing use. Also use when the user mentions 'alternative page,' 'vs page,' 'competitor comparison,' 'comparison page,' '[Product] vs [Product],' '[Product] alternative,' 'competitive landing pages,' 'how do we compare to X,' 'competitor teardown,' 'audit our competitor pages,' 'are our comparison pages out of date,' or 'competitive asset audit.' Use this for any content that positions your product against competitors. Covers four formats: singular alternative, plural alternatives, you vs competitor, and competitor vs competitor. For auditing existing claims (not researching competitors from scratch, which is competitor-profiling; not technical SEO on these pages, which is seo-audit), use the asset audit here. For internal battle cards and sales-specific competitor docs, see sales-enablement.