Architecture¶
flowchart LR
S["Sample bytes (read-only)"] --> G["Static gate: MVP heuristic (every PE replayed)"]
EF["EMBER raw features"] --> GE["triage-ember: LightGBM + TreeSHAP (standalone, not on the analyze path)"]
R["CAPEv2 / Cuckoo report"] --> A["Adapters: CAPE, API sequence, Sysmon, Speakeasy, native trace"]
X["Sysmon XML / JSON lines"] --> A
R -.->|"static.pe"| G2["PE-metadata gate"]
G -->|"detonate?"| D{"score >= calibrated threshold"}
D -->|"no"| REP
D -->|"yes: replay recorded run"| A
A --> TR["Trace: typed, validated events, bound to the sample hash when the trace records one"]
TR --> PROV["Provenance graph + ATT&CK timeline + anomaly"]
TR --> TOK["Normalised tokens"]
TOK --> FAM["Family model: hashed tokens + LR"]
TR --> SC["Behaviour scorer: API n-gram LR, MVP fallback"]
TR --> SYN["Detection synthesis v2: Sigma ladder, YARA imphash / rare imports"]
FAM -.->|"predicted family left out"| NEG
NEG[("Packaged negative corpus: Avast-CTU train runs + synthetic benign")] --> SYN
G2 --> REP
PROV --> REP
FAM --> REP["Unified report: verdict, graph, timeline, IOCs, rules, manifest"]
SC --> REP
SYN --> REP
Q[["Batch job queue (jobs.jsonl)"]] -.-> R
| Stage | Module | Notes |
|---|---|---|
| Adapters | specimen/adapters/cape.py, api_seq.py, sysmon.py, speakeasy.py |
CAPE/Cuckoo call logs and reduced reports, API sequences, Sysmon XML/JSON-lines exports (UTF-8/16/32) and Speakeasy emulation reports all map to one Trace. Hostile input is coerced and capped; Sysmon XML with DTDs is refused; non-finite numbers and deep nesting become one-line errors. |
| Evidence binding | specimen/pipeline.py |
analyze refuses a trace whose recorded sample hash (native sample_sha256, CAPE target hash, Sysmon event 1 Hashes) differs from the sample; a trace without one is unbound with low confidence. |
| Static gate | specimen/static_triage.py, specimen/ml/ember.py |
analyze uses the additive heuristic on bytes (or on static.pe for report) and still replays every PE. The EMBER v2-style vector + LightGBM with TreeSHAP and a 99 %-recall threshold runs only through triage-ember. |
| Provenance | specimen/provenance.py |
Process tree, file/registry/network/mutex/service edges; ~50 ATT&CK mapping rules. |
| Behaviour scorer | specimen/api_behaviour.py, specimen/scoring.py |
API uni+bigram TF-IDF + LR trained on MalbehavD-V1, exported as JSON and evaluated in pure Python; used when a trace has at least 5 real API calls, otherwise the MVP ATT&CK-feature scorer. The report records which one ran. |
| Tokens / family | specimen/tokens.py, specimen/ml/family.py |
Normalised behaviour tokens; 2^18 hashed features + multinomial LR stored as .npz; per-token evidence. |
| Detection synthesis | specimen/detect.py |
Sigma generalisation ladder with a negative-corpus check (packaged corpus minus the predicted family), used by both analyze and report; YARA over pe.imphash() and rare imports, or strings from sample bytes when there is no PE metadata. Emitted rules are validated with pySigma and yara-python in CI. |
| Report | specimen/report.py |
Strict JSON + Markdown (report strings entity-encoded, IOCs defanged), Mermaid graph, manifest with SHA-256 hashes, negative corpus and trace binding. |
| Queue | specimen/jobqueue.py |
Resumable append-only ledger. |
Design decisions are recorded as ADRs (see the navigation).