Skip to main content

VirusTotal IOC Enrichment Tool for SOC/DFIR workflows

Project description

vex logo

vex

IOC enrichment hub for SOC triage and DFIR — VirusTotal + AbuseIPDB / Shodan / WHOIS / MISP / OpenCTI, straight from your terminal.

 ██╗   ██╗███████╗██╗  ██╗
 ██║   ██║██╔════╝╚██╗██╔╝
 ██║   ██║█████╗   ╚███╔╝
 ╚██╗ ██╔╝██╔══╝   ██╔██╗
  ╚████╔╝ ███████╗██╔╝ ██╗
   ╚═══╝  ╚══════╝╚═╝  ╚═╝

PyPI Python License


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.6.1 (on PyPI). On main (pending 1.7.0): TI write-back, watchlist run, daily VT-quota counter, --version flag. 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 modestriage (fast verdict, minimal API calls) and investigate (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--correlate clusters a batch of IOCs by shared infrastructure (ASN, malware family, contacted IPs/domains).
  • AI explanations — opt-in --explain narratives (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-quota budget guard, and a persistent daily VT-quota counter with a stderr status line (on main, pending 1.7.0).
  • Local knowledge basevex tag / note / watchlist annotate IOCs in ~/.vex/. Re-triage an entire watchlist in one shot with vex watchlist run <name> (on main, pending 1.7.0).
  • TI write-back (on main, pending 1.7.0)vex investigate --sight writes 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

[!IMPORTANT] Secondary enrichers run on investigate only, not triage (triage stays fast). Each is no-op without its key, fail-open (a failure or timeout never blocks the run), and they run concurrently. Run vex doctor --probe to 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, vex scans 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-dedup disables 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:

  1. enrichment.writeback_enabled: true in ~/.vex/config.yaml (default false).
  2. --sight flag on vex investigate.
  3. Use --dry-run-sight first 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

[!IMPORTANT] Before using --sight against a real OpenCTI instance, run --dry-run-sight, review the payload, then confirm one MISP sighting and one OpenCTI observable in the UI. The GraphQL mutation (stixCyberObservableAdd) works for network observables on OpenCTI ≥ 5.x; file-hash observables may need a different shape on some versions. Verify against your instance. See vex manual writeback.

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

[!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.
  • Write-back is inert by default (writeback_enabled: false). Credentials for MISP and OpenCTI are stored only in ~/.vex/config.yaml (0o600) or environment variables — never in CLI arguments or logs.
  • 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 --probe to confirm connectivity. pycti is 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, --explain falls back to a deterministic template.
  • Secondary enrichers run on investigate, not triage. A triage --explain gets 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.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vex_ioc-1.7.0.tar.gz (176.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vex_ioc-1.7.0-py3-none-any.whl (120.6 kB view details)

Uploaded Python 3

File details

Details for the file vex_ioc-1.7.0.tar.gz.

File metadata

  • Download URL: vex_ioc-1.7.0.tar.gz
  • Upload date:
  • Size: 176.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vex_ioc-1.7.0.tar.gz
Algorithm Hash digest
SHA256 fa283b64d846e91217b6f5a82446a998023d2d86b3782bb5d1d58919d600361d
MD5 463980116cb35b76f16c12997cf5f69f
BLAKE2b-256 93772ddd86e1813735938c2c7fcae5b0ae4cc2225747fc9f5193d424b36b5d82

See more details on using hashes here.

Provenance

The following attestation bundles were made for vex_ioc-1.7.0.tar.gz:

Publisher: publish.yml on duathron/vex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vex_ioc-1.7.0-py3-none-any.whl.

File metadata

  • Download URL: vex_ioc-1.7.0-py3-none-any.whl
  • Upload date:
  • Size: 120.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vex_ioc-1.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 82b9aec446a6b811e6b4865867defb496aae15a0c75f99162a60063d14321380
MD5 4c3102ca9dd0faeadd0a4ca5064cd485
BLAKE2b-256 6ae6409dcf6677635cf956d7b9e73c1e311d9cd045c943b7e133225d1ddae759

See more details on using hashes here.

Provenance

The following attestation bundles were made for vex_ioc-1.7.0-py3-none-any.whl:

Publisher: publish.yml on duathron/vex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page