What it does

This is a DFIR agent built around one non-negotiable rule: the machine's analysis must be reproducible, and the LLM must never be able to corrupt it.

The pipeline ingests a disk image (E01) and a memory image, then runs a 59-step deterministic execution graph that:

  • mounts the E01 read-only, hashes it (MD5/SHA), and verifies integrity;
  • runs Volatility 3 memory forensics first (processes, network, DLLs, injected regions, tokens, services);
  • extracts disk artifacts — MFT, USN journal, EVTX, registry, Prefetch/Amcache — into normalized "behavior" records;
  • projects everything onto a typed entity layer (PROCESS / FILE / IP / USER / HOST / SERVICE / SID) and a deterministic relationship layer;
  • writes one shared, content-addressed SQLite evidence graph (evidence_graph.sqlite) plus deterministic findings, a confidence worker, and a report handoff.

Every byte of that plane is deterministic: same inputs → byte-identical store and hashes, regardless of input ordering or host. That is the authoritative truth.

On top sits an advisory LLM lane — a small local model (llama3.2:1b) acting as an evidence collector. Given an investigation question, it incrementally retrieves evidence from the graph through six read-only tools and proposes candidate leads:

Question -> tool pick -> read-only EvidenceQuery -> SQLite graph
        -> collect clues -> Advisory JSON
        { narrative_answer, key_observations, candidate_edges, open_questions }

The LLM never decides guilt, never confirms a finding, never writes a byte of deterministic state. It surfaces leads and uncertainty; the deterministic pipeline remains the sole source of truth.

How we built it

  • Stack: Python 3 (stdlib-only runtime), SQLite, Volatility 3, The Sleuth Kit, libewf (ewfmount/ewfverify), Plaso/log2timeline, on SIFT Workstation; local LLM via llama.cpp / Ollama (OpenAI-compatible API).
  • Execution model: a contract-driven 59-step graph run by a custom executor with a content-hash registry that halts on any nondeterministic re-registration — the core of reproducibility.
  • Advisory agent (STEP E.2): a tool-calling loop over a custom read-only query surface (EvidenceQuery): get_entity, search_value, neighbors_of, process_context, evidence_refs, findings_for_entity. The model picks a tool name + scalar value — never SQL. Each turn's prompt is kept under ~500 tokens (largest measured turn ≈ 376) so a 1B model can pick one tool at a time and read a few short rows.
  • Two kinds of guardrails (clearly separated):
    • Architectural (cannot be prompted away): SQLite opened mode=ro + PRAGMA query_only=ON; parameterized queries; no writer methods; a depth-first hard-wall fence that rejects any advisory record carrying a deterministic-state key (finding_id/entity_id/relationship_id/ confidence/priority_score/…); a quarantined non-canonical, NOT-hashed advisory channel.
    • Prompt-based (best-effort): the "you are an evidence collector, not a judge" system prompt, hedging, type:value referencing.

Challenges we ran into

  • Small-model JSON reliability. llama3.2:1b emits code fences, prose, and — worst — truncated JSON when generation overran the server's 4096-token context window (visible as slot context shift ... truncated=1 in the llama.cpp logs). Fixed with tolerant JSON extraction (fence-strip + balanced brace-scan), a bounded retry, and a generation cap so prompt+output stays inside the window. A bad reply now degrades cleanly instead of crashing.
  • Keeping the model from poisoning itself. Retrieved rows carry forbidden keys (entity_id, confidence). Shown raw, a 1B model echoes them and trips the wall — so every row is rendered as a compact string and the model traverses purely by type:value strings (the tools resolve ids internally).
  • Determinism vs. a nondeterministic model. The narrative is fenced off the canonical plane entirely and stamped with provenance (model digest, prompt hash, rule sha).

What we learned

  • Reproducibility is an architecture, not a wish — a halt-on-drift registry catches what tests miss.
  • The right place for an LLM in forensics is above the evidence, behind a wall it physically cannot reach through.
  • Tool surfaces beat free-form SQL for small models: picking a tool name + scalar removes both the text-to-SQL failure mode and the injection surface.

What's next

  • Surface collector leads (labeled non-authoritative) in the report handoff.
  • A chain of small task-scoped collectors, one per investigation target.
  • Corroboration scoring: a model-proposed edge that independently matches a deterministic edge raises confidence by a bounded ≤0.15 — only via the deterministic confidence worker, never the model.

Link

GitHub Repository

YouTube

Built With

  • claude-code
  • ewf
  • json
  • libewf
  • llama-3.2-1b
  • llama.cpp
  • log2timeline
  • mcp
  • ollama
  • openai-compatible-api
  • plaso
  • python
  • sift-workstation
  • sqlite
  • the-sleuth-kit
  • volatility3
Share this project:

Updates