A DFIR triage parent agent (dfir.triage) hosted on ASP.NET Core walks a
Windows artifact bundle (Registry, MFT, Evtx, Prefetch, BrowserHistory,
AmCache, ShimCache), delegates per-class parsing to seven child agents,
heuristically narrows down each class to a small set of suspicious-candidate
records, scores them with a local model, and merges everything into a
chronologically-ordered timeline plus a one-page IR brief PDF and a STIX
bundle of IOCs.
The case volume is mounted read-only at the docker-compose level; the parent
agent's lease pins fs.write to /reports/<case-id>/** so the agent cannot
modify evidence. The protocol enforces chain-of-custody — not the
trustworthiness of the parser code.
Showcases: ASP.NET Core hosting, per-class child agents, lease-scoped
filesystem access, artifact_ref for the timeline / PDF / STIX outputs,
deterministic per-case budgets, Spectre.Console TUI for live timeline
viewing.
cp .env.example .env
make up # runtime + client + ollama
# in another shell:
make submit # submits the synthetic case under samples/
make watch JOB=<id> # live timeline
make report JOB=<id> # PDF briefmake smokeOr directly:
dotnet test tests/SmokeTests/SmokeTests.csproj -p:ArcpVersion=1.0.0 --nologoThe smoke test builds the runtime, constructs a CaseManifest against the
synthetic case, runs all seven in-process child agents, merges the timeline,
renders timeline.json + a tiny PDF + a STIX bundle, and asserts the
suspicious chain is ordered and the persistence + C2 events are flagged.
| Variable | Default | Effect |
|---|---|---|
CASES_DIR |
/cases |
Read-only mount of analyst case bundles. |
REPORTS_DIR |
/reports |
Read-write mount where the parent writes outputs. |
CASE_BUDGET_USD |
2.00 |
Parent lease budget per case. |
PER_CLASS_BUDGET_USD |
0.30 |
Per-class child lease budget. |
SUSPICION_THRESHOLD |
0.60 |
Candidate-score threshold. |
PARSER_BIN_DIR |
/opt/dfir-parsers |
Mount point for an out-of-band Zimmerman parser bundle. |
PARSER_TIMEOUT_MS |
15000 |
Per-parser invocation timeout. |
OLLAMA_MODEL |
qwen2.5:1.5b-instruct |
See top-level README for alternatives. |
ARCP_SDK_VERSION |
latest |
Resolved via NuGet floating-version *. |
csharp/
├─ src/
│ ├─ Runtime/ ASP.NET Core host + agents + tools + reporting
│ │ ├─ Agents/ DfirTriage parent + per-class child agents
│ │ ├─ Tools/ Pure-C# parsers for each artifact class
│ │ ├─ Reporting/ Timeline merge, STIX bundle, PDF brief
│ │ ├─ Models/ CaseInput / CaseReport / TimelineEntry / etc.
│ │ └─ Stubs/ Replacements for SDK helpers not yet shipped
│ └─ Client/ Spectre.Console TUI — submit / timeline / report / watch
├─ tests/SmokeTests/ xUnit fast smoke (~300 ms, no docker, no Ollama)
└─ samples/
└─ synthetic-case-001/ Hand-crafted DFIR bundle: 7 artifact classes
The Zimmerman parser binaries (RECmd, MFTECmd, EvtxECmd, PECmd,
AppCompatCacheParser) are NOT bundled in the demo image — production
deployments would mount them under /opt/dfir-parsers. The shipped tools
read the JSON-Lines samples directly so the demo can run in CI.
make upbrings up ollama + runtime + client;/casesis mounted RO.make submitreturns a job id.make watch JOB=<id>shows seven child jobs delegating, with the phase tickingparsing_registry → parsing_mft → … → synthesizing → complete.- The merged timeline lands at
/reports/<case-id>/timeline.json, the brief atbrief.pdf, the STIX bundle atiocs.stix.json. - Resubmitting the same bundle returns the cached result via idempotency.