Track a DFIR investigation as a replayable graph instead of a flat spreadsheet: every command or tool you run is logged in order, findings hang off the action that produced them, and follow-up actions hang off the finding that prompted them. Anyone with the same evidence can open your case and recreate the entire investigation, step by step.
Built to replace the classic FOR508 IR tracking spreadsheet — and it still exports spreadsheet-compatible CSVs of all six classic sheets.
Works solo from the CLI, or as a shared multi-user server with accounts, roles, per-case membership, and a chain-of-custody export bundle (see Collaboration & access control).
Zero runtime dependencies (Python ≥ 3.10 stdlib only):
pip install -e . # or: pipx install .Or run straight from the repo without installing: python3 -m vera ...
For cryptographically signed provenance (Ed25519 — see Collaboration),
install the optional extra: pip install "vera[provenance]". Without it, export
bundles are still hash-verified, just unsigned.
For the MCP server (vera mcp — see AI assistants), install
pip install "vera[mcp]".
vera init lab1.vera --name "FOR508 Lab 1" --investigator you
vera evidence add "WS01 memory dump" --kind memory --sha256 <hash>
# log what you ran (pipe output through vera to capture it)
vol.py -f ws01.mem windows.pstree | vera run "vol.py -f ws01.mem windows.pstree" --host WS01 --evidence E1
# record what it showed you (attaches to the last action by default)
vera f "rundll32 spawned by wmiprvse" -t malware --host WS01 \
--time "2026-07-01 14:22" --filename rundll32.exe --star
# drill down: log the follow-up prompted by that finding
vera run "vol.py -f ws01.mem windows.netscan" --host WS01 --from F1
vera f "beacon to 203.0.113.7:443" -t netindicator --address 203.0.113.7
# a GUI/tool step has no command line — log the procedure + a screenshot
vera manual "Opened NTUSER.DAT → CurrentVersion\Run" --tool "Registry Explorer" \
--host WS01 --from F1 --shot runkey.png
vera attach F1 proof.png --role exhibit --caption "the smoking gun"
vera log # the whole investigation tree, in order
vera serve # browse/annotate in your browser (paste screenshots with Ctrl+V)
vera export md # replayable report; also: csv (FOR508 sheets), jsonFor enterprise triage (e.g. amcache/shimcache across dozens of hosts), register the hosts once, group evidence into a collection, and stack a single finding across every host it touches:
vera host add --from hosts.txt --type workstation # register 40 hosts at once
vera host add DC01 --type "domain controller" --ip 10.0.0.10
vera collection add "Lab2 amcache+shimcache" --tool AmcacheParser --hosts "WS01,WS02,…"
vera collection expand C1 --kind triage # one evidence item per collection host
vera manual "Parsed all 40 exports in Timeline Explorer" --tool AmcacheParser --collection C1
# one finding, stacked across the hosts that show the indicator
vera f "svchost.exe anomalous path C:\temp" -t malware --hosts "WS03,WS07,WS11,WS22"
vera stack # cross-host findings, rarest first (least-frequency triage)
vera coverage # per-host rollup — which hosts has nobody examined yet?
vera host edit WS03 --status compromised # disposition: clean/suspicious/compromised
vera host show WS03 # everything affecting one hostEvidence and actions link to hosts the same way — --hosts on vera evidence add, vera run, and vera manual. Host references are resolved against the
registry (vera host add first); an unknown name is an error, not a new host.
The registry is the hub: everything ties back to it by reference, not by
retyping a name.
- Evidence (
E#) — the images/dumps/collections you work from, with hashes and the source host(s) they came from. The ground truth that makes replay meaningful. - Action / step (
A#) — one investigative step, numbered in execution order. Two kinds:- a command step: exact command line, captured output (hashed, capped at 256 KB), exit code;
- a manual step (
vera manual): a GUI/tool action with no command line — Timeline Explorer, Registry Explorer, an EDR console — recorded as the tool name plus a reproducible procedure, with a screenshot standing in for the output.
- Screenshots / attachments — pasted (Ctrl+V in the web UI), dropped,
uploaded, or attached from the CLI (
vera attach,--shot). They hang off any action, finding, or evidence item; each is SHA-256 hashed and stored inside the .vera file, so the case stays a single portable artifact. A screenshot's role is eitheroutput(a step's result) orexhibit(proof on a finding or evidence item). - Finding (
F#) — something an action showed you. Typed (malware,account,host,netindicator,hostindicator,lateral,filesystem,event,lead,note) with type-specific fields. Host-based indicators carry a stackable artifact name (e.g.CRYPTBASE.dll) and its full path as separate fields — the name is what you stack on, the path pins the location; paste a path and the name auto-fills from its basename. Findings also carry an optional event time (when it happened in the incident — drives the Timeline), optional file hashes (MD5 / SHA-1 / SHA-256, validated and lowercased;vera f … --hash-file evil.execomputes all three), and a star for key findings. - Host (
H#) — a system in the investigation, held in a registry with aliases (soWS03,ws03, andWS03.corpare one host). The registry is the hub: evidence links to its source host(s) (inherited from its collection), a step's hosts derive from the evidence it examines (hosts belong to evidence and collections, not individual steps), and a finding links to the host(s) it affects (inherited from its step, adjustable) — all by reference to the registry, never by retyping. A finding on 2+ hosts becomes a cross-host finding with a stack count — the same indicator on 30 hosts is one finding, not 30;vera stacklists them rarest-first for least-frequency-of-occurrence triage. Host links are optional (host-agnostic work needs none). Each host also carries a disposition (unknown/clean/suspicious/compromised) — set it as triage progresses and the compromised-hosts view derives itself instead of being maintained by hand. - Account (a registry, like hosts) — user/service accounts seen in the
case, held in their own registry with domain, SID, type, and a disposition.
Compromised Account and Lateral Movement findings auto-register the
account they name and link to it (merge-only, never overwriting), and any
finding can be tied to accounts explicitly via an associated-accounts picker
(
--accountsonvera f/vera edit). The web Accounts tab is an inline-editable grid like Hosts; each account's panel lists every finding naming it. Add suspects up front, or let findings populate it. - Lateral Movement (a finding type) — movement is directional, so it
carries a source host → destination host, the technique (WMI, PsExec,
RDP, SMB, …), and the account used; both endpoints also join the finding's
affected-host set.
vera f "…" -t lateral --source-host RD01 --dest-host WS01 --technique "explicit creds" --account svc-backup. - Follow-ups — any finding can carry a follow-up checklist (same
machinery as leads): things to chase before the finding is done — "pull
prefetch on WKSTN01", "check 4624s on the target". Each item is workable in
place (an Investigate → button logs a drill-down step + finding and links
it back), and the Leads tab shows a case-wide Open follow-ups queue.
vera followup add F70 "prefetch on WKSTN01";vera followuplists what's open. - Attribution — every action and finding records who logged it
(
created_by), shown as a👤 userbyline on its card and in the Markdown/ JSON exports; the per-case audit log records who made each later edit. Attribution is stored as the username string inside the case, so it survives export and displays with no user-database lookup. (CLI edits record no user — the CLI is unauthenticated; see Collaboration.) - Collection (
C#) — a batch/sweep (e.g. a 40-host artifact export) with its provenance (tool, operator, scope) and the hosts it covers. Evidence in a collection sources its hosts from the collection — that's where they're edited, and edits follow through to evidence (and steps) still tracking the collection's set, while deliberately narrowed items (e.g. per-host expansion) keep their own. Standalone evidence has its own host picker.vera collection expand C1creates one evidence item per covered host in a single step, skipping hosts that already have evidence in it. - Coverage —
vera coverage(and the web Coverage tab) rolls up, per host, the evidence, steps, and findings that reference it, plus which tools were used and when it was last examined. Hosts with no analysis logged are called out — the answer to "did we look at everything?". - Lead (a finding type) — a triage worklist, not an indicator: e.g. the
rows an LFO autoruns sweep surfaced. A lead carries a checklist of items
you work through, each markable
open/triaged/dismissedand linkable to the finding that resolved it (vera lead, or the web Leads tab, shows "N of M triaged"). Leads are deliberately kept out of the Artifacts and cross-host Stack views — only the concrete indicators you drill down to belong there. - Indicators vs. Observations — finding types are grouped into two kinds. Indicators are IOCs you correlate (Host/Network Indicators, Malware & Tools, Compromised Hosts/Accounts); Observations are context/scope you record but don't correlate (File / Directory, notes, timeline events). Only indicators feed the Artifacts stack. The two groups are labelled in the "Findings" menu.
- File / Directory (a finding type) — a file or folder an artifact touched, recorded for scope, not correlation (e.g. the contents of a malware staging directory). It's deliberately kept out of the Artifacts stack; flip a finding between this and Host Indicator just by changing its Type (they share fields, so it's lossless).
- Artifacts —
vera artifacts(and the web Artifacts tab) stacks host-based indicators by artifact name regardless of path: the same planted DLL name seen in several app directories across hosts collapses into one entry that still lists every distinct full path and host, most-spread first. The Host Indicators tab defaults to this grouping too (toggle to a flat table). - Evidence cascade — the evidence flows down a drill-down chain so you never
re-pick it: a finding inherits the evidence its action examined, and a
follow-up action inherits its finding's evidence in turn (explicit choice
always wins). So
E4 → F8 → A7 → F9 …all point back to the same evidence. - Drill-down —
vera run ... --from F3links a new action to the finding that prompted it. That chain is the investigation.
Three ways, most to least faithful:
vera run -x "cmd"— vera executes the command and records stdout/stderr and the exit code.cmd | tee /dev/tty | vera run "cmd"— you watch the output live, vera captures it from the pipe.vera run "cmd"— records the command only; paste highlights into findings.
Captured output is SHA-256 hashed so a replay can be verified against the original run.
vera serve opens the viewer (bound to 127.0.0.1 by default; sign-in required
once accounts exist — see Collaboration & access control) with:
- Investigation — the action→finding→action tree; add actions/findings and
edit anything. Add/edit/clone open in a modal dialog (Esc or click-away to
close) rather than shoving forms into the tree. Every action and finding
collapses in place (click its header, or Collapse all / Expand all) so a
large case stays scannable. Clone on any action or finding opens a new one
pre-filled from it — a step keeps its tool/command/evidence (output is a fresh
capture); a finding keeps its type, attrs, hashes, hosts, and detail — so you
can enter a batch of similar entries without re-typing. (
vera clone A6/vera clone F9from the CLI too.) Affected hosts show the same everywhere — a chip with the first few names, "(… and N more)", full list on hover; the ★ on any finding/lead toggles the key-finding flag (also a checkbox in the form). - Timeline — every finding with an event time, in incident order, with a
date-range filter (
From/To, date or timestamp) that lives in the URL so a filtered view is shareable. - Stack — cross-host findings, rarest first (least-frequency triage)
- Hosts — an inline-editable registry grid: click any cell, tab between fields, changes autosave as you go. The blank row at the bottom adds a host (paste a newline/comma list to add many at once); ✕ removes one. Per-host finding counts click through to what affects each host. The Status column color-codes each row by disposition.
- Accounts — the account registry as an inline-editable grid like Hosts; each account's finding count opens a panel of every finding naming it.
- Coverage — the hosts × analysis matrix: evidence/step/finding counts, per-tool step counts, and last-examined time for every host, with unexamined hosts highlighted.
- Artifacts — host-based indicators stacked by artifact name regardless of path; the Host Indicators tab groups by name by default (toggle to flat)
- Leads — triage worklists (e.g. an LFO sweep): add/check off items, link each to the finding that resolved it, track "N of M triaged"
- Category tabs — Compromised Hosts / Accounts, Malware & Tools, Network / Host Indicators, Lateral Movement, generated automatically from finding types
- Evidence — items and hashes, plus collections/batches
- header actions — Export .md (any signed-in user), Bundle (download a chain-of-custody bundle; lead/admin), Members (the case roster; lead/admin edit it), and Admin (user management + access log; admins only)
Findings carry an affected-hosts tag control; a 🖥 N hosts chip on any
cross-host finding jumps to the registry.
Every tab is a real URL (/investigation, /timeline, /evidence,
/accounts, /findings/<type>, …), served so a refresh or a shared link lands
on the right view. A ref jump is a deep link too: /investigation?F=13 or
?A=24 opens the tree expanded to that node — click a finding's ref anywhere
(Timeline, a category sheet) and copy the URL straight to a teammate.
vera mcp serves the active case over the
Model Context Protocol — an open standard, so
any MCP-capable client can drive an investigation with you: query the tree,
stacks, timeline, and coverage; log actions with their captured output; record
findings; manage the registries and worklists. It needs the optional extra:
pip install "vera[mcp]".
Register it with the standard mcpServers config (the same JSON shape works in
Claude Code, Cursor, Zed, VS Code, and most other clients):
{
"mcpServers": {
"vera": {
"command": "vera",
"args": ["mcp", "--case", "/path/to/inv.vera"]
}
}
}Omit --case to serve whatever vera use / $VERA_CASE selects at launch;
either way the case is fixed for the life of the session — a later
vera use never redirects a running assistant.
To get the web dashboard alongside the assistant, point the client's
command at start.sh (repo root) instead — it starts vera serve in the
background (or reuses one already on the port), runs the MCP server on stdio,
and stops the dashboard when the session ends:
{
"mcpServers": {
"vera": {
"command": "/path/to/vera/start.sh",
"args": ["--case", "/path/to/inv.vera", "--no-browser"]
}
}
}It takes --case, --port (dashboard, default 8845), --actor, and
--no-browser, and also works standalone in a terminal (Ctrl+C stops both).
The tool surface is read + write, minus anything administrative: no case
creation, membership, adoption, exports, or deletes — those stay in the CLI and
web UI. Every write is attributed: created_by and the per-case audit log
record mcp:<username> (override with --actor), so AI-entered records are
always distinguishable from your own.
Trust model: same as the CLI. The server speaks stdio to a local client
process — no network listener, no auth layer — and can do exactly what anyone
who could launch vera on that machine could already do.
vera serve can be a shared, multi-user server. Accounts, roles, and case
membership gate the web UI; the CLI is unchanged and unauthenticated (it
edits case files directly — see the threat-model note below).
- First run — with no users yet, the server shows a one-time screen to create the initial admin. From then on, signing in is required.
- Roles (global, per user):
- admin — everything an investigator can do, plus user management and full access to every case (an implicit member everywhere);
- investigator — logs actions/findings, but only on cases they're a member of;
- viewer — read-only across all cases (sees investigations and reports, changes nothing).
- Case membership & lead investigator — each case has exactly one lead (the creator becomes lead) plus any number of member investigators. The lead (or an admin) manages the roster from the Members panel. A global investigator can only modify a case once they've been added to it.
- Passwords — salted and hashed with
scrypt(stdlib); minimum 12 chars with 3 of 4 character classes, or 16+ of anything (passphrase-friendly), with a common-password blocklist. Sign-in has failed-attempt lockout. Users change their own password from the header; an admin can issue a one-time reset code for a forgotten one. - Admin pages (admins only) — a Users view (create/disable, change roles, issue reset codes; the last active admin can't be locked out) and an Access log view.
- Two audit trails — each case carries its own append-only edit log
inside the
.vera(who changed what, part of chain of custody, ships with the case). Separately, a global access log (vera-audit.db) records server-wide security events — sign-ins, user administration, and every case export — and is never part of a case export.
Every record stores which system wrote it (created_by_origin), not just
which user — so a case can move between servers without its attribution
blurring, even if usernames collide.
- Server identity — on first run a server gets an identity. With the
provenanceextra that's an Ed25519 keypair; itsserver_idis the public key's fingerprint, so the id is unforgeable (you can't claim it without the private key, which never leaves the server — a0600vera-server-key.pem). Give the server a human label at bootstrap. - Signed bundles —
vera export bundle(web Download / lead-admin) signs the manifest; anyone canvera verifyit offline to prove both integrity and origin, with no ability to forge. Without the extra, bundles are hash-verified but unsigned (the receipt says so). - Origin badge — a record made on a different server than the case now
lives on is badged
👤 user · from <server>in the UI, with the originatingserver_idon hover. Same username, different origin = unmistakably distinct. - Import & adopt —
vera import <bundle>.zipverifies and extracts the.vera. An imported case's home server isn't yours, so its membership is inert (no investigator inherits access by a name match) until an admin adopts it (an Adopt to this server banner, orPOST /api/adopt): that stamps the case's home to your server and resets the roster (the adopter becomes lead). Attribution and history are never rewritten — only the access-granting roster resets.
Threat model. This is web-tier access control: it governs who can do what
through the browser. Anyone with filesystem access to a .vera file, or the
CLI on the server box, has full access — that's inherent to the portable
single-file design, and the CLI is intentionally auth-free. Team deployments
should keep case files on the server, have collaborators come in via the web UI,
and terminate TLS at a reverse proxy (passwords over plaintext LAN HTTP
would undermine the point). Sessions are HttpOnly cookies with a CSRF header
check on writes.
"Deleting" in vera is a soft-delete: the row gets a deleted_at timestamp and
is hidden from views, exports, and counts, but the data is never removed from
the case file. This holds for hosts and screenshots/attachments today, and any
future delete follows the same rule — a case file remains a complete record of
everything that was ever entered.
vera export md— full replayable report: hosts + collections, evidence + hashes, every action in order with commands and captured output, nested findings, timeline, a cross-host-indicator appendix (rarest first), an artifacts-by-name appendix (host indicators stacked by name across paths), and the classic category appendices.vera export csv— one CSV per classic IR-spreadsheet sheet (same column headers), plusHosts.csv,CompromisedHosts.csv(derived from host disposition), andCrossHostFindings.csv.vera export json— complete structured dump (hosts, collections, findings with their affected-host sets, and attachment manifest).vera export bundle— a chain-of-custody bundle (<case>-<date>.bundle.zip): the.veraplus the renderedmd/csv/jsonreports, zipped, SHA-256 hashed, and zipped again with aMANIFEST.json+ human-readableRECEIPT.txtso the package is tamper-evident.--include-evidence DIRalso packs raw evidence files whose recorded hash matches (verifying each; off by default — otherwise evidence is referenced by hash). In the web UI it's the Bundle button (lead/admin). Every export is logged to the case's export ledger.vera verify <bundle>.zip— recompute every hash against the manifest; reports intact (green) or tampered (red, naming the offending file). With theprovenanceextra it also checks the signature and reports which server sealed it. Needs no case, no accounts, no network — verification is fully offline.vera import <bundle>.zip— verify a bundle and extract its.vera(refuses a tampered bundle). See moving cases between servers below.
Markdown and JSON exports include who logged each action/finding.
Commands use, in order of precedence: --case PATH, $VERA_CASE, or the
case selected with vera use PATH (stored in ~/.config/vera/active).
Everything is plain SQLite on disk — no database server to run.
<name>.vera— the case itself: one self-contained SQLite file holding the whole investigation (actions, findings, evidence, hosts, accounts, screenshots, audit log, and — once you have collaborators — the case's member list). This is the file you share: copy it, zip it, hand it to a teammate; its SHA-256 is its chain of custody. It's created wherever you pointvera init <path>.vera. In the browser,vera servelists and creates cases in its case directory — the--diryou pass, else the active case's folder, else the current working directory.vera-users.db— the global accounts/sessions database (users, roles, salted+hashed passwords, login sessions, reset tokens). Created in the case directory the first time youvera servewith users. It is not part of any case and spans all of them — do not share it or commit it; it holds password hashes and is specific to one server install. (git-ignored by default.)vera-audit.db— the global access/security log (sign-ins, user administration, and every case export), besidevera-users.dbin the case directory. Also global, server-private, and never part of a case export — don't share or commit it. View it under Admin → Access log. (git-ignored.) This is distinct from each case's ownaudit_log(the data-edit history that lives inside the.veraand ships with it).vera-server-key.pem— the server's Ed25519 private key (only with theprovenanceextra), besidevera-users.db,0600. It signs export bundles and is this server's provenance identity. Never share or commit it — losing it means you can no longer sign as this server; leaking it lets someone forge your seal. (git-ignored.)<name>.vera-wal/<name>.vera-shm— SQLite write-ahead-log sidecars, created while a case is open so multiple people can read during a write. Transient; they fold back into the.verafile on a clean close. No need to copy them when sharing.<name>.vera.pre-v<N>— an automatic backup vera takes before a schema migration upgrades an older case, so a bad upgrade can never eat the only copy. Safe to archive or delete once you've confirmed the upgraded case opens.
So to hand off an investigation, share only the .vera file (or a
vera export bundle of it). To move a whole team server, copy the case
directory including vera-users.db and vera-audit.db (keep them private —
they're your credential store and security log).
pip install -e ".[dev]"
pytest