Skip to content

Latest commit

 

History

History

README.md

csharp — Windows DFIR triage agent

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.

Quickstart

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 brief

Local smoke test (no docker, no Ollama)

make smoke

Or directly:

dotnet test tests/SmokeTests/SmokeTests.csproj -p:ArcpVersion=1.0.0 --nologo

The 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.

Configure

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 *.

Project layout

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.

What "done" looks like

  1. make up brings up ollama + runtime + client; /cases is mounted RO.
  2. make submit returns a job id.
  3. make watch JOB=<id> shows seven child jobs delegating, with the phase ticking parsing_registry → parsing_mft → … → synthesizing → complete.
  4. The merged timeline lands at /reports/<case-id>/timeline.json, the brief at brief.pdf, the STIX bundle at iocs.stix.json.
  5. Resubmitting the same bundle returns the cached result via idempotency.