VirusTotal IOC Enrichment Tool for SOC/DFIR workflows
Project description
vex
IOC enrichment hub for SOC triage and DFIR — VirusTotal + AbuseIPDB / Shodan / WHOIS / MISP / OpenCTI, straight from your terminal.
██╗ ██╗███████╗██╗ ██╗ ██║ ██║██╔════╝╚██╗██╔╝ ██║ ██║█████╗ ╚███╔╝ ╚██╗ ██╔╝██╔══╝ ██╔██╗ ╚████╔╝ ███████╗██╔╝ ██╗ ╚═══╝ ╚══════╝╚═╝ ╚═╝
vex takes an indicator (a hash, IP, domain, or URL) and turns it into a verdict with context. It started as a VirusTotal CLI and grew into an enrichment hub: one primary source (VirusTotal) plus optional secondary sources that add reputation and your own threat-intel. It runs standalone, and it slots into a pipeline (barb → vex → sift) over stdin/stdout.
[!NOTE] Current release:
vex 1.5.0(on PyPI). Full manual:docs/.
Install
pip install vex-ioc # core
pip install vex-ioc[ai] # + Anthropic / OpenAI explanations (Ollama needs no extra)
Quickstart
vex config --set-api-key YOUR_VT_KEY # or set VT_API_KEY
vex triage 8.8.8.8
vex triage evil.com --explain # add an AI narrative
vex investigate <sha256> -o rich # deep dive
IOC: 8.8.8.8 (ipv4)
Verdict: CLEAN
Detections: 0 malicious / 0 suspicious / 94 engines
Reputation: 0
What you get
- Two modes —
triage(fast verdict, minimal API calls) andinvestigate(deep DFIR: PE info, sandbox behavior, passive DNS, relationships, MITRE ATT&CK, timeline). - Multi-source enrichment — VirusTotal primary; AbuseIPDB, Shodan, WHOIS, MISP, and OpenCTI as secondary enrichers (key-gated, fail-open).
- Batch correlation —
--correlateclusters a batch of IOCs by shared infrastructure (ASN, malware family, contacted IPs/domains). - AI explanations — opt-in
--explainnarratives (Claude / OpenAI / local Ollama / deterministic template), with prompt-injection defense on the untrusted data fed to the model. - Pipeline-ready output — JSON, NDJSON (streaming), CSV, STIX 2.1, ATT&CK Navigator, self-contained HTML; verdict-mapped exit codes.
- Quota-aware batches — IOC dedup, an up-front ETA, and a
--max-quotabudget guard. - Local knowledge base —
vex tag/note/watchlistannotate IOCs in~/.vex/.
Verdicts
| Verdict | Meaning |
|---|---|
🟢 CLEAN |
No engine flagged it. |
🟡 UNKNOWN |
Not enough data, or the IOC was not found. |
🟠 SUSPICIOUS |
Low-confidence detections. |
🔴 MALICIOUS |
Meets the malicious-detection threshold. |
Enrichment sources
VirusTotal is the primary source; it produces the verdict. The others are secondary enrichers that augment an investigate result.
| Source | IOC types | Enable via |
|---|---|---|
| VirusTotal | all | VT_API_KEY / --api-key / vex config |
| AbuseIPDB | IP | VEX_ABUSEIPDB_API_KEY |
| Shodan | IP | VEX_SHODAN_API_KEY |
| WHOIS | domain | core (no key) |
| MISP | all | MISP_URL + MISP_API_KEY |
| OpenCTI | all | OPENCTI_URL + OPENCTI_TOKEN |
[!IMPORTANT] Secondary enrichers run on
investigateonly, nottriage(triage stays fast). Each is no-op without its key, fail-open (a failure or timeout never blocks the run), and they run concurrently. Runvex doctor --probeto see which are configured and reachable.
Correlation
vex triage -f iocs.txt --correlate # deterministic cluster table
vex triage -f iocs.txt --correlate --explain # + per-cluster AI campaign narrative
--correlate groups a batch by shared ASN, malware family, contacted IPs, and contacted domains — surfacing likely campaigns. It is deterministic by default; the AI narrative is added only when --explain is also set.
AI explanations
--explain adds a narrative and next steps; --explain-model overrides the model. Providers: Anthropic, OpenAI, local Ollama, and a deterministic template fallback when none is configured.
export VEX_AI_PROVIDER=ollama # local, no cloud key
vex investigate evil.com --explain
[!WARNING] Enrichment data (sandbox strings, file names, family labels) is attacker-influenceable. Before it reaches an LLM,
vexscans it for prompt-injection patterns and redacts critical attempts, and the system prompt instructs the model to treat the data as untrusted. This is defense-in-depth, not a guarantee.
Output formats
-o rich (default TTY) · -o console · -o json · -o ndjson (one result per line, streamed) · --csv · --stix · --navigator (ATT&CK layer, investigate) · --html <path> (self-contained report). All formats show real IOCs by default; --defang defangs them (e.g. evil[.]com); HTML reports always defang.
sift triage alerts.json -o json | vex triage --from-sift -o ndjson
Pipeline
flowchart LR
barb -->|URLs / IOCs| vex
vex -->|enriched IOCs| sift
sift -.->|extracted IOCs| vex
vex is the enrichment hub. --from-barb reads barb JSON; --from-sift reads a sift TriageReport, extracts its IOCs, and enriches them — closing the sift ↔ vex loop. NDJSON output and verdict exit codes make it scriptable downstream.
Scale
For large batches, such as IOCs extracted from a SIEM export:
- Dedup — duplicate IOCs are collapsed before any query, which saves quota;
--no-dedupdisables it. - ETA + counters — an up-front estimate and a
processed (N from API, M cached)summary on stderr. --max-quota N— caps fresh API lookups; cached IOCs are still served, the rest are skipped with a notice. Re-run to continue — the SQLite cache resumes where you left off.
Configuration
Priority: --api-key flag → environment variable → ~/.vex/config.yaml → defaults.
vex config --show # active config, masked secrets
vex doctor # what's configured (no network)
vex doctor --probe # + live connectivity, surfaces the actual error
| Variable | Purpose |
|---|---|
VT_API_KEY |
VirusTotal |
VEX_AI_API_KEY / VEX_AI_PROVIDER / VEX_AI_MODEL |
AI provider |
VEX_ABUSEIPDB_API_KEY |
AbuseIPDB enricher |
VEX_SHODAN_API_KEY |
Shodan enricher |
MISP_URL / MISP_API_KEY |
MISP lookup |
OPENCTI_URL / OPENCTI_TOKEN |
OpenCTI lookup |
enrichment.stix_tlp_version (1.0 default, 2.0) selects which STIX TLP marking ids the export emits.
Security
[!WARNING]
- Secondary enrichers (AbuseIPDB / Shodan / WHOIS / MISP / OpenCTI) fail-open — never block a run — and are no-op without a key.
- The VT API key is never hard-coded — env var, config, or
--api-key.- Premium calls are gated behind
config.is_premium; the free tier is never broken.- Restricted TI: TLP/markings are carried through; marked intel is never emitted unmarked.
- Machine output stays clean — all notices go to stderr, not stdout.
Exit codes
Computed from the highest-severity verdict in the run:
| Code | Meaning |
|---|---|
0 |
CLEAN / UNKNOWN |
1 |
SUSPICIOUS — or a runtime error / bad input |
2 |
MALICIOUS |
Known limitations
- MISP (v2.5.38) and OpenCTI (demo v7.26) lookups are live-verified end-to-end, and both have mock tests. They query real instances and stay version-sensitive: if a server's schema differs, the enricher fails open (no enrichment, no crash). Use
vex doctor --probeto confirm connectivity.pyctiis a possible future fallback if OpenCTI's GraphQL schema drifts. - AI: Anthropic and Ollama are live-verified end-to-end; OpenAI shares the same code path (mock-tested). Without a provider,
--explainfalls back to a deterministic template. - Secondary enrichers run on
investigate, nottriage. Atriage --explaingets no AbuseIPDB / Shodan / MISP / OpenCTI context by design. - STIX TLP:CLEAR maps to the TLP 1.0 WHITE marking id by default — set
enrichment.stix_tlp_version: "2.0"to switch to TLP 2.0 ids. - The VirusTotal free tier allows roughly 4 requests/min, so large batches are quota-bound — see Scale.
Docs
Full manual: docs/ — getting started, every command/flag, the enrichment model, output formats, the barb/sift pipeline contracts, and configuration. Built-in guide: vex manual (and vex manual ai / pipeline / config). Changelog: CHANGELOG.md.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vex_ioc-1.6.0.tar.gz.
File metadata
- Download URL: vex_ioc-1.6.0.tar.gz
- Upload date:
- Size: 157.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c10f8a64e8649c297f4bf1a7c913792c0b4291032ea34acb906e470c33c8bc57
|
|
| MD5 |
e0ca0790fc4eb4daf5a6a941d0675353
|
|
| BLAKE2b-256 |
00d478c3932a34e1a7581026e0d5a6cabc4681a9c3e23a3337fde68ae9721945
|
Provenance
The following attestation bundles were made for vex_ioc-1.6.0.tar.gz:
Publisher:
publish.yml on duathron/vex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vex_ioc-1.6.0.tar.gz -
Subject digest:
c10f8a64e8649c297f4bf1a7c913792c0b4291032ea34acb906e470c33c8bc57 - Sigstore transparency entry: 1734855296
- Sigstore integration time:
-
Permalink:
duathron/vex@a495064f697d5393a243aa91bcd7f74799783e64 -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/duathron
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a495064f697d5393a243aa91bcd7f74799783e64 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vex_ioc-1.6.0-py3-none-any.whl.
File metadata
- Download URL: vex_ioc-1.6.0-py3-none-any.whl
- Upload date:
- Size: 109.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2fc5ef9850be2856a656953c4bf584ad8cc9a424504be2cefaddd520d0909fe
|
|
| MD5 |
37b6c9940b8ae70692574672843eb4f7
|
|
| BLAKE2b-256 |
703d2bad99b76b51d050081b98c87517158e81f0bad8e63d7bdd1269ab2e3209
|
Provenance
The following attestation bundles were made for vex_ioc-1.6.0-py3-none-any.whl:
Publisher:
publish.yml on duathron/vex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vex_ioc-1.6.0-py3-none-any.whl -
Subject digest:
c2fc5ef9850be2856a656953c4bf584ad8cc9a424504be2cefaddd520d0909fe - Sigstore transparency entry: 1734855340
- Sigstore integration time:
-
Permalink:
duathron/vex@a495064f697d5393a243aa91bcd7f74799783e64 -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/duathron
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a495064f697d5393a243aa91bcd7f74799783e64 -
Trigger Event:
push
-
Statement type: