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.
Install
pip install vex-ioc # core
pip install vex-ioc[ai] # + Anthropic / OpenAI explanations (Ollama needs no extra)
Quickstart
vex config --set-virustotal 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
Secondary enrichers (optional, saved to ~/.vex/config.yaml):
vex config --set-abuseipdb YOUR_KEY --set-shodan YOUR_KEY
vex config --set-misp-url URL --set-misp-key YOUR_KEY
vex config --set-opencti-url URL --set-opencti-token YOUR_TOKEN
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, a
--max-quotabudget guard, and a persistent daily VT-quota counter with a stderr status line (on main, pending 1.7.0). - Local knowledge base —
vex tag/note/watchlistannotate IOCs in~/.vex/. Re-triage an entire watchlist in one shot withvex watchlist run <name>(on main, pending 1.7.0). - TI write-back (on main, pending 1.7.0) —
vex investigate --sightwrites MISP sightings and OpenCTI observables for IOCs above a configurable verdict floor; triple opt-in (config killswitch + flag + dry-run preview).
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 |
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
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.
TI write-back (on main — pending 1.7.0)
vex investigate --sight writes MISP sightings and OpenCTI observables back for IOCs that meet a configurable verdict floor. Three gates must all be open before any write occurs:
enrichment.writeback_enabled: truein~/.vex/config.yaml(defaultfalse).--sightflag onvex investigate.- Use
--dry-run-sightfirst to preview the payloads without sending.
vex investigate 1.2.3.4 --dry-run-sight # preview payloads, no network
vex investigate 1.2.3.4 --sight # write if enabled and above floor
vex investigate -f iocs.txt --sight # batch write-back
Key config keys (in enrichment: section):
| Key | Default | Meaning |
|---|---|---|
writeback_enabled |
false |
Master switch — must be true or --sight is a no-op. |
writeback_tlp |
"green" |
TLP ceiling. Writes are blocked when the IOC's source TLP is more restrictive. |
writeback_min_verdict |
"SUSPICIOUS" |
Verdict floor — IOCs below this are silently skipped. |
Security
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.
Author
Christian Huhn — building security tooling for SOC/DFIR workflows.
- GitHub: @duathron
- LinkedIn: Christian Huhn
License
MIT
Metadata
Release files for vex-ioc 1.8.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| vex_ioc-1.8.1.tar.gz | 193.9 kB | With provenance | Details for vex_ioc-1.8.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vex_ioc-1.8.1-py3-none-any.whl | Python 3 | none | any | Details for vex_ioc-1.8.1-py3-none-any.whl |
Total release size: 319.1 kB
Release files / vex_ioc-1.8.1.tar.gz
| Download URL | vex_ioc-1.8.1.tar.gz |
|---|---|
| Size | 193.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6abfb673ea3d022b3775d3951c67e8f9e56fa3fe5f7a312c3a615cb30853fb1e
|
|
BLAKE2b-256 checksum How to use checksums |
622887139a2c6b3194f8693496dd9881094943ec7a9077403212abb5da461fb4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / vex_ioc-1.8.1-py3-none-any.whl
| Download URL | vex_ioc-1.8.1-py3-none-any.whl |
|---|---|
| Size | 125.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1f7abe0d3d2d8b89dd3768056f3ebc061ba2040a2ec319dcae1292d0d118743a
|
|
BLAKE2b-256 checksum How to use checksums |
647630dec5ba965305c96f565c2ad663b3e9caf95ff598259530171abc058b65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logSecurity for vex-ioc 1.8.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vex_ioc-1.8.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.