Fast, multi-source IOC enrichment from your terminal — IPs, domains, URLs, and hashes against AbuseIPDB, URLhaus, and more.
Project description
ioc-vet
Multi-source IOC enrichment from your terminal. Drop in an IP, domain, URL, or file hash — get back a unified verdict pulled from multiple threat intel sources in parallel, instead of opening five browser tabs.
$ iocvet lookup 185.220.101.45
╭──────────────────────────────────────────────────╮
│ 185.220.101.45 [ipv4] SUSPICIOUS │
╰──────────────────────────────────────────────────╯
Provider Verdict Summary Latency
ip-api SUSPICIOUS Germany · M247 Europe SRL · known... 180ms
abuseipdb SUSPICIOUS abuse confidence 42/100 across 11... 310ms
abuseipdb → https://www.abuseipdb.com/check/185.220.101.45
Why this exists
Most tools in this space are built to be clicked, not scripted. Browser extensions and note-taking plugins solve the same problem, but they only run while a human is looking at a screen — and the ones that go further usually paywall the providers that actually matter (AbuseIPDB, URLhaus) behind a paid tier.
ioc-vet is built for the other half of the job: the part that runs in a CI pipeline, a cron job checking yesterday's suspicious IPs, or a one-line grep | iocvet batch over last night's logs. It works out of the box with zero API keys, gets better as you add free ones, and every provider it supports is free — there's no Pro tier holding anything back.
Install
pip install ioc-vet
Or from source:
git clone https://github.com/ashish-cybersec/ioc-vet
cd ioc-vet
pip install -e .
Usage
# Single lookup, pretty terminal output
iocvet lookup 8.8.8.8
# Works on domains, URLs, and hashes too — type is auto-detected
iocvet lookup example.com
iocvet lookup https://example.com/payload.exe
iocvet lookup 44d88612fea8a8f36de82e1278abb02f
# Defanged IOCs — paste them straight from a ticket or report
iocvet lookup 'evil[.]com'
iocvet lookup 'hxxp://evil[.]com/malware.exe'
iocvet lookup '1[.]2[.]3[.]4'
# Machine-readable output for scripts/pipelines
iocvet lookup 8.8.8.8 --json
# Batch mode: one IOC per line
iocvet batch suspicious_ips.txt
# Exit code 1 if malicious — useful in CI or alerting pipelines
iocvet lookup 1.2.3.4 --fail-on-malicious
# See what's configured
iocvet providers
Providers
| Provider | IOC types | API key needed? |
|---|---|---|
| ip-api | IP | No — works immediately (non-commercial use only) |
| AbuseIPDB | IP | Yes, free (1,000 checks/day) — sign up |
| RDAP | Domain | No — works immediately |
| URLhaus | IP, domain, URL, file hash | Yes, free — sign up |
Note on ip-api: the free endpoint is non-commercial use only and rate-limited to 45 requests/minute. If you're running iocvet at work or in a company CI pipeline, you need their paid tier — or drop ip-api and rely on the other providers.
Exit codes
iocvet uses distinct exit codes so pipelines can branch on them:
| Code | Meaning |
|---|---|
| 0 | Ran successfully (verdict may be clean, suspicious, or unknown) |
| 1 | --fail-on-malicious set and the overall verdict was malicious |
| 2 | Bad usage — unrecognisable IOC, missing/undecodable file, or config error |
| 3 | --fail-on-error set and at least one IOC had no provider answer |
For a security gate that must fail closed, combine both flags:
iocvet lookup "$IOC" --fail-on-malicious --fail-on-error
This exits non-zero on a malicious verdict and when no provider could reach a conclusion — so an outage in the threat-intel sources can't let an unchecked indicator pass as clean.
Security & privacy
iocvet is built to be run against untrusted indicators, so it takes some care:
- Private and reserved IPs are never sent to external providers. RFC1918, loopback, link-local (including cloud metadata
169.254.169.254), and reserved addresses are recognised and skipped — they'd disclose internal network structure to a third party and no reputation source can rate them anyway. - ip-api uses plaintext HTTP (SSL is paid-tier on their side), so a public IP you look up is visible to an on-path observer. All other providers use HTTPS with certificate verification.
- Malformed inputs (path traversal, CRLF, multi-value smuggling, oversized strings) are rejected before any request is built. API keys are never written to output, errors, or
--json.
Set keys as environment variables, or run iocvet configure to generate a config file at ~/.config/iocvet/config.toml:
export ABUSEIPDB_API_KEY="your-key"
export URLHAUS_AUTH_KEY="your-key"
iocvet works with zero keys configured — it just runs fewer providers.
Adding a provider
This is the part we'd love help with. Every provider is a self-contained class:
from iocvet.providers.base import Provider
from iocvet.core.models import IOCType, ProviderResult, Verdict
class YourServiceProvider(Provider):
name = "yourservice"
requires_key = True
api_key_env = "YOURSERVICE_API_KEY"
def supports(self, ioc_type: IOCType) -> bool:
return ioc_type in (IOCType.IPV4, IOCType.IPV6)
async def _query(self, client, ioc, ioc_type) -> ProviderResult:
resp = await client.get(f"https://api.yourservice.com/{ioc}")
data = resp.json()
return ProviderResult(
provider=self.name,
verdict=Verdict.MALICIOUS, # map their response onto ours
summary="short human-readable summary",
)
Register it in src/iocvet/providers/__init__.py and open a PR. Good candidates we don't cover yet: VirusTotal, AlienVault OTX, GreyNoise, Shodan, MalwareBazaar. See CONTRIBUTING.md.
Roadmap
- SQLite response caching (avoid re-querying the same IOC within a TTL)
- Domain support (RDAP registration data + URLhaus host lookups)
- VirusTotal, OTX, GreyNoise, Shodan providers
- Markdown / CSV report export for tickets
-
--watchmode to tail a log file and enrich IOCs as they appear
License
MIT — see LICENSE.
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 ioc_vet-0.2.0.tar.gz.
File metadata
- Download URL: ioc_vet-0.2.0.tar.gz
- Upload date:
- Size: 40.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5581c39edfc6d44bb33f87fb5e317d0b1636fe87396984a777098430c4bb9219
|
|
| MD5 |
5f088309554609d4eff92e81ca0ae441
|
|
| BLAKE2b-256 |
a054c80a7bac1c5854105b7002be3ce8e20b731d00e248aa20b85cafeec3bc74
|
Provenance
The following attestation bundles were made for ioc_vet-0.2.0.tar.gz:
Publisher:
release.yml on ashish-cybersec/ioc-vet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ioc_vet-0.2.0.tar.gz -
Subject digest:
5581c39edfc6d44bb33f87fb5e317d0b1636fe87396984a777098430c4bb9219 - Sigstore transparency entry: 2194552735
- Sigstore integration time:
-
Permalink:
ashish-cybersec/ioc-vet@df46f592ce925ef5371dc9057c0d908ef7a847bf -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ashish-cybersec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@df46f592ce925ef5371dc9057c0d908ef7a847bf -
Trigger Event:
push
-
Statement type:
File details
Details for the file ioc_vet-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ioc_vet-0.2.0-py3-none-any.whl
- Upload date:
- Size: 32.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5322e73ce2bff36f884e5a48c8e94ad6326e143050644eefecc60b1f20ad81f0
|
|
| MD5 |
458fd0779af76e43fa956ab44f59bb11
|
|
| BLAKE2b-256 |
6b01f1215788893c4fbffa9bc612078d798273b3679bf63702d56da249a0b739
|
Provenance
The following attestation bundles were made for ioc_vet-0.2.0-py3-none-any.whl:
Publisher:
release.yml on ashish-cybersec/ioc-vet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ioc_vet-0.2.0-py3-none-any.whl -
Subject digest:
5322e73ce2bff36f884e5a48c8e94ad6326e143050644eefecc60b1f20ad81f0 - Sigstore transparency entry: 2194552747
- Sigstore integration time:
-
Permalink:
ashish-cybersec/ioc-vet@df46f592ce925ef5371dc9057c0d908ef7a847bf -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ashish-cybersec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@df46f592ce925ef5371dc9057c0d908ef7a847bf -
Trigger Event:
push
-
Statement type: