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:valuereferencing.
- Architectural (cannot be prompted away): SQLite opened
Challenges we ran into
- Small-model JSON reliability.
llama3.2:1bemits code fences, prose, and — worst — truncated JSON when generation overran the server's 4096-token context window (visible asslot context shift ... truncated=1in 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 bytype:valuestrings (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.
Log in or sign up for Devpost to join the conversation.