dnr-hunt
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesednr-hunt
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:
- = a dnr target directory (contains
$1withconfig.yaml, e.g.kind: dnr), or a bare directory of log files. Defaults totargets/dnrcanaryif it exists, else cwd.targets/dnrcanary - = source tree of the application that produced the logs. For a dnr target this defaults to its
--repodirectory. Without source, no finding can rise aboveapp/(see the hard rules).suspected - = ignore any checkpoint in the run dir's
--freshand mint a new run dir..dnr-hunt-state/
主动威胁狩猎:输入日志与源代码,输出事件记录。你将从无预警状态开始——核心问题是“这里是否存在异常内容?”。方法与发现结果同样重要:先建立基线,再遵循假设→查询→深挖的流程,每一步都需书面记录,所有可疑点必须在源代码中确认并通过触发验证后才能认定为真实事件。
调用方式:。
/dnr-hunt [target-or-logs-dir] [--repo path] [--fresh]参数说明:
- = dnr目标目录(包含
$1且config.yaml,例如kind: dnr),或单纯的日志文件目录。若targets/dnrcanary存在则默认使用该目录,否则使用当前工作目录。targets/dnrcanary - = 生成日志的应用源代码目录。对于dnr目标,默认使用其
--repo目录。若无源代码,任何发现都只能停留在app/(可疑)级别(详见硬性规则)。suspected - = 忽略运行目录
--fresh中的任何检查点,创建新的运行目录。.dnr-hunt-state/
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:
- requires all three: log evidence, the flaw identified in source (file + function), and a PoC you fired against a local instance in this session.
confirmed_exploited - = log evidence (with or without a source read), but no fired PoC.
suspected - = 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.
attempted_not_vulnerable
- Query, don't read. Log files are deliberately bigger than any
context window. Never a log file; never
Readone. Profile withcat/wc/du/head, then interrogate withtail/grep/rg/awk/sortpipelines (oruniqfor 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).python3 - Propose, never execute. No blocking, no account disabling, no
credential rotation, no config changes — not even on the demo app.
Response actions belong in 's plan, as proposals.
/dnr-respond - Local only. PoCs fire at against an app instance you started. Never send traffic to a remote host, no matter what the logs contain.
127.0.0.1 - 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
when you need owner-level joins).
seed_command
- 仅日志证据无法确认漏洞。日志仅能显示某行为被尝试以及应用的响应方式——无法证明代码存在漏洞。判定等级如下:
- (已确认被利用)需同时满足三点:日志证据、在源代码中定位到缺陷(文件+函数)、本次会话中在本地实例触发了PoC验证。
confirmed_exploited - (可疑)= 有日志证据(无论是否查看源代码),但未触发PoC验证。
suspected - (尝试攻击但未成功)= 观测到攻击流量,但源代码显示存在防御机制,且PoC验证(若触发)未成功。 现场团队曾遇到验证Agent仅通过读取可观测日志就“确认”发现的情况——这是最省事但不严谨的方式。PoC验证要么成功要么失败,无法含糊其辞。
attempted_not_vulnerable
- 查询而非读取。日志文件的大小远超上下文窗口。绝不要或
Read整个日志文件;先用cat/wc/du/head分析概况,再用tail/grep/rg/awk/sort管道(或uniq处理有状态的内容)进行查询。告警队列和错误日志通常较小——先检查大小;若少于约200行可完整读取(“绝不读取”规则针对的是多MB级的日志 corpus,而非此类小文件)。python3 - 仅提议,不执行。不得执行任何阻塞操作、禁用账户、轮换凭证或修改配置——即使是演示应用也不行。响应操作属于的规划范畴,仅作为提议提出。
/dnr-respond - 仅限本地。PoC验证仅针对你启动的本地应用实例,目标地址为。无论日志内容如何,绝不要向远程主机发送流量。
127.0.0.1 - 所有数据均来自查询结果。行数、请求数、时间跨度、受影响客户数——每一项都必须来自本次会话中运行的管道,并记录在台账中。绝不要凭记忆估算。针对日志的管道查询和针对应用确定性种子数据库的查询均有效(当需要所有者级联查询时,运行目标的)。
seed_command
Run directory and checkpointing (runs before Phase 0 and after every phase)
运行目录与检查点(Phase 0之前及每个Phase之后执行)
First action of every run — establish the run directory. Let be
the basename of the resolved target/logs path (, or by
default), and that resolved path. Bash:
TARGET$1targets/dnrcanarySCOPEpython3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-hunt-state --scope "SCOPE"Append to that command if is in .
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
(UTC, same format as the vuln-pipeline, so lexical
sort is chronological). That path is — every below means
substituting this literal path; double-quote it (and ) in Bash, since
paths may contain spaces. All outputs and state live under .
--fresh--fresh$ARGUMENTS--scoperesults/TARGET/<timestamp>/{RUN}{RUN}/...SCOPE{RUN}Phase state persists to so an interrupted hunt resumes
without re-running sweeps. All checkpoint I/O goes through
(atomic writes, JSON-validated).
Never use the Write tool for 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
with the Write tool, then:
{RUN}/.dnr-hunt-state/python3 .claude/skills/_lib/checkpoint.pyprogress.json{RUN}/.dnr-hunt-state/_chunk.tmppython3 .claude/skills/_lib/checkpoint.py save {RUN}/.dnr-hunt-state <N> <name> --from {RUN}/.dnr-hunt-state/_chunk.tmpStart of run:
python3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-hunt-state- or
status == "absent", or"complete"given → reset (--fresh) and start at Phase 0.checkpoint.py reset {RUN}/.dnr-hunt-state - with
status == "running"→ readphase_done == N…phase0.jsonin order, printphaseN.json, skip to Phase N+1.Resuming from checkpoint: Phase N complete
End of run: after writing both outputs,
—
the call is Phase 5's checkpoint; there is no .
python3 .claude/skills/_lib/checkpoint.py done {RUN}/.dnr-hunt-state 5donephase5.json{RUN}/.dnr-hunt-state/results/每次运行的首要操作——建立运行目录。设为解析后的目标/日志路径的基名(即,默认),为解析后的完整路径。Bash命令:
TARGET$1targets/dnrcanarySCOPEpython3 .claude/skills/_lib/checkpoint.py rundir results/TARGET --state .dnr-hunt-state --scope "SCOPE"若中包含,则在命令后追加该参数。会为运行目录添加标记,确保基名相同的两个目标不会共享运行目录。该命令会输出一个路径——当前狩猎可继续使用的最新运行目录(可能是本次狩猎中途创建的目录,或其他dnr技能为该目标创建的目录,以便整个调查过程集中在同一位置),或新生成的目录(UTC时间戳,与漏洞管道格式一致,便于按时间排序)。该路径即为——下文所有均指代此路径;在Bash中需用双引号括起(同理),因为路径可能包含空格。所有输出和状态均存储在目录下。
$ARGUMENTS--fresh--scoperesults/TARGET/<timestamp>/{RUN}{RUN}/...SCOPE{RUN}Phase状态会持久化到,以便中断的狩猎可从断点恢复,无需重新执行扫描。所有检查点I/O均通过完成(原子写入,JSON格式验证)。绝不要直接使用Write工具操作。绝不要通过 heredoc 或标准输入传递负载——日志衍生的字符串可能与heredoc分隔符冲突,导致Shell执行异常。请先使用Write工具将负载写入,再执行:
{RUN}/.dnr-hunt-state/python3 .claude/skills/_lib/checkpoint.pyprogress.json{RUN}/.dnr-hunt-state/_chunk.tmppython3 .claude/skills/_lib/checkpoint.py save {RUN}/.dnr-hunt-state <N> <name> --from {RUN}/.dnr-hunt-state/_chunk.tmp运行开始时:
python3 .claude/skills/_lib/checkpoint.py load {RUN}/.dnr-hunt-state- 若或
status == "absent",或指定了"complete"→ 重置状态(--fresh)并从Phase 0开始。checkpoint.py reset {RUN}/.dnr-hunt-state - 若且
status == "running"→ 依次读取phase_done == N……phase0.json,打印phaseN.json,跳过至Phase N+1。Resuming from checkpoint: Phase N complete
运行结束时: 在生成两个输出文件后,执行 ——调用是Phase 5的检查点;不存在文件。
python3 .claude/skills/_lib/checkpoint.py done {RUN}/.dnr-hunt-state 5donephase5.json{RUN}/.dnr-hunt-state/results/Phase 0 — Locate and verify inputs
Phase 0 — 定位并验证输入
- Resolve . If it contains
$1withconfig.yaml, read the config forkind: dnr,logs_dir,app_command, andseed_command. Otherwise treatportitself as the logs directory. The corpus is expected to exist already — the target README covers generating it.$1 - Inventory what you do have: for each file in the logs directory and any
committed alert queue — ,
du -h,wc -l,head -2. Identify each format (combined access log, app error log, JSONL alerts, …) and the field positions you'll use intail -2.awk - Resolve (default: the target's
--repo). List its files; do not read source yet — Phase 3 reads with hypotheses in hand, not before.app/
Checkpoint :
phase0.json{"target": ..., "logs_dir": ..., "repo": ..., "files": [{"path", "format", "lines", "bytes"}], "port": ...}- 解析。若该目录包含
$1且config.yaml,读取配置中的kind: dnr、logs_dir、app_command和seed_command。否则将port视为日志目录。日志corpus需已存在——目标README文档包含生成方法。$1 - 清点现有资源:对日志目录中的每个文件及已提交的告警队列执行、
du -h、wc -l、head -2操作。识别每个文件的格式(组合访问日志、应用错误日志、JSONL告警等),以及将在tail -2中使用的字段位置。awk - 解析参数(默认:目标的
--repo目录)。列出其文件;暂不读取源代码——Phase 3将在有假设的前提下读取源代码,而非提前读取。app/
检查点文件格式:
phase0.json{"target": ..., "logs_dir": ..., "repo": ..., "files": [{"path", "format", "lines", "bytes"}], "port": ...}Phase 1 — Profile the corpus (baseline before hypotheses)
Phase 1 — 分析日志corpus概况(建立假设前的基线)
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), and which routes take parameters./orders/N - 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": [...]}异常是相对于基线的偏差,因此需先建立基线——约十几个简单的管道命令,每个都是针对访问日志的单行命令:
- 时间窗口: 首个和最后一个时间戳;每日请求数。
- 状态码直方图: 按状态码统计请求数。记录所有非2xx/3xx类状态码。
- Top访问源: 按源IP统计请求数,取前20个,并与中位数对比——记录任何数量级远超平均值的IP。
- Top路由: 按路径统计请求数(归一化ID:→
/orders/123),并标记带参数的路由。/orders/N - User-agent清单: 按UA统计请求数;标记自动化UA(curl、python-requests、sqlmap、Go-http-client、zgrab)并记录它们访问的路由。
- 认证情况: 哪个字段承载已认证用户;每个账户的登录次数;从多个IP登录的账户;登录失败的源IP。
- 路由大小概况: 每个带参数路由的响应大小最小值/中位数/最大值——通常响应较小的路由出现大幅异常值是数据泄露的信号。
- 错误日志: 若文件较小则完整读取;按时间戳将每个条目与访问日志关联。
- 告警队列: 完整读取(文件较小)。对每个告警记录:规则、源、目标、严重程度。这些是狩猎的输入,而非发现结果——告警噪声和告警静默均为有效信息。
记录每个观测结果及生成该结果的精确管道命令。
检查点文件格式:
phase1.json{"window": ..., "status_hist": ..., "top_ips": [...], "routes": [...], "automation_uas": [...], "auth": ..., "size_profile": ..., "alerts_summary": ..., "oddities": [...]}Phase 2 — The hunt loop (hypothesis ledger REQUIRED)
Phase 2 — 狩猎循环(必须记录假设台账)
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: / / . A refuted hypothesis
stays in the ledger — ruling things out is half the job and feeds
in the output.
supportedrefutedneeds-pivotruled_outSeed 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
entry, written down with the evidence.
ruled_out - 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, comment markers (SELECT,--), boolean pairs (%23/AND 1=1). Decode what you find and reconstruct the sequence per source IP, in time order.AND 1=2 - Enumeration patterns. One session or IP accessing object ids
sequentially (,
/orders/1, …) at machine cadence. Compare with how legitimate sessions access the same route./orders/2 - 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 but merely confirms
benign behavior counts as no-new-entities — record it and route it to
). Note the stop in the ledger.
supportedruled_outCheckpoint after every round:
phase2.json{"ledger": [...], "suspects": [{"name", "entities": {"ips", "accounts", "routes", "sessions"}, "summary", "evidence": [...]}], "round": N}本阶段为循环流程,台账是核心要求:无假设不查询,无台账不记录假设。在工作内存中维护下表,并在每轮结束后保存检查点:
| # | 假设 | 查询 | 结果 | 判定 | 下一步深挖方向 |
|---|
判定结果:(支持)/ (推翻)/ (需深挖)。被推翻的假设需保留在台账中——排除可能性也是工作的一部分,并会纳入输出的字段。
supportedrefutedneeds-pivotruled_out初始扫描 —— 基于Phase 1的基线,在第一轮中运行以下所有扫描;根据命中结果生成后续假设:
- 服务器错误。哪些IP在哪些路由上触发了5xx错误,使用了哪些负载?与错误日志关联——应用错误中引用攻击者输入是探测行为的特征。
- 所有告警判定。对每个告警队列条目:其标记的活动是否成功?若告警触发于无害流量,则记录为条目,并附上证据。
ruled_out - Top访问源异常。对每个请求量远超基线的IP:它在执行什么操作?是扫描噪声、爬虫还是有明确意图的行为?
- 查询字符串中的注入语法。编码引号()、
%27/UNION、注释标记(SELECT、--)、布尔对(%23/AND 1=1)。解码发现的内容,并按源IP和时间顺序重建攻击序列。AND 1=2 - 枚举模式。单个会话或IP以机器频率顺序访问对象ID(、
/orders/1……)。与合法会话访问同一路由的方式对比。/orders/2 - 认证异常。从未见过的IP登录账户;失败后成功的登录序列;单个IP以一个账户认证但访问量异常。
- 大小异常。带参数路由的响应大小为该路由中位数的倍数——这些响应中包含什么内容?
- 低峰时段活动。对流量最低时段的活动进行详细检查;慢速攻击活动会隐藏在流量中,但在凌晨3点会格外显眼。
深挖原则: 当扫描命中时,后续假设需基于命中的实体展开——该IP的所有行为、该会话访问的所有内容、以相同方式访问同一路由的其他IP、数据流向等。攻击活动是链式的;确认一个环节意味着需向两个方向深挖(攻击者如何进入?下一步会去哪里?)。
停止条件: 连续两轮未发现新实体(假设被但仅确认良性行为也视为无新实体——记录并归入)。在台账中记录停止情况。
supportedruled_out每轮结束后保存检查点文件:
phase2.json{"ledger": [...], "suspects": [{"name", "entities": {"ips", "accounts", "routes", "sessions"}, "summary", "evidence": [...]}], "round": N}Phase 3 — Source confirmation
Phase 3 — 源代码确认
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
verdict instead of a false positive — and writing it down is mandatory, not optional.
attempted_not_vulnerable - 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": [...]}]}对每个涉及路由的可疑点,读取中的处理代码:
--repo- 找到路由的处理函数。追踪可疑输入从请求到 sink(查询、文件系统、认证决策)的流程。
- 若存在漏洞:精确描述缺陷——文件、函数、具体代码模式(例如用户输入拼接进SQL;认证与获取操作之间缺少所有权检查)。
- 若无漏洞:精确描述防御机制(参数化查询、范围查找)。这是将攻击流量判定为而非误报的关键——且必须书面记录,不得省略。
attempted_not_vulnerable - 在查看文件时:检查同级路由是否存在同类缺陷。日志仅能显示攻击者的攻击路径,无法显示所有漏洞位置。
检查点文件格式:
phase3.json{"confirmations": [{"route", "file", "function", "vulnerable": bool, "mechanism", "siblings_checked": [...]}]}Phase 4 — PoC detonation (gates confirmed_exploited
)
confirmed_exploitedPhase 4 — PoC验证(confirmed_exploited
的必要条件)
confirmed_exploitedFor each source-confirmed vulnerability:
-
Start the app locally per the target config: itsand
seed_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.:app_commandpython3 -c "import os,subprocess; p=subprocess.Popen(['python3','app/app.py'],cwd='targets/dnrcanary',env={**os.environ,'DNRCANARY_PORT':'5252'}); print(p.pid)"Polluntil it answers. If the target's default port is busy, pick another via the documented override (dnrcanary:curl -s http://127.0.0.1:<port>/healthz, as above) and record the port you used.DNRCANARY_PORT -
Fire the minimal PoC with— the smallest request that demonstrates the flaw (one row of data, one foreign object), not a re-run of the attacker's full campaign.
curl -
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 (— never kill by port or pattern).
kill <pid>
If the app cannot run in this environment, say so explicitly; affected
verdicts stay and the report says what to run where.
suspectedCheckpoint :
phase4.json{"detonations": [{"vuln", "command", "status", "response_excerpt", "verdict"}]}对每个经源代码确认的漏洞:
-
根据目标配置在本地启动应用:和
seed_command均为目标相对路径,需在目标目录下运行(dnrcanary应用会自行解析数据路径,因此在仓库根目录运行也能生效——但不要依赖此特性运行其他目标)。先执行seed命令,再在后台启动应用并打印PID,例如:app_commandpython3 -c "import os,subprocess; p=subprocess.Popen(['python3','app/app.py'],cwd='targets/dnrcanary',env={**os.environ,'DNRCANARY_PORT':'5252'}); print(p.pid)"轮询直到收到响应。若目标默认端口被占用,通过文档说明的方式修改端口(dnrcanary:curl -s http://127.0.0.1:<port>/healthz,如上例)并记录使用的端口。DNRCANARY_PORT -
使用触发最小化PoC验证——仅需能证明漏洞存在的最小请求(一行数据、一个外部对象),无需重现攻击者的完整攻击活动。
curl -
记录精确的命令及响应的相关片段。
-
若存在反向验证场景,也需触发反向PoC:对判定为无漏洞的路由使用相同技术,验证攻击失败。
-
使用捕获的PID停止应用(——绝不要通过端口或进程模式终止)。
kill <pid>
若应用无法在当前环境运行,请明确说明;相关判定将保持级别,报告中需说明在何处运行验证。
suspected检查点文件格式:
phase4.json{"detonations": [{"vuln", "command", "status", "response_excerpt", "verdict"}]}Phase 5 — Outputs
Phase 5 — 输出
Write :
{RUN}/INCIDENTS.jsonjson
{
"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: holds what happened () or
might have (); anything verdicted or
goes in , never in .
incidentsconfirmed_exploitedsuspectedattempted_not_vulnerablebenignruled_outincidentsWrite : 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.
{RUN}/INCIDENT_REPORT.mdThen mark complete () and tell the
user their next moves (substitute the literal path):
checkpoint.py done {RUN}/.dnr-hunt-state 5{RUN}- — scope, blast radius, and a proposed response plan for a specific incident; it picks up this same run dir automatically
/dnr-respond INC-<n> - on a dnr target: to self-score (the user runs this)
python <target>/grade.py {RUN}/INCIDENTS.json - from inside :
{RUN}then/triage INCIDENTS.json --repo <repo>— both skills write to the directory they're invoked from, so running them in the run dir keeps/patch TRIAGE.json --repo <repo>andTRIAGE.jsonbeside the incidents, and patch consumes triage's true-positive verdicts rather than raw incidents; the vuln entries carryPATCHES//file/function, which is what those skills ingesttype
生成:
{RUN}/INCIDENTS.jsonjson
{
"target": "<目标名称或日志目录>",
"incidents": [
{
"id": "INC-1",
"title": "<一行描述:发生了什么>",
"verdict": "confirmed_exploited | suspected",
"attacker_ips": ["..."],
"accounts_involved": ["..."],
"vuln": {"type": "...", "route": "...", "file": "...", "function": "..."},
"timeline": [{"ts": "...", "event": "..."}],
"impact": "<涉及数据/数量/影响范围——所有数据均来自查询>",
"evidence": ["<日志文件>: <管道命令> → <结果>", "..."],
"poc": {"command": "...", "verified": true}
}
],
"ruled_out": [
{"subject": "...", "verdict": "attempted_not_vulnerable | benign",
"reason": "<防御机制,含file:function,或良性解释>"}
]
}分类规则:字段记录已发生()或可能发生()的事件;任何被判定为或的内容均归入,不得放入。
incidentsconfirmed_exploitedsuspectedattempted_not_vulnerablebenignruled_outincidents生成:执行摘要(最多3句话)、所有事件的统一攻击时间线、每个事件的单独章节(证据、源代码确认、PoC验证记录、影响)、“已排除”章节,以及作为附录的完整假设台账——台账是使狩猎过程可审计的关键依据。
{RUN}/INCIDENT_REPORT.md然后标记任务完成()并告知用户后续操作(替换为实际路径):
checkpoint.py done {RUN}/.dnr-hunt-state 5{RUN}- —— 针对特定事件的范围、影响半径及响应方案建议;该命令会自动使用同一运行目录
/dnr-respond INC-<n> - 若为dnr目标:进行自我评分(由用户执行)
python <target>/grade.py {RUN}/INCIDENTS.json - 在目录内:
{RUN},然后/triage INCIDENTS.json --repo <repo>—— 这两个技能会写入调用时所在的目录,因此在运行目录内执行可将/patch TRIAGE.json --repo <repo>和TRIAGE.json与事件文件放在一起;补丁技能会使用分类技能的真实阳性判定结果,而非原始事件;漏洞条目包含PATCHES//file/function字段,为这些技能所需的输入内容。type
Customizing beyond the demo
扩展至演示以外的场景
- Your own logs: point at 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.
$1 - 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
() and run the same sweeps as SQL.
python3 -c "import duckdb; ..." - Detection engineering follow-on: every confirmed incident should end
as a detection rule proposal in 's plan — the hunt that doesn't improve the alert queue will be re-run by hand forever.
/dnr-respond
- 自定义日志: 将指向任何包含访问/应用/认证日志的目录。Phase 0会自动适配发现的内容——方法(基线→台账→源代码→验证)与格式无关。Nginx/Apache组合日志、JSON行格式应用日志、CDN/WAF导出日志及认证日志均适用;在Phase 0中识别字段位置并调整awk管道命令即可。
$1 - EDR / 终端遥测: 将威胁事件导出为CSV/JSON格式,按相同方式进行狩猎——实体变为进程/主机/用户,而非IP/会话/路由;“源代码确认”变为配置或二进制文件审查。需遵循判定等级:仅遥测数据无法确认事件。
- 大规模场景: 若grep扫描速度过慢,可将日志加载到DuckDB()并通过SQL执行相同扫描。
python3 -c "import duckdb; ..." - 检测工程后续: 每个已确认的事件最终应作为检测规则建议纳入的规划——无法改进告警队列的狩猎将永远需要手动重复执行。
/dnr-respond