Phase 1 complete ·  Phase 2 proposed · FYP Mid-Review · v1.0

LLM-Powered Automated
Malware Analysis Framework.

Fathom is a retrieval-augmented malware analysis pipeline that ingests raw sandbox reports (CAPEv2) and produces structured analyst output - MITRE ATT&CK mappings, IOC tables, behavioural narratives, and executive summaries. The current deployment serves Mixtral-8x7B-Instruct-v0.1 through vLLM with LoRA support on a single AMD MI300X VF with a 32,768-token context window; Azure enrichment remains conditional, with Kimi primary and GPT fallback when needed. Phase 2 still proposes a targeted retraining on Foundation-Sec-8B-Instruct.

Explore architecture Full project map Future work
Project Team
MH
Muhammad Haseeb
22I-1698 · Team Lead · Backend
AH
Abdul Hadi
22I-1646 · Team Member
MA
Muhammad Ammar
22I-1626 · Team Member
Dr. Sana Aurangzeb
Supervisor · FAST-NUCES Islamabad
16 APR 2026 FAST NUCES ISB
Knowledge
88.0%
CyberMetric-500
440 / 500 correct
Operational
0.841
Parent-Tech F1 · Real CAPE
Emotet · Formbook · Dridex
Runtime
32K
Context Window
vLLM + LoRA serving
Dataset
332K
Training Rows
28 sources · Alpaca JSONL
01  /  Executive Summary

What Phase 1 delivered, and why Phase 2 is needed.

Phase 1 · Feb–Apr 2026
Working end-to-end pipeline delivered

The current stack runs Mixtral-8x7B-Instruct-v0.1 behind a vLLM OpenAI-compatible endpoint with LoRA support, a FAISS-indexed MITRE ATT&CK retrieval layer, a Neo4j knowledge graph, and conditional Azure enrichment. Kimi is still the primary external model, with GPT fallback enabled when Kimi is rate-limited or unavailable. Evaluated in a twelve-run benchmark campaign from 2-4 April 2026.

Delivered 12 runs 2–4 Apr 2026
Phase 2 · Proposed
Retrain on Foundation-Sec-8B-Instruct

Cisco’s Foundation-Sec-8B-Instruct is a Llama-3.1-8B variant continually-pretrained on 5.1 B cybersecurity tokens and RLHF-aligned. The Phase 2 design preserves the entire Phase 1 architecture and swaps only the base weights and chat template — targeting the capability and safety gaps identified in benchmarking.

1x MI300X VF USD 75–100
02  /  System Architecture

Thirteen-stage pipeline, three LLM inference phases.

Deterministic extraction and retrieval frame three LLM phases: a Fathom first-pass (local vLLM-served Mixtral + LoRA), a conditional Azure enrichment swarm, and an Azure synthesis stage with GPT fallback if Kimi is unavailable. The full step-by-step pipeline is on the Project Map.

01
Auth
02
Extract
03
Store
04
Cache
05
Route
06
Adapter
07
Prompt
08
Sanitise
09
Fathom
10
Swarm
11
Synth
12
Validate
13
SSE
Deterministic (no LLM)
Fathom local inference
Azure enrichment / synthesis
Persistence & delivery
Base model
Mixtral-8×7B-Instruct-v0.1
Active params
13B of 46.7B (2 of 8 experts / token)
Fine-tune
QLoRA 4-bit · rank 32 (64 for unified) · α 64 · dropout 0.05
Serving
vLLM + LoRA modules via systemd on the host
Hardware
1x AMD MI300X VF, 205.8 GB VRAM, ROCm 7.0, full bf16
Context
32,768 tokens
vLLM config
gpu-memory-utilization 0.90, max-num-seqs 2, max-lora-rank 32
Hugging Face repo
umer07/fathom-mixtral
Cost per hour
$1.99
Supporting infrastructure
Storage · retrieval · serving · observability
MinIO — S3-compatible object store · samples / CAPE / reports (SHA-256 keyed)
Neo4j — Sample → Process → IOC → Technique graph + chat history
FAISS — attack_kb (768-d) · sample_kb · chat_kb semantic cache
vLLM + FastAPI — OpenAI-compatible API · SSE streaming
Langfuse + structlog — per-call traces, tokens, latency
Next.js 16 + React 19 - upload, stream, markdown render
Adapter Primary domain Training rows CyberMetric-80
unified-v2Cross-domain base / routing fallback123,912
expert-e1-staticPE static analysis (EMBER2024, PS obfuscation)11,00083.75%
expert-e2-dynamicSandbox API sequences, CAPE reports11,59485.00%
expert-e3-networkNetwork traffic, intrusion detection19,99190.00%
expert-e4-forensicsMemory / registry / artefact forensics19,18390.00%
expert-e5-threatintelOTX, MalwareBazaar, MISP pulses13,15981.25%
expert-e6-detectionSigma rule authoring, detection eng.19,98688.75%
expert-e7-reportsMalware report generation94,06388.75%
expert-e8-analystAnalyst QA, SOC playbook, CTI reasoning19,50491.25%
02b  /  LLM Inference Architecture

How evidence becomes a report.

The LLM core: deterministic prompt assembly on the left, three-phase inference in the middle (Fathom local → conditional Kimi swarm → synthesis), guardrails and delivery on the right.

Context Assembly · deterministic LLM Inference · three phases Validation & Delivery Input · deterministic Evidence Brief + RAG CAPE extraction · FAISS ATT&CK Neo4j graph · sample similarity Step 06 + 07 Prompt & Sanitise [INST]...[/INST] format · injection filter Phase A · LOCAL MODEL Fathom — Mixtral-8×7B + LoRA Domain router → active expert adapter E1 E2 E3 E4 E5 E6 E7 E8 First-pass analysis · emits ENRICHMENT GAPS contract bf16 · 8K ctx · fallback → unified-v2 when expert unavailable Phase B · INTEL AGENTS · conditional Four parallel enrichment agents threat intel actors · campaigns attack enrichment sub-techniques · mitigations ioc correlation reputation · passive DNS context enrichment family · infection chain Phase C · SYNTHESIS Consolidation Engine Merges evidence + Fathom + agents into final report Executive Summary MITRE ATT&CK IOC Tables Threat Assessment streamed in 180-char SSE chunks Step 11 · guardrails Output Validation hallucination · repetition · section checks Step 13 · streaming SSE Delivery → Dashboard no gaps Cross-cutting observability Langfuse traces  ·  TokenUsageTracker  ·  structlog audit  ·  slowapi rate limit  —   instrumented across every LLM call
Local inference — Fathom (Mixtral + LoRA)
Intel agents — enrichment & synthesis
Streaming delivery
Deterministic / no LLM
Conditional / async path
02c  /  Backend Architecture

How the system is deployed and connected.

vLLM runs on the host via systemd; backend, dashboard, Neo4j, and MinIO run as Docker containers. The backend is a FastAPI app that routes requests through evidence extraction, caching, inference, and persistence layers.

Host System · AMD MI300X · systemd Docker Compose · 4 containers + init systemd · fathom-vllm.service vLLM · Mixtral-8×7B + LoRA endpoint 127.0.0.1:8000 model config model: fathom-base · ctx: 32768 · bf16 DOCKER fathom-dashboard · :3000 Next.js 16 Upload page Analysis page (SSE) Report · Graph · Chat localStorage persistence DOCKER fathom-backend · :7860 FastAPI Backend main.py → routers at /api · health at /health Auth Rate Limit CORS Metrics HUD /api/upload CAPE · PE · KSPN /api/analyze/stream SSE · 3-phase pipeline /api/report/* generate · HTML /api/chat/stream Q&A · cache · history /api/graph Cypher · predefined /api/similar FAISS · Neo4j Internal layers Evidence Guardrails Orchestrator Audit in-memory · volatile _evidence_store Azure OpenAI · conditional Kimi-K2.5 Swarm DOCKER fathom-neo4j · :7687 Neo4j Sample → Process graph IOC · Technique · Behavior NetworkConn relationships Chat session history vol: fathom_neo4j_data DOCKER fathom-minio · :9000 MinIO (S3) fathom-samples fathom-cape fathom-reports (cache) vol: minio_data · SHA256 keyed FAISS · in-process Vector Indexes attack_kb · 768-d ATT&CK RAG sample_kb · cross-sample sim chat_kb · semantic cache (0.88) Docker volumes fathom_neo4j_data · fathom_neo4j_logs · minio_data · adapter cache · HF cache Frontend flow Upload → /api/upload → Analysis → /api/analyze/stream (SSE) → Report → /api/report/generate → Chat → /api/chat/stream /api/* :8000 conditional async
Dashboard + Backend (Docker)
Neo4j (Docker)
MinIO (Docker)
FAISS (in-process)
Async / best-effort path
03  /  Benchmark Results

How Fathom compares — and how we got here.

Twelve-run benchmarking campaign across general cybersecurity knowledge, ATT&CK mapping precision, and operational real-sample evaluation. Headline: 0.841 Parent F1 on real malscore-10 samples, competitive with GPT-4 on CyberMetric and ahead of Llama-2-70B by +34.25 pp.

Comparison · frontier & baseline models
CyberMetric-80 · MMLU Computer Security · MMLU Security Studies

Accuracy across three general cybersecurity benchmarks. Fathom matches GPT-4 on MMLU Security Studies and closes the gap on CyberMetric while running on a single GPU.

Reference figures for GPT-4 / GPT-3.5 / Llama-2-70B from published CyberMetric & MMLU literature · Fathom scores from umer07/fathom-mixtral model card.
Core contribution
ATT&CK F1 progression · Run 4 → Run 7

Four runs · same model weights. The 9× jump came from correcting the prompt format and evidence-layer context — no retraining.

Run 4
naïve API
Run 5
structured
Run 6
pipeline · 3K ctx
Run 7
pipeline · 8K ctx
Real malware samples
Emotet · Formbook · Dridex · malscore 10/10

Per-sample Exact F1 vs Parent F1 on real CAPEv2 reports. Dridex hits 1.000 / 1.000 after the context-window fix.

Aggregate Exact F1 0.868 · Parent F1 0.841
Adapter performance
Nine LoRA experts · CyberMetric-80
Additional benchmarks
ATT&CK MCQ · MMLU subdomains · TruthfulQA · robustness
CyberMetric-500 88.0%
ATT&CK Behaviour → Technique MCQ 80.0%
TruthfulQA MC1 65.0%
MMLU Machine Learning 60.0%
Adversarial robustness (15 prompts) 73.3% · 11/15
Inference speed (LoRA rank-32) 15.36 tok/s · −18.1%
base Mixtral: 18.76 tok/s · peak VRAM 94.16 GB
Where Fathom succeeds

On three malscore-10 samples, Fathom produces ATT&CK mappings with Exact F1 0.868 and Parent F1 0.841. The delta against the synthetic Rigorous evaluation (0.344) demonstrates that structured CAPE prompts plus retrieval augmentation recover ~0.5 F1 over a naive baseline.

Diagnostic lessons learned

Run 1 reported ATT&CK correctness of 0.20. Root cause: Alpaca prompt format on a Mixtral model expecting [INST]...[/INST], plus tokenizer max_length=3072 truncating long briefs. Fixing both moved Dridex from 0.000 to 1.000.

Dataset, not model

The Mal-API-2019 result (12.6%) reflects a dataset limitation: labels are assigned by static AV signatures but API-level behaviour of Trojans/Backdoors/Droppers overlaps extensively. The same model achieves 0.841 Parent F1 on real CAPE reports, which contain multi-modal evidence.

04  /  Acknowledged Limitations

Seven limitations identified through benchmarking.

Transparent limitation reporting is required for academic integrity. Grouped into three categories below.

Capability · base model
3
  • 1. Sub-technique specificity. Parent techniques map reliably; sub-techniques do not — Mixtral’s corpus has limited ATT&CK sub-technique coverage.
  • 2. Rare-technique coverage. UAC bypass (T1548.002), ICMP C2 (T1095), and Windows credential-dumping variants missed entirely (0% recall).
  • 3. Family ID without enrichment. Without the KSPN sidecar, family labelling drifts — Emotet tagged as "Cobalt Strike" on some prompt variants.
Safety
2
  • 4. Prompt-injection role override. "Ignore previous instructions" bypasses the analyst role — base Mixtral was not RLHF-hardened for strict role adherence.
  • 5. APT-attribution hallucination. Given two generic API calls, the model confirmed a false "APT41 attribution" instead of refusing.
05  /  Future Work

Retrain on Foundation-Sec-8B-Instruct.

Phase 1 limitations fall into two categories: domain-knowledge gaps and safety-alignment gaps. Neither is addressed by increasing general-purpose model capacity. Future work adopts a base model whose pretraining corpus is domain-aligned and whose instruction-tuning stage includes safety alignment — Cisco’s Foundation-Sec-8B-Instruct.

Selected base model
Foundation-Sec-8B-Instruct

An 8-billion-parameter instruction-tuned language model released by Cisco’s Foundation AI group. Built on a Llama-3.1-8B backbone with continued pretraining on approximately 5.1 billion tokens of cybersecurity-specific corpora (CVE/CWE, MITRE ATT&CK, threat intelligence reports, red-team playbooks, incident-response documentation, NIST/OWASP). The Instruct variant adds SFT + RLHF with PersonaGym-evaluated cybersecurity role adherence.

Llama-3.1-8B backbone 5.1B domain tokens RLHF-aligned 128K native context Mature ROCm support
Candidate Disposition Rationale
Mixtral-8×7B (retain)RejectedDoes not fix limitations 1–5; high inference cost.
Qwen3.5-27BRejectedGated-DeltaNet hybrid attention; open ROCm kernel issues in llama.cpp and vLLM as of Feb–Mar 2026.
Qwen3-Next-80B-A3BRejected80B exceeds single-MI300X 192 GB capacity for bf16 LoRA; MoE + QLoRA documented as broken combination.
Llama-3.3-70BRejectedNon-domain; ~9× parameter cost for marginal gains on cybersecurity benchmarks.
Foundation-Sec-8B-InstructSelectedDomain-pretrained on 5.1B cyber tokens; Llama-3.1-8B architecture has mature ROCm support; Instruct variant is RLHF-aligned.
Projected improvements · by limitation
Phase 1 measured Post-retrain target
01
Sub-technique specificity
Rigorous P/R/F1 · Exact F1
Phase 10.184
Expected≥ 0.30
02
Rare-technique coverage
Per-category Parent F1 · 15 categories
Phase 10–33% on 6
Expected≥ 50% on 4/6
03
Family ID without enrichment
Standalone check · 3 CAPE samples
Phase 1Unreliable
Expected≥ 2 / 3 correct
04
Prompt-injection role override
Adversarial suite · 15 prompts
Phase 111 / 15 · 73%
Expected≥ 13 / 15 · 87%
05
APT-attribution hallucination
Adversarial Test 8 · APT41 false premise
Phase 1Fail
ExpectedRefuse w/ reason
06
Inference cost
Run 9 equivalent · throughput & VRAM
Phase 115.36 tok/s~ 94 GB VRAM
Expected40–60 tok/s~ 16 GB VRAM
07
Cuckoo noise-API dilution
Regression check only · data-layer issue
Phase 10.841 Parent F1
Hold≥ 0.80 · no regression
Rigorous Exact F1
0.184 ≥ 0.30
Adversarial pass rate
11/15 ≥ 13/15
Throughput (tok/s)
15.36 40–60
VRAM (bf16)
94 GB ~16 GB
Project Map · exact step-by-step pipeline

USER DROPS FILE
CAPE report.json or PE binary.

The full Fathom analysis pipeline — fifteen stages, three LLM inference phases, two storage layers, and the SSE delivery loop. Every step corresponds to production code paths.

Deterministic (no LLM)
Local Fathom inference (Mixtral + LoRA)
Azure Kimi-K2.5 (enrichment / synthesis)
Persistence / I/O
STEP 01 Input Routing + Auth deterministic
Request enters FastAPI. Bearer-token authentication, rate limiting, size cap, and file-type detection decide the pipeline path.
file routes.py:81-198
Checks performed
  • Bearer token auth (optional, via API_TOKENS env var)
  • Rate limit: 20 / minute (slowapi)
  • File size validation: max 50 MB
File type routing
  • .json  →  CAPE report path
  • .exe / .dll / .sys  →  Module 1 static analysis (PE parser)
  • other  →  HTTP 400 rejected
STEP 02 Evidence Extraction deterministic · no LLM
Pure Python. Converts CAPE report.json into a structured EvidenceBrief dataclass.
if CAPE report.jsoncape_extraction_layer_v3.py
CAPEEvidenceExtractor.from_report_dict(report)
  • _extract_static()  —   hashes, PE sections, imports, entropy, YARA
  • _extract_detections()  —   family name, malscore
  • _extract_api_calls()  —   unique APIs, suspicious API map, n-grams
  • _extract_behavior_summary()  —   processes, commands, files, registry, mutexes
  • _extract_enhanced_events()  —   semantic events (write/file, execute/file)
  • _extract_cape_payloads()  —   extracted shellcode, configs, YARA on payloads
  • _extract_amsi_payloads()  —   PowerShell / .NET deobfuscated content
  • _extract_dropped_files()  —   dropped executables with hashes
  • _extract_network()  —   IPs, DNS, HTTP requests, flows
  • _extract_signatures_ttps()  —   CAPE signature matches
  • _extract_process_tree()  —   parent-child process relationships
  • _compute_risk_signals()  —   HIGH / CRITICAL risk flags
  • _compute_known_gaps()  —   what the sandbox did NOT produce
Output
  • EvidenceBrief — structured Python dataclass with ~30 fields: IOCs, behaviors, capabilities, risk_signals, known_gaps, api_ngrams, process_tree, ...
If KSPN summary exists alongside report.json
  • enrich_from_kspn(brief, kspn_data)
  • Pre-mapped MITRE techniques  →  brief.behaviors source="kspn"
  • Family confidence score  →  brief.detections
  • Risk score + reasons  →  brief.risk_signals
  • PCAP IPs  →  brief.iocs source="kspn"
If PE binary (.exe / .dll / .sys)
  • module1_adapter.py → Module1Adapter.analyze_pe_binary(path)
  • File hashes (MD5, SHA256, SHA1)
  • PE section parsing + entropy (via pefile)
  • Import table extraction
  • String extraction + pattern matching
  • Builds EvidenceBrief (partial — no dynamic data)
STEP 02b Storage + Audit background · parallel to response
MinIO object storage · best-effort, non-blocking
  • store_sample(sha256, content)  →  fathom-samples bucket
  • store_cape_report(sha256, json)  →  fathom-cape bucket
Audit log · structlog → stdout JSON + /var/log/fathom/audit.jsonl
  • log_upload(sha256, filename, size, file_type, ioc_count, behavior_count)
In-memory store & cache
  • _evidence_store[brief_id] = EvidenceBrief (dict, single session)
  • MinIO get_report(sha256) — check if sample was analyzed before
Return to frontend
  • UploadResponse { brief_id, sha256, file_type, ioc_count, behavior_count, cached_report }
Frontend redirects → /app/analysis/{briefId} · calls POST /api/analyze/stream
STEP 03 Cache Check before any LLM work
routes.py:456-475
MinIO get_analysis_report(sha256)
  • IF cache HIT  →  stream cached report immediately via SSE
  •     yield status: "Loading cached report..."
  •     yield chunk: {cached_report_text}
  •     yield done: {metadata}
  •     SKIP all remaining steps
  • IF cache MISS  →  continue to Step 4
STEP 04 Domain Routing no LLM · embeddings only
domain_classifier.py
  • Take the user query OR the formatted evidence text
  • Embed with sentence-transformers/all-mpnet-base-v2  →  768-dim vector
  • Compute cosine similarity against 8 pre-computed domain centroids (from centroid_data.json)
  • Return: top domain + confidence score
Centroids — 8 expert domains
  • E1_static · E2_dynamic · E3_network · E4_forensics
  • E5_threatintel · E6_detection · E7_reports · E8_remediation
Example output
domain = "E2_dynamic"
confidence = 0.87
If confidence < 0.3 threshold: falls back to keyword matching.
STEP 05 Adapter Selection no LLM · registry lookup
adapter_registry.py
  • domain="E2_dynamic" → checks if expert-e2-dynamic adapter exists
  • IF yes: use that LoRA adapter path
  • IF no: use unified-v2 adapter (handles all domains)
Currently trained adapters
  • E2_dynamic has_trained_adapter = True
  • All others: False (use unified fallback)
STEP 06 Prompt Construction no LLM · string assembly
6a · Format evidence into text
format_evidence_text(brief) → structured text block:

  ●●● FATHOM EVIDENCE BRIEF ●●●
  Sample: emotet.exe
  SHA256: abc123...
  ══ BEHAVIORAL INDICATORS (47 total, 12 HIGH/CRITICAL) ══
  [HIGH] Process injection: NtMapViewOfSection...
  ══ IOCs (23 total) ══
  [ip] 185.45.23.1 (conf: 85%)...
  ══ KNOWN GAPS ══
✗ No DNS records parsed...
  ●●● END EVIDENCE BRIEF ●●●
6b · RAG retrieval · retriever.py
  • Query FAISS attack_kb index with the evidence text
  • Returns top-5 ATT&CK technique descriptions:
[T1055.001] Process Injection: DLL Injection (relevance: 0.91)
Adversaries may inject DLLs into processes...
6c · Graph context injection · context_retriever.py
  • Query Neo4j for related samples sharing techniques
  • Related samples sharing IOCs
  • IOC reputation (how many samples used this IOC)
  • Formatted as text block appended to evidence
6d · Cross-sample FAISS similarity · sample_similarity.py
  • find_similar_samples(brief, top_k=3)
  • Previously analyzed samples with shared TTPs / IOCs
  • Formatted and appended to evidence text
6e · Assemble prompt · prompt_templates.py
  • Selects domain-specific template (e.g. E2_dynamic)
  • System: "You are Fathom, a malware dynamic behavior analyst..."
  • Instruction: "Analyze this dynamic execution trace:"
  • Input: [evidence_text + rag_context + graph_context + similarity_context]
  • Formatted in Alpaca format via config.build_prompt()
STEP 07 Guardrails — Input Sanitisation deterministic · regex
guardrails.py
sanitize_input(prompt)
  • Detect injection patterns: "ignore previous instructions", "you are now a", "system:", <script>, eval(, exec(, {{...}} (template injection), \xHH (hex escapes)
  • Replace matches with [REDACTED], log warnings
  • Strip null bytes and control characters (preserve \t \n \r)
  • Truncate to max 50,000 characters
  • Return (sanitized_text, warnings_list)
sanitize_malware_report(report_dict)
  • Recursively walk all string values in evidence JSON
  • Apply sanitize_input() to each string
LLM inference — three phases
STEP 08 LLM Phase 1 - Fathom first-pass local - vLLM + Mixtral + LoRA
orchestrator.py → run_fathom_phase()
8a · Try local Fathom model server
requests.post(FATHOM_ENDPOINT + "/v1/chat/completions")

FATHOM_ENDPOINT   = http://127.0.0.1:8000
Model             = fathom-base + fathom LoRA module
System prompt     = FATHOM_ANALYSIS_PROMPT
                    (emits "=== ENRICHMENT GAPS ===" section)
max_tokens = 1500 - temperature = 0.1

External server (fathom-vllm.service):
  - vLLM serve mistralai/Mixtral-8x7B-Instruct-v0.1
  - served-model-name fathom-base; LoRA module fathom=umer07/fathom-mixtral
  - --dtype bfloat16; --max-model-len 32768; --enable-lora
8b · Quality check · _looks_degenerate(raw)
  • Too short (< 40 chars)
  • Single character dominates > 70% (e.g. "---...---")
  • Unique word ratio < 0.08 in 30+ word outputs
8c · If Fathom offline OR degenerate output → FALLBACK
  • Azure Kimi-K2.5 via _collect_azure(messages, source="azure/fathom-phase1")
  • Uses AZURE_DIRECT_PROMPT system prompt
  • Azure OpenAI /v1/chat/completions
8d · Parse output for enrichment contract · _parse_fathom_output(text)
  • Look for "=== ENRICHMENT GAPS ===" delimiter
  • Extract gap lines: "Unknown family background", "IOCs need correlation"
  • If no gaps or "No significant gaps detected" → contract = None
8e · Decide whether to enrich
  • should_enrich = force_enrichment OR _query_requests_enrichment(query)
  • Keywords: "enrich", "latest intel", "threat actor", "correlate iocs", "detection opportunities", "family background"...
8f · Build enrichment contract
_contract_from_gap_text() maps free text gaps → structured contract:

{
  "gaps": ["live_threat_intel", "ioc_correlation",
           "attack_enrichment", "family_background"],
  "evidence_summary": "...(500 chars)...",
  "specific_questions": { gap → specific_question_text }
}

OR _auto_contract() if no gaps parsed but enrichment requested
OR forced full contract if CAPE report with enrichment enabled
Output
  • (analysis_text, enrichment_contract_or_None)
  • Token usage recorded via TokenUsageTracker
  • Langfuse trace recorded via observability.record_generation()
STEP 09 LLM Phase 2 - Azure Enrichment Swarm Azure - conditional - Kimi primary / GPT fallback
Runs only if enrichment contract has gaps AND enable_enrichment = True.
orchestrator.py → run_swarm_phase(contract) · azure_swarm.py → run_swarm()
ThreadPoolExecutor dispatches 4 parallel enrichment agents
threat_intel
Recent campaigns, actor attribution, current prevalence, public reporting
attack_enrichment
Precise sub-technique IDs (T1059.001), detection data sources, mitigations
ioc_correlation
IP / domain / hash reputation, ASN, infrastructure links, passive DNS
context_enrichment
Malware family history, infection chain, related variants, C2 infrastructure
Each agent
  • Receives: system prompt + evidence_summary + specific_question
  • Calls Azure Kimi-K2.5 first; falls back to GPT when Kimi returns 429 or is unavailable
  • max_tokens = 3000 · temperature = 0.1 · timeout = 600s
  • All 4 run in parallel via ThreadPoolExecutor + as_completed
Output
  • format_swarm_results(results) → formatted enrichment text
  • Token usage tracked per agent call
STEP 10 LLM Phase 3 - Azure Synthesis Azure - conditional - Kimi primary / GPT fallback
Runs only if the swarm returned enrichment.
Azure synthesis consolidates all sources
Azure synthesis consolidates all sources
  • System: FATHOM_SYNTHESIS_PROMPT — "You are Kimi, acting as the synthesis engine for Fathom..."
  • Report structure: Executive Summary · ATT&CK Mappings · Behavioral Indicators · IOCs · Threat Assessment · Actor Attribution · Intelligence Contributors
User content (assembled)
  • Original CAPE evidence (first 2000 chars)
  • Fathom initial analysis (first 2000 chars)
  • Live intelligence from 4 specialist agents (first 3000 chars)
  • Original user query
  • max_tokens = 3000 · temperature = 0.1
  • Output: full synthesis text, yielded in 180-char chunks for SSE
If NO enrichment (disabled or swarm failed) → stream_direct_chunks()
  • Try local Fathom → fallback to Azure direct
  • Uses AZURE_DIRECT_PROMPT or FOLLOWUP_QA_PROMPT
STEP 11 Guardrails — Output Validation deterministic
guardrails.py → validate_output(text, response_type="report")
  • Empty / too-short check (< 10 chars → invalid)
  • Forbidden patterns: "I cannot", "As an AI", "I apologize" → warning
  • Hallucination indicators: "definitely", "certainly", "100%", "all malware", "always" → warning if > 3 occurrences
  • Repetition detection: last 10 words repeated > 3× → ' warning
  • Report-specific checks: required sections "Executive Summary", "MITRE ATT&CK", "Risk"; ATT&CK technique IDs present T\d{4}(\.\d{3})?
  • ATT&CK ID validation against known-good set (if provided)
  • Clean response: strip "Sure,", "Here is", "Of course,"; normalize whitespace
NOTE: does NOT cross-validate IOCs against the EvidenceBrief.
STEP 12 Persistence background · parallel to response
12a · Neo4j graph ingestion (async)
ingest_cape.py → ingest_evidence_brief(brief):

Nodes:
  (:Sample {sha256, family, malscore, analyzed_at})
  (:Process {pid, name, ppid})
  (:IOC {type, value, confidence, source})
  (:Technique {technique_id})
  (:Behavior {description, severity, source})
  (:NetworkConn {dst_ip, dst_port, protocol})

Relationships:
  (Sample)-[:SPAWNED]->(Process)
  (Process)-[:SPAWNED]->(Process)   // parent-child
  (Sample)-[:HAS_IOC]->(IOC)
  (Sample)-[:USES_TECHNIQUE]->(Technique)
  (Sample)-[:EXHIBITED]->(Behavior)
  (Sample)-[:HAS_NETWORK]->(NetworkConn)
12b · FAISS sample indexing (async)
  • sample_similarity.py → index_sample(brief)
  • Embeds sample features for cross-sample similarity queries
12c · MinIO report storage (async)
  • store_analysis_report(sha256, report_text, metadata)
  • fathom-reports bucket, keyed by SHA256
  • Enables cache hits for future analyses of the same sample
12d · Audit log
audit.log_analysis_complete(
  sha256, verdict, confidence, technique_count,
  ioc_count, enrichment_used, adapter_used
)
12e · Token usage summary
  • tracker.summary()  →  {input_tokens, output_tokens, total_tokens, cache_read_tokens, requests}
  • Logged per-session via Langfuse + stdout
STEP 13 SSE Response Delivery streaming
routes.py → StreamingResponse (text/event-stream)
SSE event sequence
data: {"type": "status", "text": "Fathom analyzing..."}
data: {"type": "status", "text": "Running 4 intelligence agents..."}   // if enrichment
data: {"type": "status", "text": "Synthesizing final report..."}       // if enrichment
data: {"type": "chunk",  "text": "## Executive Summary\n\n..."}
data: {"type": "chunk",  "text": "...process injection via..."}
...
data: {"type": "done",   "text": "{metadata_json}"}
Metadata in the "done" event
{
  "graph_id": "sha256_of_sample",
  "routing": {
    "domain_id": "E2_dynamic",
    "domain_name": "Dynamic Behavior Analysis",
    "confidence": 0.87,
    "scores": {"E1_static": 0.3, "E2_dynamic": 0.87, ...},
    "adapter": "expert-e2-dynamic"
  },
  "ioc_count": 23,
  "behavior_count": 47,
  "techniques": ["T1055.001", "T1059.001", ...],   // max 15
  "family": "AgentTesla",
  "file_name": "malware.exe",
  "sha256": "abc123...",
  "md5": "def456..."
}
Frontend saves AnalysisResult → localStorage · user navigates /app/report/{id} · calls POST /api/report/generate
STEP 14 Structured Report Generation deterministic + optional LLM
routes.py:960-1049 → /report/generate
Runs the same 3-phase pipeline (Fathom → Swarm → Synthesis) then passes the full report text to report_generator.py → generate_report_sections(full_report, sample_meta).
Operations
  • Extract IOCs via regex: IPs \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}, domains, SHA256/MD5, mutexes, registry keys, URLs — each assigned severity: critical / high / medium / low
  • Extract MITRE techniques: T\d{4}(\.\d{3})? → map to tactic names and severity
  • Determine verdict: "malicious" / "suspicious" / "benign" from report text
  • Extract risk score from report text (or infer)
  • Split report into sections by ## Header markers
  • If include_ai_sections: call Kimi for missing sections — Static Analysis, Dynamic Behavior, Detection Rules, Remediation
Builds StructuredReport
{
  "verdict": "malicious",
  "confidence": 92,
  "riskScore": 8.7,
  "malwareFamily": "AgentTesla",
  "sections": [
    {"id": "executive-summary", "title": "Executive Summary",
     "icon": "FileText", "content": "...markdown..."},
    {"id": "ioc-extraction", "title": "IOC Extraction",
     "icon": "Search", "iocTable": true},
    {"id": "mitre-mapping", "title": "MITRE ATT&CK Mapping",
     "icon": "Target", "mitreTable": true},
    ...   // 9-11 sections total
  ],
  "iocs": [...],
  "techniques": [...],
  "report_text": "...full markdown...",
  "kimi_enrichment_used": true,
  "generated_at": "2026-04-11T..."
}
STEP 15 Dashboard Renders Next.js · frontend
/app/analysis/{briefId}
  • Real-time SSE streaming (status → chunks → done)
  • Tabs: Overview, Analysis Report, Evidence, RAG Context
  • ChatPanel sidebar (custom, NOT CopilotKit)
  • Routing metadata display (domain, confidence, adapter)
  • Sticky header: verdict badge, family, enrichment / cache status
    • Sticky header: verdict badge, family, enrichment / cache status
    • Meta bar: risk score (0–10), confidence %, IOC count, technique count
    • Left nav: clickable section list (9–11 sections)
    • Main content: markdown sections + IOC table + MITRE table
    • Raw report: collapsible full text
    • Action buttons: Copy · Print · Download HTML
    • ChatPanel sidebar for follow-up Q&A
    /app/graph
    • Neo4j graph explorer (generic, not per-analysis)
    Optional · Download HTML Report
    OPT POST /api/report/html optional
    report_html.py → generate_html_report(structured, meta)
    • Self-contained HTML with embedded CSS (dark GitHub theme)
    • Severity badges, verdict color coding
    • IOC and MITRE tables
    • Content-Disposition: attachment
    • filename: fathom_report_{sha256_short}.html
    Optional · Follow-up Chat
    OPT POST /api/chat/stream (SSE) optional
    1 · Semantic cache check · FAISS chat_kb
    • cache_lookup(query, sample_sha256, threshold=0.88)
    • IF HIT → stream cached response immediately, skip LLM
    2 · Load persistent history from Neo4j
    • ChatSession → ChatTurn nodes (not Firebase)
    • get_history_as_turns(session_id) → last N turns
    3 · Generate response
    • stream_direct_chunks(query, cape_context, history, mode="followup")
    • Try local Fathom (FOLLOWUP_QA_PROMPT, max_tokens=1500)
    • Fallback to Azure Kimi-K2.5
    4 · Persist turn + cache
    • save_turn(session_id, "user", query, sha256)
    • save_turn(session_id, "assistant", response, sha256)
    • cache_store(query, response, sha256) → FAISS chat_kb index
    Storage Layer

    What each layer stores.

    LayerWhat it stores
    In-memory dict_evidence_store[brief_id] → EvidenceBrief (lives only for server session)
    MinIO (S3-compatible)fathom-samples (binaries / JSON) · fathom-reports (analysis text + meta) · fathom-cape (raw CAPE JSON). Keyed by SHA256 (auto-deduplicated).
    Neo4jGraph: Sample → Process · IOC · Technique · Behavior · NetworkConn nodes + edges. Chat: ChatSession → ChatTurn nodes (persistent conversation history).
    FAISS (vector)attack_kb — ATT&CK technique embeddings (768-d, all-mpnet-base-v2). sample_kb — per-sample feature embeddings (cross-sample similarity). chat_kb — query/response pair embeddings (semantic chat cache, threshold 0.88).
    localStorage (frontend)fathom_upload (last upload metadata) · fathom_analysis_{sha256} (cached analysis) · fathom_report_{sha256} (cached structured report).
    Audit log/var/log/fathom/audit.jsonl · structlog JSON: uploads, analyses, chat, graph queries, auth events.
    Observability Layer

    Tracing, accounting, and rate control.

    ComponentResponsibility
    LangfuseFull prompt/response traces per LLM call · token usage per call and per session · latency per phase (phase1 · swarm · synth) · error rates and fallback triggers.
    TokenUsageTrackerThread-safe per-session token accounting (input · output · cache_read). Sources: local/fathom · azure/Kimi-K2.5 · azure/fathom-phase1 · azure/synthesis.
    slowapiRate limiting per endpoint per IP: /upload 20/min · /analyze 10/min · /chat 100/min · /agent 5/min.
    Alternative Agent Endpoints

    Not used by the main frontend flow.

    EndpointPurpose
    POST /api/agentLangChain ReAct agent (5-tool loop)
    POST /api/agent/strandsStrands SDK agent (Bedrock · Anthropic · OpenAI)
    POST /api/agent/pydanticPydantic AI agent (structured output)
    GET /api/joe-reportsPre-processed Joe Sandbox report catalog