Skip to content

CLI and API reference

Command line

$ vitrine --help
usage: vitrine [-h] [--version] {gen-corpus,train,analyze,scan,serve,demo} ...

VITRINE command-line interface (static analysis only; nothing is ever
executed).

positional arguments:
  {gen-corpus,train,analyze,scan,serve,demo}
    gen-corpus          write synthetic inert PE corpus
    train               train the linear demo model on the synthetic corpus
    analyze             statically analyze a PE file
    scan                triage every PE in a directory (JSON lines)
    serve               run the FastAPI service + triage UI
    demo                end-to-end demo on synthetic samples

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

Each subcommand has its own --help. analyze and scan only read files; nothing is executed. Files over the parser's 128 MiB cap are skipped before they are read (scan reports them as too large). Analysis keeps the file and several derived copies (strings, byte histograms, feature blocks) in memory, so peak memory is a large multiple of the file size; scan very large installers one at a time.

HTTP API (vitrine serve)

Method Path Body Returns
GET /health none status, version, model type, benign corpus size
GET /api/model none model type, calibrated thresholds, metadata, known families
POST /api/analyze raw PE bytes (application/octet-stream, at most 32 MB) full triage result (verdict, score, SHAP drivers, capabilities, YARA rule)
POST /api/yara/validate {"text": "<rule>"} benign hits and specificity against the configured benign corpus

The OpenAPI schema is at /openapi.json. The interactive /docs page (Swagger UI, loaded from a CDN) is served only with vitrine serve --enable-docs; /redoc is off. At most two uploads are buffered and analysed at once (further requests wait unread), which bounds the service's memory. A static, server-less copy of the UI is on the demo page.

Python API

vitrine.triage

End-to-end static triage pipeline: dissect -> tag -> featurize -> score/attribute -> YARA -> route.

Works with either verdict model:

  • :class:vitrine.gbdt.GBDTVerdictModel -- XGBoost trained on EMBER 2018 (real data), with validation-calibrated thresholds: MALICIOUS at the 0.1 %-FPR point, SUSPICIOUS at the 1 %-FPR point.
  • :class:vitrine.model.VerdictModel -- the dependency-free linear model trained on the synthetic corpus (demo / CI), with fixed 0.7 / 0.3 thresholds.

analyze(data, model, benign=None, siblings=None)

Statically triage one PE file (bytes are parsed, never executed).

Parameters:

Name Type Description Default
data bytes

raw file bytes.

required
model VerdictModel | GBDTVerdictModel

a verdict model from :func:load_model or :func:train_default.

required
benign list[bytes] | None

optional benign corpus; when given, non-benign verdicts get a candidate YARA rule validated for specificity against it.

None
siblings list[bytes] | None

optional suspected same-family samples, used for rule coverage.

None

Returns:

Type Description
TriageResult

TriageResult with verdict, score, SHAP attributions, capabilities, notes and rules.

Raises:

Type Description
PEParseError

the bytes are not a parseable PE file.

load_model(path)

Load either model format (EMBER XGBoost vitrine-gbdt-1 or linear demo) by sniffing the file.

train_default(n_per_family=20, seed=0)

Linear model on the synthetic inert corpus (fast, dependency-free; for demos and CI).

vitrine.dissect

Pure-stdlib static PE dissector. Reads bytes only; never loads, maps or executes anything.

Supports PE32 and PE32+ (x64): headers, sections, imports (incl. ordinals and delay-load), exports, resource types, data directories, overlay, strings, entropy and imphash. Every read is bounds-checked; malformed input raises :class:PEParseError instead of crashing.

PEParseError

Bases: ValueError

dissect(data)

vitrine.gbdt.GBDTVerdictModel dataclass

contributions(X)

TreeSHAP values, shape (n, d+1); last column is the bias (expected margin).

vitrine.yara_synth

YARA rule synthesis + specificity/coverage validation.

Rules are generated in a restricted subset of YARA (text strings, N of them, MZ check) so VITRINE can evaluate them natively without yara-python. If yara-python is installed, compile_check also verifies the rule compiles with the real engine.

synthesize(name, group, benign, family=None)

validate(rule, benign, siblings)

match(rule, data)

parse(text)

Parse the restricted YARA subset line by line (bounded work per line, no backtracking blow-up).

vitrine.capabilities

capa-inspired capability tagger: small declarative rules over static artifacts.

Rules match on imported API names (case-insensitive, A/W suffix-agnostic) and, when the actual strings are available, on string substrings. When only EMBER-style string statistics are available (no raw strings), string rules fall back to the urls/registry/paths counters. Each rule maps to a MITRE ATT&CK technique and carries the evidence that fired it.

norm_api(name)

Canonical API name: case-folded, ANSI/Unicode A/W suffix dropped, Zw folded to Nt.

tag_imports(import_names, strings=None, string_stats=None, rules=RULES, ignore_strings=())

Tag capabilities from imported API names plus either raw strings or EMBER string stats.

ignore_strings (e.g. strings from the certificate table) never count as URL evidence; neither do XML-namespace and certificate-infrastructure (CRL/OCSP/AIA) URLs.

tag_raw(raw, rules=RULES)

Tag an EMBER raw-feature dict (no raw strings: string rules use the counters).