Inspiration

The name is half a joke and half a real fear: Timeline Longa, Vita Brevisthe timeline is long, life is short. Anyone who has rebuilt an incident from Windows artifacts knows the dread of watching the timeline grow and wondering whether you'll finish chasing it in this lifetime. We wanted to stay nimble — get to the answer fast — and the way to do that isn't to throw out what already works in favour of shiny AI, but to combine the best of both: the proven, deterministic speed of signatures (Sigma / Hayabusa / ATT&CK / LOLBAS) as the floor, and an LLM for exactly the things signatures can't do.

We also refused to let the new part run unsupervised. A human stays in the loop at every review gate, so the analyst defeats the adversary with the agent rather than trusting it blindly — which is also the safer way to do forensics. Within that guard rail, though, we built real scaffolding for the AI to show its strengths: deep-diving contradictions to work out why something doesn't add up, taking a signature hit and writing SQL to explore the surrounding logs, and — when metadata isn't enough — opening the actual file to confirm what it's looking at.

And that, in the end, is the point of all of it: by this agent's work — his, or hers — I am given back the time to enjoy art, which is what a short life is for.

What it does

TLVB is an autonomous Windows-forensics incident-response agent. Point it at evidence — a collector .zip, a disk image (E01 / raw / VMDK / VHD / VHDX), or a triage directory — and it runs end to end: parse → signature detection → anomaly detection → timeline reconstruction → DFIR report (attack chain, IOCs, MITRE ATT&CK map, open questions). It runs from a one-click Web UI or the command line. Concretely, TLVB:

  • backs every finding with a concrete event (audit_id + source_artifact), so a judge can trace any conclusion to the exact log record behind it;
  • recovers from its own failed queries mid-run and pivots when a query finds nothing — autonomy that fires on real data, not a scripted demo;
  • goes back to the raw disk image on demand to read a file's actual contents when metadata isn't enough to make the call;
  • labels each finding confirmed (deterministic signature match) vs inferred (LLM judgement), and flags data gaps so a miss is honest;
  • writes one timestamped execution log of everything it did, across all tiers.

On a blind WinRM-intrusion case (winrm_spray_case, a standalone Windows Server 2022 disk image) TLVB detected the full chain — single-account brute force → WinRM remote execution → recon → an attempted LSASS dump → WMI-subscription persistence — and, crucially, reported the credential theft as *failed* because Defender/AMSI blocked it, instead of over-claiming a breach. It also refused to read the lab's future-dated clock as attacker timestomping. (Self-assessment: eval/winrm_spray_accuracy.md.)

How we built it

The organising idea of the architecture is to split the tiers by when the LLM runs (full diagrams in docs/ARCHITECTURE.md):

  • Build time — Sigma / Hayabusa / MITRE ATT&CK (STIX) / LOLBAS / custom forensic rules are compiled once by an LLM into DuckDB SELECTs and cached (rules.duckdb, with content-hash / schema / model invalidation and cost guards). The built SQL is vendored as JSONL, so a fresh clone runs the signature tier with no rebuild and no API key.
  • Tier 0 — Parsers (Python wrappers over SIFT tools: EZ Tools, Hayabusa, Plaso, The Sleuth Kit) → normalised UnifiedEvent rows in cases.duckdb. Covers EVTX, registry, MFT/USN, prefetch, amcache, shimcache, shellbags, LNK/jumplists, SRUM, browser history, the Win10 timeline, and web-server access/error logs (IIS/W3C + Apache / nginx / Tomcat).
  • Tier 1A — Signature agent, runtime LLM = zero. Executes the cached SQL; a hit is a finding. Deterministic, reproducible, free, and structurally unable to call out.
  • Tier 1B — Skills-driven anomaly agent. The LLM reasons over the SQL results plus Tier 1A findings and may propose new queries, which are cached and grow across cases (the system gets cheaper and sharper the more it's used).
  • Tier 2 — Timeline Analysis agent. Clusters findings, reads the ±N-minute raw timeline, and runs hypothesis-driven active-search SQL — with the two autonomy loops below.
  • Tier 3 — Reporter. A SANS-DFIR / NIST SP 800-86-structured report in HTML / CSV / JSON (ja/en), with a chain-of-custody section and AI-use disclosure.

The agentic core — two autonomy loops you can watch:

  1. Self-correction. When an active-search query fails validation, errors at execution, or returns all-NULL columns, TLVB feeds the failure reason plus the cluster's real schema back to the LLM, gets a revised query, and re-runs it (--max-self-correct, default 2). When a query runs clean but returns 0 rows, the agent judges true-negative vs wrong-angle and, if wrong-angle, re-issues from a different artefact / field / hypothesis — an investigative pivot (--max-reframe, default 1) that fires naturally because 0-row results are common on real evidence. Every attempt is recorded (n / sql / outcome / error / hits), so the detect → revise / re-sequence → recover arc is auditable.
  2. On-demand evidence extraction. When the LLM judges a file's contents essential (is loot.txt actually staged credentials?), it lists them in a requested_files field; TLVB does a bounded follow-up pass that mounts the disk image read-only, copies out exactly those files, and re-runs the analysis with the contents in hand. The agent decides when to go back to the raw image, and that decision lands in the execution log like everything else.

Stack: Go 1.22 (agents, read-only MCP server, web UI, reporter), Python 3.11 (parsers), DuckDB, the Anthropic API + Claude (build-time rule compilation and Tier 1B/2 reasoning), and the Sigma / Hayabusa / MITRE ATT&CK STIX / LOLBAS corpora.

Challenges we ran into

  • Getting an LLM to compile detection rules into valid SQL — reliably. Translating the rule corpora (Sigma / Hayabusa / STIX / LOLBAS) into DuckDB SQL at build time started at a ~40 % failure rate. We added build-time SQL validation that rejects and regenerates any query that would break at runtime, cost/timeout guards with resume, and retries on transient CLI kills — bringing the failure rate down to ~8 % and getting the full buildable Sigma corpus compiled and cached.
  • The signature tier silently faking hits. An active-search query that "executed" but returned all-NULL columns (a wrong EVTX JSON field path) was quietly passing as a hit. We fixed the EVTX field extraction, made the runtime flag all-NULL projections instead of trusting them, and fed that signal into self-correction.
  • Hardening the guardrails against real bypasses. We wrote adversarial bypass tests against the SELECT-only / single-bind SQL validator — and they found a genuine hole: a mid-statement ; could smuggle a stacked statement past it. We now reject any bare semicolon, and the bypass suite runs in CI.
  • Making self-correction fire on real data, not in a staged demo. Early on, self-correction only triggered when we injected a synthetic fault — theatre. We promoted it to fire naturally: a clean query that returns 0 rows triggers an investigative reframe, which happens constantly on real evidence, and the labelled --reproduce-llm-fault switch reproduces the genuine most-common LLM mistake (treating an EventData field as a column) so the error→recovery arc is guaranteed on camera without faking anything.
  • Making the agent earn the right to read raw files. Reading file contents off a disk image is powerful and risky. On-demand extraction is gated behind an explicit requested_files request and a bounded second pass that mounts the image read-only — so "go look at the file" stays a deliberate, logged step rather than an open-ended escape hatch.
  • Teaching the report to say "it failed". The hardest accuracy problem isn't recall, it's restraint. On the blind winrm_spray_case, the attacker tried to dump LSASS but Defender/AMSI blocked it — and the tempting wrong answer is "credentials stolen". We label findings confirmed vs inferred, run a report-consistency gate, and added a completeness check that separates a real miss from an uncollected-data gap — so the agent reports the credential theft as blocked, not breached, and never reads the lab's future-dated clock as attacker timestomping.

Accomplishments that we're proud of

  • A signature tier with provably zero runtime LLM — cost-bounded, fully reproducible, and structurally unable to exfiltrate or mutate evidence.
  • Self-correction that fires on real data, demonstrated as natural reframe arcs on a 1.1 GB real triage image (no_evidence rows=0 → reframe → ok rows=51).
  • Safety enforced in code, not prompt — read-only MCP (no execute_shell), SELECT-only / single-bind SQL validator, DB opened access_mode=read_only — covered by 20+ bypass tests in CI.
  • Accuracy measured by restraint, not just recall: on the blind winrm_spray_case TLVB caught every must-detect step and passed all of the "must-not-over-claim" checks — reporting the LSASS dump as a blocked attempt, not a breach — while custom forensic rules carry negative-example regression tests with 0 false positives, all backed by confirmed vs inferred labels and a completeness check so a data gap is never reported as a detection.

What we learned

  • For forensics, reproducibility and provable safety matter as much as raw detection rate. An LLM with a path to mutate evidence is a non-starter — which is exactly why we moved the guardrails out of the prompt and into code.
  • Self-correction is only credible if it fires on real data. Injected faults read as theatre to a forensics audience; 0-row reframes happen on their own.
  • The "data gap vs detection miss" distinction is essential for an honest accuracy report — tlvb completeness makes it explicit.

What's next for TLVB

  • Breadth: memory (Volatility 3) and network (pcap / Zeek) analysis beyond the current Windows disk-artefact depth.
  • Provenance: persist the full derivation chain into each per-finding JSON.
  • Orchestration: expand the read-only MCP surface so an external agent can drive TLVB end to end.

Built With

  • altpf
  • and-any-mcp-servers
  • att&ck
  • claudecode
  • duckdb
  • go
  • hayabusa
  • mcp
  • multi-agent
  • python
  • sift-workstation
  • sigma
  • stix
Share this project:

Updates