SAVVYDFIR-MCP
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
AI-driven DFIR framework: an MCP server that turns Claude Code into an autonomous digital-forensics analyst on SANS SIFT - correlates disk + memory evidence, enforces a deterministic evidence-provenance gate, and produces auditable, hash-chained investigation reports.
SAVVYDFIR-MCP
DFIR MCP server for SIFT Workstation that correlates disk and memory evidence, tracks provenance, and produces investigation reports.
Demonstration video: https://youtu.be/2tDT23DmE1I - a live start on the ROCBA case plus a walkthrough of a completed run, the deliverables (report.html / report.pdf / graph.html), and a self-correction event.
What It Does
SAVVYDFIR-MCP is a purpose-built MCP (Model Context Protocol) server that turns Claude Code into a DFIR investigation interface on SANS SIFT Workstation. It exposes 60+ typed forensic tools over stdio transport (65 at this writing - call describe_tool_catalog for the live count, don't hardcode it), supports cross-artifact correlation between disk and memory evidence via 10 anti-forensics detection checks, and keeps findings traceable through persisted artifacts, state, and a hash-chained audit log, with structured provenance (every CONFIRMED finding cites a resolvable execution_id; heuristics carry CTX-NNN references).
Design: autonomous-first. You point it at a case manifest.json and it
investigates with minimal interaction - the 7-phase workflow is enforced by hooks
and a coverage gate in code, not by a human driving each step. TheCONFIRMED / ACTIVE / REJECTED states are the agent's own evidence-graded output
lifecycle (a human reviews the final report.html + hash-chained audit trail); this
is not a manual, click-through investigator console. An interactive,
analyst-driven review surface (e.g. an Approve/Reject canvas like the one in the
decision flow below) is on the roadmap, not current scope.
Architecture
Evidence Files SIFT CLI Tools MCP Server
───────────── ────────────── ──────────
/evidence/disk/*.E01 ───> ewfmount, fls, mmls ───> ┌──────────────┐
/evidence/memory/*.zip ───> vol3, yara ───> │ savvydfir-mcp│
MFTECmd, EvtxECmd ───> │ (FastMCP) │
log2timeline.py ───> │ │
regripper, strings ───> │ SafeRunner │
│ AuditLogger │
│ StateManager│
└──────┬───────┘
│ stdio
│ JSON-RPC
v
┌──────────────┐
│ Claude Code │
│ Agent Loop │
│ │
│ Skill: │
│ .claude/ │
│ skills/ │
│ workflow │
└──────┬───────┘
│
v
┌──────────────┐
│ Output │
│ │
│ state.json │
│ audit.jsonl │
│ report.html │
│ graph.html │
└──────────────┘
Knowledge Layers & How to Extend
The forensic reasoning is not hard-coded in the engine - it lives in three editable knowledge
layers that feed the agent at different points. The engine (the "hands") runs tools and enforces
gates; these layers (the reference the "brain" reads) say what each artifact means and what it does
not prove.
| Layer | Where | Fed to the agent | Carries |
|---|---|---|---|
| 1. Case manifest | case-templates/manifest.json |
start_investigation() - the primary structured input |
Disk/memory paths + investigative_taxonomy (dispute_type, OS, keywords). dispute_type drives which extractors are required (file-centric disputes pull in the file-access bundle). |
| 2. Forensic-knowledge YAML | data/forensic-knowledge/artifacts/{windows,analysis_outputs}/*.yaml (19 files; 17 Windows OS artifacts + 2 analysis-tool outputs) |
Injected into mapped tool responses at interpretation time | forensic_caveat (what the artifact does NOT prove), corroborate_with (what to check next), discipline_reminder. |
| 3. Injected artifact heuristic KBs | 8 mapped .claude/agents/*-analyst.md files (mft, evtx, prefetch, amcache, registry, srum, sigma, memory) |
Bounded, CTX-cited slices injected as applicable_heuristics on mapped tools; deeper sections via get_heuristic(artifact, topic) (same 8 artifacts) |
Artifact-specific query + interpretation guidance for inline main-agent analysis. |
Layers 2 and 3 are two different injection paths: the YAML supplies the caveat/corroboration
envelope; the analyst .md supplies the applicable_heuristics slice. Unmapped tools (and
file-access-only tools) carry the envelope but no applicable_heuristics slice.
A note on the .claude/agents/ naming (it can mislead): these files live in Claude Code's
subagent directory, so they look like spawnable agents - but the 8 mapped *-analyst.md are not
spawned as subagents in the normal flow. They are heuristic knowledge bases: the relevant
CTX-cited slice is injected inline into the main agent's tool responses (applicable_heuristics)
and read as reference text. The directory name reflects their origin in the subagent concept;
functionally they are reference KBs, not separate agents. The only *-analyst.md ever invoked as
real Task subagents are the optional orchestration playbooks (synthesis / corroboration /timeline-analyst), and only when you explicitly opt in.
Not in the injection layer: four other *-analyst.md files remain in the repo but are not
sliced into tool responses - browser-analyst.md is currently unwired (browser extraction uses FK
YAML only; the post-tool hook routes browser analysis to registry-analyst), andsynthesis-analyst.md / corroboration-analyst.md / timeline-analyst.md are orchestration
playbooks for optional Task delegation or the optional build_timeline - not heuristic injection.
Confidence vs. CONFIRMED status - three separate mechanisms (often conflated; they are not the same):
- Base artifact weights (
semantics.py) - a per-source confidence multiplier: ShimCache 0.70,
Amcache 0.75, Registry-Run 0.85, Prefetch / EVTX 4688 / memory-process 1.00 (sigma-corroborated
capped at 1.0). - Execution validation hierarchy - at corroboration promotion the engine derives confidence from
source stacking: Observation 0.70 (ShimCache/Amcache alone) -> Probable 0.85 (Prefetch or BAM/DAM)
-> Definitive 1.00 (Prefetch + EVTX 4688 + MFT) -> Stacked 1.00 (3+ independent sources). - CONFIRMED status gate - a separate lifecycle check: a finding reaches CONFIRMED only with ≥2
corroborating references plus a resolvablesource_execution_idand a ruled-out alternative.
High confidence (1.00) is not the same as CONFIRMED status.
How to extend (capability ladder). The knowledge layers are the low-friction surface for
guidance; new capability (evidence acquisition, deterministic checks) lives in code:
| Change | Touches | Effect | Code? |
|---|---|---|---|
| Edit a mapped FK YAML | data/forensic-knowledge/ |
Refines caveat/corroboration guidance on an existing artifact | No (restart MCP server to pick up) |
| Edit one of the 8 mapped artifact KBs | .claude/agents/{mft,evtx,prefetch,amcache,registry,srum,sigma,memory}-analyst.md |
Deeper inline heuristics on mapped extractors (applicable_heuristics / get_heuristic) |
No (restart) |
| Add a new artifact's FK YAML | YAML + _FK_MAP entry + tool wrapper |
Enriches a newly mapped tool | Yes (registry entry - not drop-in) |
| Extend manifest taxonomy | case-templates/manifest.json |
Per-case coverage policy | No |
| Add a correlation check | sift_mcp/tools/correlation.py |
New deterministic cross-artifact reasoning | Yes |
| Add an extractor (MCP tool) | sift_mcp/server.py |
New evidence acquisition | Yes |
FK YAML loading is registry-driven (_FK_MAP), not directory auto-discovery: editing an
already-mapped YAML needs only a server restart, but a brand-new artifact needs a _FK_MAP entry and
an envelope hookup. An external knowledge pack at /opt/savvydfir-knowledge/... shadows the vendored
copy when present.
Investigation & Decision Flow
The agent does not free-associate over evidence - it runs a documented 7-phase
workflow (the sequence below is the intended order; the agent may reorder steps within a
case) where detection anchors seed hypotheses, hypotheses drive targeted
queries, and every conclusion must earn its confidence by stacking
independent sources through an evidence-provenance gate. Negative space (a missing
artifact) is treated as evidence, not silence.
flowchart TD
E["Evidence<br/>disk E01 + memory<br/>(or CyLR / KAPE / Velociraptor triage)"] --> ACQ["Phase 1 · Acquire<br/>mount_image (ewfmount) · or detect_triage_layout"]
subgraph P12["Phase 2 · Extract (per-artifact MCP tools -> durable CSV/JSON handle)"]
direction TB
ACQ --> MEM["Memory · Volatility 3<br/>list/scan_processes · scan_network<br/>detect_injection · list_dlls"]
ACQ --> DSK["Disk · EZ Tools + SleuthKit<br/>MFT / USN · EVTX · Prefetch · Amcache<br/>Registry / ShimCache · SRUM"]
ACQ --> FAB["File-access bundle<br/>ShellBags · LNK · JumpLists · browser<br/>RecentDocs · Recycle Bin · PowerShell hist · scheduled tasks"]
end
MEM --> INT["Interpretation envelope on each tool response<br/>(CSV/JSON handle + injected knowledge)"]
DSK --> INT
FAB --> INT
FKY[("forensic-knowledge YAML<br/>data/forensic-knowledge/artifacts/**<br/>forensic_caveat · corroborate_with · discipline_reminder")] -. "injected at interpretation (all mapped tools)" .-> INT
KBS[(".claude/agents · 8 analyst KBs<br/>applicable_heuristics slice · CTX-NNN provenance<br/>mft·evtx·prefetch·amcache·registry·srum·sigma·memory")] -. "sliced onto mapped tools only" .-> INT
INT --> RA["run_analysis (Pandas over the CSV)<br/>-> submit_finding (cites execution_id + CTX-NNN)"]
RA --> D["Phase 3 · Detect<br/>Sigma / Chainsaw (2,278 rules) · anti-forensics checks"]
D --> H["Phase 4 · Hypothesize<br/>detection anchors seed 2-5 ranked hypotheses"]
H --> P["Pivot loop<br/>targeted run_analysis over each artifact CSV<br/>(query the data, never load it into context)"]
P --> C["Phase 5 · Correlate<br/>temporal clusters + 10 correlation checks (6 core + 4 extended)"]
C --> S["Phase 6 · Synthesize<br/>stack 2+ independent sources"]
S --> G{"Evidence-Provenance Gate<br/>(enforced by code)"}
G -->|"execution_id resolves to a real audit row<br/>+ corroborated_by ≥ 2<br/>+ benign alternative ruled out"| CONF["CONFIRMED<br/>(2+ corroborating sources)"]
G -->|"single source / unresolved"| ACT["ACTIVE<br/>honest lead"]
G -->|"contradicted by other artifacts"| REJ["REJECTED"]
CONF --> R["Phase 7 · Report<br/>report.html · graph.html · trace.html"]
ACT --> R
REJ --> R
How a verdict is decided (the finding lifecycle). Every finding starts as anOBSERVATION / INFERENCE / HYPOTHESIS and is promoted to CONFIRMED only
when it clears three code-enforced invariants - not by the model's say-so:
- Provenance - its
execution_idmust resolve to a realaudit.jsonlrow
(no inherited claims, no placeholder IDs). - Corroboration - ≥ 2 independent artifact sources agree (1 source =
ACTIVE
lead, never confirmed; "stacking defeats anti-forensics"). - Alternative ruled out - the strongest benign explanation is recorded with a
specific observation that refutes it; unresolved alternatives force a downgrade.
These are the agent's autonomously-assigned, evidence-graded output states
(CONFIRMED / ACTIVE / REJECTED) - defensible, reviewable conclusions in the
final report, not raw detector noise. A human reviews the finished report + audit
trail; the agent is not driven click-by-click. The coverage gate blocks strict
report generation until configured coverage requirements are satisfied (e.g.generate_graph completion and conditional anti-forensics follow-up such as analyze_vss
when signals are present); a caller can pass allow_partial=true to bypass blocking
coverage checks (in the Ali run the report was then marked COMPLETE_WITH_GAPS).
disk↔memory contradictions emit self-correction events to the audit log. Full layer
breakdown + interfaces: docs/architecture.md.
Prerequisites
| Requirement | Detail |
|---|---|
| OS | Ubuntu 22.04+ x86-64. Primary tested: SANS SIFT Workstation 2024 (Ubuntu 24.04 LTS). Ubuntu 22.04 works but is not the primary test target. |
| CPU + RAM | 4 vCPU, 8 GB RAM minimum (16 GB recommended). The framework's Phase 2 disk extraction can spike to ~6 GB; 4 GB swap is required if you stay at 8 GB RAM. |
| Disk | 80 GB free minimum (evidence + Plaso super-timeline + Vol3 symbol cache + audit logs) |
| Shell | bash, sudo, git, curl, python3 (3.10+) - install.sh installs everything else automatically |
| Internet (install time) | needed for pipx install volatility3, the Chainsaw release binary, and the Sigma rules clone. Investigations themselves do not require internet beyond Anthropic Claude API access. |
Note on SIFT 2024: A clean SIFT Workstation 2024 install ships with EZ Tools, Sleuth Kit (fls/mmls/icat), Plaso, ewfmount, esedbexport, dotnet, and Python 3.12. It does not ship with Volatility 3, Chainsaw, or the Sigma rules corpus - install.sh installs all three.
Installation
One-line install (recommended):
git clone https://github.com/kismatkunwar89/SAVVYDFIR-MCP.git
cd SAVVYDFIR-MCP
bash install.sh
install.sh is idempotent - safe to re-run. It will:
- Verify Python 3.10+, pip, git are present.
- Install apt prerequisites:
python3-venv,tmux,libfuse2t64(orlibfuse2),libewf-dev,build-essential,pipx,curl,jq. - Install Volatility 3 via
pipx install volatility3(createsvolon PATH). - Install Chainsaw - downloads the latest pre-built binary from GitHub releases to
/usr/local/bin/chainsaw. - Clone Sigma rules to
/opt/sigma(the corpus Chainsaw runs against). - Install Claude Code via the native installer (
curl -fsSL https://claude.ai/install.sh | bash) if not already present. - Ensure
~/.local/binis on PATH (writes to~/.bashrconce). - Optional: install Protocol SIFT - skip with
SKIP_PROTOCOL_SIFT=1 bash install.shif you don't need the SANS framework. - Create venv at
./venv/and installrequirements.txt. - Deploy Claude Code global config (
CLAUDE.md+ skills) to~/.claude/.
Hooks, permissions, and MCP server registration live in the project-local.claude/settings.json
inside the repo - they take effect automatically when you launchclaudefrom the repo directory.install.shdoes not write a~/.claude/settings.json, so it cannot drift out of sync with the
Claude Code schema your installed version expects. - Create directories:
/cases/{analysis,exports,reports}and/evidence/{disk,memory}(with sudo) or~/cases+~/evidencefallback. - Verify the Python environment by importing fastmcp + pydantic.
After install completes:
# 1. Pick up new PATH (claude + vol + chainsaw + pipx-installed bins)
source ~/.bashrc
# 2. Authenticate Claude Code (browser flow - one-time)
# IMPORTANT: stay INSIDE the SAVVYDFIR-MCP directory so the project-local
# .claude/settings.json (hooks + MCP server + permissions) gets picked up.
cd SAVVYDFIR-MCP # if you aren't already here
claude
# 3. Activate venv for direct Python use (optional - MCP starts it automatically via .mcp.json)
source venv/bin/activate
# 4. Verify the framework imports
python -c "import sift_mcp.server; print('OK')"
venv vs
claude- what runs what (no confusion):claudeis a standalone binary
(~/.local/bin/claude); it does not live in the venv. Launched from the repo, it
auto-starts the MCP server declared in.mcp.jsonusing the projectvenv- so you do
not need tosource venv/bin/activateto run an investigation. Activate the venv only to run
Python directly (the import check above, or thescripts/). The one thing that matters:
launchclaudefrom inside the repo dir so.claude/settings.json(hooks) +.mcp.json
(MCP server) are picked up. The(venv)prefix on your prompt is harmless either way.
Settings are project-local:
.claude/settings.jsonlives in the repo (committed alongside the
code), so there is no global~/.claude/settings.json- launching from the repo dir is all you need.
For a production deployment to /opt/SAVVYDFIR-MCP/ (so any user on the box can run investigations), copy after the local install verifies:
sudo cp -r . /opt/SAVVYDFIR-MCP/
sudo chown -R $USER:$USER /opt/SAVVYDFIR-MCP/
Notes:
install.shuses the native Claude Code installer (Anthropic's recommended path; auto-updates).- For interactive use, run
claudeand complete the one-time browser login.
Usage
Working directory: the examples below use
/opt/SAVVYDFIR-MCP(the optional
production deploy from Installation step 11). If you only ranbash install.sh
in your clone, use your clone directory instead (e.g.cd ~/SAVVYDFIR-MCP).
Always launchclaudefrom inside the repo so the project-local.claude/settings.json(hooks + MCP server) is picked up.
Execution model
Investigations run autonomously. When you launch claude from the repo root, the project-local.claude/settings.json hooks load automatically and enforce the workflow - a PreToolUse gate
(.claude/hooks/workflow-enforce-pre.py) and a PostToolUse gate (workflow-enforce-post.py) that blockgenerate_report until the mandatory detectors have run. These hooks are always-on enforcement, not a
toggle: launching from outside the repo means settings.json isn't picked up and the coverage gate
is silently disabled - so always cd into the repo first. There is no per-tool approval/checkpoint
UI today - you review the finished report.html + hash-chained audit trail; an interactive
Approve/Reject review canvas is roadmap, not current scope.
Model used for evaluation. All validated eval runs were executed on Claude Sonnet 4.6
(the.claude/settings.jsondefault), not the flagship Opus tier. The accuracy numbers in this README
were therefore achieved on a mid-tier model - the framework's gates and correlation logic carry the
rigor, so results do not depend on running the most expensive model. Opus was never run; as a
frontier-tier model it should match or exceed these results, but that has not been measured here.Expect context compaction on larger runs. A full disk+memory investigation is long enough that
Claude Code will compact its context window at least twice on the bigger cases. This is by design
and safe: the framework persists everything to durablestate.json+ hash-chainedaudit.jsonl, so the
agent resumes from on-disk state (findings, lanes, executions) rather than in-context memory - a
compaction mid-run does not lose progress or break the coverage/provenance gates.
Run a single host - two equivalent styles
Both are autonomous and hook-enforced; choose by whether you want to watch the session.
Interactive (analyst-initiated, watch it run live):
cd SAVVYDFIR-MCP # or /opt/SAVVYDFIR-MCP if deployed to production
claude --dangerously-skip-permissions --allowedTools "mcp__savvydfir__*"
# then type at the prompt:
# Read case-templates/manifest.json and investigate fully following the 7-phase workflow.
Headless one-shot (unattended / scripted / CI):
cd SAVVYDFIR-MCP
claude --allowedTools "mcp__savvydfir__*" --dangerously-skip-permissions \
-p "Read case-templates/manifest.json and start the investigation. Investigate fully following the 7-phase workflow, run the mandatory tools detect_injection(case_id), compare_disk_and_memory(case_id), and sigma_hunt(case_id), then call generate_report(case_id) and generate_graph(case_id), and stop only after both report outputs are written."
Permissions:
--allowedTools "mcp__savvydfir__*"pre-allows the forensic tools (narrows tool
access).--dangerously-skip-permissionsskips all per-tool confirmation prompts - required for
unattended autonomous runs, but it bypasses every confirmation, so use it only inside a trusted,
isolated DFIR VM (the intended deployment).
Note -
sigma_huntis mandatory for a strict report, and it takes time.
It runs Chainsaw across the configured Windows Sigma corpus (~2,278 rules in the validated setup;
the exact count varies with an unpinned SigmaHQ clone). It is a hard-success requirement for
strict report generation - one successful run with durable Chainsaw JSON (exit_code=0,duration_seconds>0); a failed attempt does not satisfy the gate.sigma_scanis a separate
internal anomaly detector and does not count toward it. Runtime is volume-dependent: observed
runs ranged from a few seconds on small single-host EVTX to ~6 minutes on high-volume enterprise logs
(each directory attempt times out at 300s, then may retry prioritized channels). There is no casual
per-run switch to turn it off - to deliberately skip it, callgenerate_report(case_id, allow_partial=true), which produces a non-strict report markedCOMPLETE_WITH_GAPSand records the
omission indata_gaps. (SAVVYDFIR_SKIP_PHASE3_GATE=1only relaxes the ordering gate; it does
not waivesigma_huntcoverage.)
Claude calls MCP tools → accumulates findings → writes analysis/state.json + analysis/audit.jsonl → calls generate_report(case_id) and generate_graph(case_id).
Output: reports/{case_id}/report.html and reports/{case_id}/graph.html.
PDF export (optional). The report HTML is self-contained (no JS, no external assets), so any
Chromium-family browser can print it faithfully.scripts/render_report_pdf.sh <report.html> [out.pdf]
wraps headless Chromium to writereport.pdfalongside the HTML - there is no PDF dependency in
the MCP server itself.Run it as your normal user - do NOT use
sudo. On SIFT, Chromium is a snap; running it as root
fails (snap namespace +/run/user/0errors) and the PDF is not written. Pass thereport.htmlpath
as a single argument on one line (a wrapped line drops the arg and prints usage):./scripts/render_report_pdf.sh reports/<case_id>/report.html(If the script isn't executable after a fresh checkout:
chmod +x scripts/render_report_pdf.shor run
it asbash scripts/render_report_pdf.sh ....)
Multi-host Enterprise Investigation
Run each host as a separate Claude session with its own analysis directory:
# Per host - set SAVVYDFIR_ANALYSIS_DIR to isolate state
SAVVYDFIR_ANALYSIS_DIR=/opt/SAVVYDFIR-MCP/investigations/<host-case-id> \
claude --allowedTools "mcp__savvydfir__*" \
-p "Read case-templates/manifest.json and investigate."
# After all hosts - merge into unified cross-host graph
claude --allowedTools "mcp__savvydfir__*" \
-p "Call merge_host_graphs() then build_reports_index()."
Serve all reports:
cd /opt/SAVVYDFIR-MCP/reports && python3 -m http.server 8080
# Open: http://<server>:8080/index.html
Optional: agent-session trace alongside the report
report.html carries the forensic narrative. The framework also lets you
render the Claude Code agent's step-by-step session as a trace.html
companion using the open-source claude-code-log tool (pinned to v1.3.0 inrequirements.txt). The helper script applies a layered redaction pass -
operator filesystem paths, API/credential shapes, session UUIDs, operator-LAN
IPs - and writes the result into the same reports/<case_id>/ directory
served by the static HTTP server above. The link automatically appears inreport.html once the trace file exists.
Always pass --session-jsonl explicitly (safer than auto-discovery, which
picks the most-recent JSONL by mtime). --detail high is opt-in for internal
audit prep only - the default --detail low is the safer disclosure level
for any public submission. Mandatory eyeball pass in a browser before
publishing: redaction is best-effort.
# One-time: install the optional dependency
./venv/bin/pip install claude-code-log==1.3.0
# Render (default detail=low)
./venv/bin/python3 scripts/render_session_trace.py \
--case-id MY-CASE-001 \
--session-jsonl ~/.claude/projects/<hash>/<session-id>.jsonl
# Then re-run generate_report to surface the "View Agent Session Trace" link
# in report.html. The link is rendered only when trace.html actually exists.
Investigation graph (graph.html)
generate_graph(case_id) produces an interactive D3 graph atreports/<case_id>/graph.html (plus graph.json). The sidebar groups findings
into an Artifact Type tree (Memory / Event Logs / Filesystem / Execution
Artifacts / Registry / Network-SRUM), an Analysis Layers section (Rule
Detection - Sigma/Hayabusa/YARA, Correlation), and Node Roles toggles, with
an Evidence Kind legend. Click any bucket to filter; the View: chip + Visible: X / Y status + ↺ Show all reset track what's shown. The Case → Evidence Source → Finding lineage (the produced arrows) is visible by default.
Classification is data-driven and case-agnostic: findings bucket from theirartifact_type + artifact_subtype; when artifact_subtype is blank (legacy
findings), a tool_name fallback recovers it. Unknown artifact families degrade
to Uncategorized - the sidebar never breaks.
Both graph.html and graph.json are judge-facing artifacts, soinvestigation_graph.py runs a recursive infrastructure-path redaction pass
before writing either: operator install paths (/opt/SAVVYDFIR-MCP/,/home/<operator>/), and the /cases/, /evidence/, /mnt/ RBAC prefixes are
replaced with <install>/, <home>/, <case-dir>/, <evidence>/, <mount>/.
Forensic evidence (case emails, attacker IPs, registry paths, hostnames, finding
IDs) is preserved. No flags needed - it runs automatically on everygenerate_graph. Design notes + the maintenance contract (how to add a new
artifact family) are documented inline in the graph renderer module;
the bucket/redaction regression fixture is tests/fixtures/graph_bucket_synthetic/.
Validation Status
Validated blind end-to-end on five independent blind cases - 0 scored hallucinations across all (no reported finding asserted an artifact/event absent from the evidence, measured against ground truth; the investigation reads only manifest.json and never the answer key). The ground-truth keys are published under scripts/eval/ground_truth/ so anyone can re-score with gt_match_scorer.py. Full results in docs/accuracy-report.md; per-case artifacts in docs/agent-execution-logs/:
- ROCBA-2020-FREDS-LAPTOP - insider IP theft (Windows) - 90% recall, 107 findings, 3 CONFIRMED.
- LONEWOLF-2018-DESKTOP-PM6C56D - mass-shooting plot (Windows) - 91.7% recall, 88 findings, 2 CONFIRMED.
- NIST-DATALEAK-2015-PC - insider data leak (Windows, disk-only) - 60% recall, 503 findings, 4 CONFIRMED.
- ALI-WEBSERVER-WIN-L0ZZQ76PMUF - web-server breach (Win Server 2008) - 92.3% recall, 427 findings, 2 CONFIRMED.
- NIST-HACKINGCASE-2004-MREVIL - war-driving / credential theft (Win XP) - 86.7% recall, 304 findings, 3 CONFIRMED.
Reading the numbers - "findings" vs "CONFIRMED findings" are not the same thing.
A finding is anything the agent recorded - an observation or investigative lead. Most
findings are ACTIVE: single-source leads (e.g. "ShimCache shows this binary existed"), reported
as leads, not as facts. A CONFIRMED finding is the small, court-defensible subset that cleared
the evidence-provenance gate: a resolvableexecution_id(realaudit.jsonlrow) + ≥2 independent
corroborating sources + a ruled-out benign alternative. So "503 findings, 4 CONFIRMED" means the
agent surfaced 503 leads/observations and 4 of them were independently corroborated to the
defensible bar - not that 499 were wrong. (Recall is scored against ground truth over all
findings, not just CONFIRMED; CONFIRMED measures evidentiary strength, recall measures coverage.)
Each case is a different attack class and OS era (2004-2020); the framework adapted with no cross-case contamination.
Framework operational properties:
- Audit-backed completion for
sigma_hunt,compare_disk_and_memory,find_temporal_clusters,generate_report - Summary-first MCP responses for heavy disk tools (csv_path + run_analysis mediation)
- PreToolUse phase-transition gate blocks
sigma_hunt,hayabusa_hunt, andcompare_disk_and_memoryuntil the eight required Phase 2 disk tools have succeeded or recorded an explicit absence (override:SAVVYDFIR_SKIP_PHASE3_GATE=1; state-read errors fail open). Other detection tools (e.g.detect_injection) are not covered by this gate, so observed step ordering can still vary - Memory-hygiene mitigations (stdout/stderr drop post-audit, gc.collect after heavy tools) - validated under 7.6 GB RAM constraint with 4 GB swap
- Per-tool Vol3 timeout (malfind: 900 s, overrideable via
SAVVYDFIR_MALFIND_TIMEOUT) - Vol3
incompatible_profileclassification →tool_incompatibleoutputs_summary marker; coverage gate treats this as a legitimate gap (no zombie retries when the image's kernel build has no matching symbols) EvidenceFinding.timestamp_observedpopulated by detectors with artifact event-time in scope (MFT timestomping $SI_created, Sigma hitsystem_time);find_temporal_clustersprefers event-time over finding creation-time- IOC categorizer aligned with STIX 2.1 / MISP attribute types (IP, hostname, URL, hash, file path/name, account, email, registry) - drops tooling internals
COMPLETE_WITH_GAPSremains an expected investigation outcome when unresolved forensic discrepancies are documented (anti-forensics-induced gaps); it is not treated as a report-generation failure
Still pending broader end-to-end validation:
- Multi-host merge flow:
merge_host_graphs()andbuild_reports_index() - MCP-hosted graph serving via
serve_graph()as the primary operator path - Deferred optimization work from the original plan: parallel RECmd execution and wider timeout tuning
- Full live coverage of less-used artifact tools such as
analyze_vss,extract_pca,extract_shimcache,extract_srum,coverage_report, and YARA/timeline workflows
So the current repo is ready for single-host investigations and Batch 1-4 validation, with adjacent multi-host and less-used artifact workflows still marked as pending live validation rather than fully signed off.
Skills Reference
Claude Code skills provide on-demand forensic expertise. Skills auto-discover at startup (only name + description load). Full content loads when invoked.
| Slash Command | Skill | What It Does |
|---|---|---|
/memory-forensics |
Memory Forensics | Volatility 3 plugins: pslist, psscan, netscan, malfind, dlllist, hashdump |
/disk-forensics |
Disk Forensics | ewfmount, mmls, fls, icat - E01 mounting and filesystem analysis |
/ez-tools |
EZ Tools | MFTECmd, EvtxECmd, PECmd, AppCompatCacheParser, LECmd, JLECmd, SBECmd, regripper |
/timeline |
Timeline | log2timeline.py + psort.py - super timeline creation and filtering |
/yara |
YARA | Signature scanning on disk files and memory dumps |
Evidence Structure
/evidence/
disk/
<host>.E01 # Disk image (E01 or raw dd)
memory/
<host>-memory.zip # Memory dump (optional - disk-only is supported)
Evidence directories are READ-ONLY. By default output goes to analysis/ and reports/. Set SAVVYDFIR_ANALYSIS_DIR before launching Claude when you want per-host isolation.
Manifest Fields
The manifest is the single human input that drives the whole autonomous run. The analyst
fills case-templates/manifest.json; start_investigation reads
it (the Usage commands point Claude at this file). It is intentionally rich - theinvestigative_taxonomy block is load-bearing: dispute_type decides what the coverage gate
enforces. For file-centric disputes (intrusion_response / data_exfiltration / insider_threat /ransomware / financial_fraud / policy_violation) the gate makes the file-access extractors
required (ShellBags, LNK, Jump Lists, browser history, registry file-access, Recycle Bin,
PowerShell history, scheduled tasks) before a report can be
generated - so a wrong or blank dispute_type changes what the investigation must cover.
Triage layout detection (v1a - detect-only, experimental)
detect_triage_layout(path) recognizes raw mounts and extracted CyLR, KAPE, and
Velociraptor offline-collection layouts (including the real ntfs-only collector-tar
shape, where only the drive component is URL-encoded, e.g. %5C%5C.%5CC%3A) and reports
the usable volume root(s) and artifact paths. The drive letter is read from the
collection (e.g. CyLR's source drive may be G, not C) - never assumed.
It is read-only and detect-only: it does not import pre-parsed collector output,
skip the canonical extractors, or change report coverage requirements. You point the
existing disk extractors at the reported volume root yourself. Validated against a real
public Velociraptor collection (the hunt_lab DFIR-RansomHub sample), not just our own data.
Partial triage collections (honest current limitation). When a collector delivers
only a subset of artifacts, the standard extractors still run, but a tool that finds
nothing at the probed root returns artifact_absent - which means "not found at the
supplied root," not "absent on the victim" or "out of collector scope." Tools that
discover inputs but fail to parse (collection_failed/warning) can still block report
generation. Scope-aware triage coverage - an explicit manifestevidence_scope.mode = triage_partial that records un-collected artifacts as a
non-blocking collection_gap (distinct from victim-level absence) - is planned (v1b-a),
not yet implemented. Extraction-skip / audited triage import is a later increment (v1b-b).
{
"case_id": "VANKO-ZEBRAFISH-2016",
"mode": "blind",
"investigation_goal": "What the agent must determine (the case questions).",
"investigative_taxonomy": {
"side": "victim",
"dispute_type": "data_exfiltration",
"expected_attack_class": "data_exfil",
"os_in_scope": ["Windows 10"],
"initial_keywords": ["StarkResearch", "Zebrafish", "Level 5 Classified"]
},
"disk_images": [
{"path": "/evidence/disk/image.E01", "host": "wkstn-01", "image_type": "E01"}
],
"memory_dumps": [],
"known_iocs": [],
"max_iterations": 4,
"incident_date": "2016-06-30"
}
| Field | Required | Description |
|---|---|---|
case_id |
✅ | Unique case identifier (no spaces) |
investigation_goal |
✅ | What the agent should determine (the case questions) |
investigative_taxonomy.side |
✅ | victim / attacker / neutral |
investigative_taxonomy.dispute_type |
✅ | Load-bearing - drives the coverage gate (file-centric types require the file-access extractors) |
investigative_taxonomy.os_in_scope |
✅ | OS list, e.g. ["Windows 10"] |
disk_images |
◑ | {path, host, image_type}; EWF auto-spans .E02+ from .E01. At least one of disk/memory required |
memory_dumps |
◑ | {path, host}; [] = disk-only (the memory triage gate auto-relaxes) |
mode |
- | "blind" (no IOC hints) or "seeded" (IOCs provided to agent) |
investigative_taxonomy.expected_attack_class |
- | Best guess, or "unknown" |
investigative_taxonomy.initial_keywords |
- | Case leads to seed the hunt (names, hostnames, paths) |
known_iocs |
- | IOC array (returned to the agent only when mode="seeded") |
max_iterations |
- | Max triage iterations before forced completion (default 4) |
incident_date |
- | Annotates the timeline; never filters rows |
MCP Tools (65)
Representative subset below; call
describe_tool_catalogfor the complete live list - including the full file-access bundle,submit_finding/record_analysis_lane,hayabusa_hunt, anddetect_triage_layout.
| Namespace | Tools | Description |
|---|---|---|
| evidence | verify_integrity, get_provenance |
Hash verification and finding traceability |
| disk | extract_prefetch, get_amcache, extract_mft_timeline, list_deleted_files, summarize_evtx, extract_registry_run_keys |
Windows disk artifact analysis |
| memory | detect_profile, list_processes, scan_processes, scan_network, detect_injection, list_dlls |
Volatility 3 memory analysis |
| timeline | build_timeline, query_timeline |
Plaso super timeline - optional, not gate-enforced, not used in the validated single-host flow |
| yara | scan_files, scan_memory |
YARA signature scanning |
| correlation | compare_disk_and_memory, flag_discrepancy, find_temporal_clusters |
Cross-artifact correlation (10 anti-forensics checks) + temporal clustering for synthesis |
| state | read_state, get_finding, get_findings, export_trace, describe_tool_catalog |
Case state summary, retrieval, trace export, and catalog metadata |
| lifecycle | start_investigation, add_finding, coverage_report, generate_report |
Investigation lifecycle |
| mounting | mount_image, load_memory |
Evidence preparation |
| graph | generate_graph, serve_graph, merge_host_graphs, build_reports_index |
D3 investigation graph + multi-host unified view + reports dashboard |
| detection | sigma_hunt, query_sigma_results, sigma_scan, analyze_vss, extract_pca, extract_shimcache, extract_srum |
Sigma/Chainsaw detection, read-only Sigma result paging, VSS recovery, PCA, ShimCache, SRUM |
| analysis | run_analysis |
Targeted local Pandas analysis over CSV outputs |
Retrieval and Response Contracts
read_state(case_id)is the summary/resume surface. Use it for case status, counts, open questions, and the latest finding window.get_findings(case_id, ...)is the full finding-corpus retrieval surface. It supportsartifact_type,evidence_kind,finding_status,mitre_tactic,min_confidence,limit, andoffset.get_finding(case_id, finding_id)drills into a singleF-NNNrecord.extract_prefetch,get_amcache,extract_mft_timeline,summarize_evtx,extract_registry_run_keys, andgenerate_reportare summary-first by default. Passresponse_format="detailed"only when you truly need rawdataarrays.extract_prefetchseparates exact Prefetch-native execution history from.pffile metadata: uselast_run_timesfor run history, and treatpf_created_time/pf_modified_timeas.pffile timestamps.sigma_hunt(...)creates findings and persists full hunt output. Usequery_sigma_results(output_path=..., ...)for read-only filtering and paging over persisted Sigma JSON.- Summary mode preserves the operational fields agents need, including
execution_id,records_count,total_records,csv_path,cache_hit, andfindings_created.
Scoring (TP/FP/FN)
When evaluating against ground truth:
- True Positive (TP): Finding matches a known-bad artifact in ground truth
- False Positive (FP): Finding flagged as suspicious but is benign per ground truth
- False Negative (FN): Known-bad artifact in ground truth not detected by agent
The compare_disk_and_memory() correlation engine runs 10 anti-forensics checks - the 6 core checks below, plus 4 extended (USN-journal timestamp validation, ShimCache vs Amcache, EID 1102 log-clearing, SRUM exfiltration):
- Process in memory with no disk binary (fileless)
- Execution evidence for deleted binary (cleanup)
- VAD anomaly on legitimate process path (injection)
- Network connection with no disk artifact (fileless C2)
- Registry persistence key for missing binary (cleaned malware)
- SI vs FN timestamp mismatch (timestomping)
Self-Correction
When compare_disk_and_memory detects an evidence contradiction, it can emit a
correction event and downgrade the affected finding's confidence. The intended
lifecycle is:
- Log a
CORRECTION_EVENTtoaudit.jsonl - Downgrade the affected finding
- Run follow-up tools from the alert's
recommended_followup - Re-promote or reject based on new evidence
It is evidence-triggered - it fires when disk and memory contradict, not when the LLM
second-guesses itself. Honesty caveat from the captured traces: steps 1-2 were
observed in 3 of 8 runs (16 correction events total), where step 2 was a confidence
demotion (not necessarily a status change to HYPOTHESIS). Automated follow-up and
re-adjudication (steps 3-4) were not demonstrated in those traces (revised_finding_id
was always null) - treat them as designed-but-unproven.
Project Structure
SAVVYDFIR-MCP/
├── CLAUDE.md # Investigation guide (7-phase workflow + rules)
├── README.md # This file
├── THIRD_PARTY.md # Third-party tool / dataset attribution
├── LICENSE # MIT
├── .mcp.json # MCP server connection config
├── requirements.txt # Python dependencies
├── .claude/
│ ├── settings.json # Claude Code MCP + hook config
│ ├── hooks/ # session-start, pre/post coverage gates, stop verification
│ ├── agents/ # *-analyst.md: 8 injected heuristic KBs + opt-in specialists (see Knowledge Layers)
│ └── skills/ # investigation-workflow, artifact-routing, pivot-methodology, sigma-detection, tools-reference
├── case-templates/
│ └── manifest.json # Example case manifest
├── data/
│ └── forensic-knowledge/artifacts/
│ ├── windows/ # 17 per-artifact FK YAMLs (forensic_caveat / corroborate_with / discipline)
│ └── analysis_outputs/ # 2 analysis-tool-output FK YAMLs
├── sift_mcp/
│ ├── server.py # FastMCP entry point (65 tools registered)
│ ├── audit.py # JSONL audit logger (fail-closed)
│ ├── state.py # Case state manager
│ ├── reporting.py # Report builder + coverage/provenance gate
│ ├── analysis_debt.py # Extraction catalog + analysis-debt gate
│ ├── tool_catalog.py # Per-tool domain / result-kind metadata
│ ├── models/ # Pydantic data models
│ ├── tools/ # MCP tool impls (disk.py, correlation.py, _cache.py durable-reuse, _contracts.py)
│ └── runners/ # SafeRunner subprocess wrappers
├── scripts/
│ ├── run-case.sh # Investigation wrapper (investigate -> report -> trace)
│ ├── extract_heuristic_slice.py # Injects analyst-KB slices as applicable_heuristics
│ ├── render_session_trace.py # Renders trace.html from the session log
│ ├── render_report_pdf.sh # Renders report.pdf from report.html (headless Chromium)
│ ├── investigation_graph.py # Per-case D3 graph (generate_graph)
│ ├── merge_graphs.py # Cross-host IOC graph (merge_host_graphs)
│ ├── build_index.py # Reports index (build_reports_index)
│ └── eval/ # Baselines + published ground-truth keys + scorer
├── analysis/ # Default single-host working state (state.json, audit.jsonl)
├── investigations/ # Optional per-host state roots via SAVVYDFIR_ANALYSIS_DIR
├── reports/ # Investigation outputs (gitignored)
│ └── {case_id}/ # report.html · report.json · report.pdf · graph.html · graph.json · trace.html
└── docs/ # architecture, accuracy-report, eval-methodology, dataset docs
└── agent-execution-logs/ # Committed run artifacts: 5 blind cases + VANKO (report/graph/trace/audit)
Acknowledgements & Third-Party Attribution
SAVVYDFIR-MCP is an orchestration layer - it does not reimplement forensic
parsers; it drives best-in-class open-source DFIR tools (Sigma, Chainsaw,
Hayabusa, Eric Zimmerman's EZ Tools, Volatility 3, Plaso, The Sleuth Kit, YARA,
libyal) and adds cross-artifact correlation + an evidence-provenance gate on top.
One file is vendored verbatim - Chainsaw's official Sigma→EVTX mapping
(rules/chainsaw-sigma-mapping.yml, GPL-3.0, © WithSecure Labs).
Full credits, sources, and licenses for every third-party tool, vendored file,
Python dependency, test fixture, and evaluation dataset are inTHIRD_PARTY.md. ATT&CK® is a trademark of The MITRE Corporation.
License
MIT License - Copyright (c) 2026 Kismat Kunwar. See LICENSE.
The MIT license covers this project's own code only; vendored/invoked
third-party works retain their own licenses (see THIRD_PARTY.md).
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi