Threat-intel definitions
Where malscan's known-bad indicators come from, how --fetch-definitions refreshes them, running offline, and the feeds.json format for your own blocklists.
The IOC scan matches files against three indicator sets. None needs an account.
| Set | Contents | Where it comes from | Refreshed |
|---|---|---|---|
| Vulnetix STIX | Domains, IPs and URLs from Vulnetix malware advisories | https://vulnetix.com/malscan-stix/index.json and the feeds it lists |
On a scan, when the cached copy is over 1 hour old |
| TweetFeed | A rolling 30 days of community-reported IOCs, plus SHA-256 and MD5 file hashes used by badhash | https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/stix/month.json |
On a scan, when over 24 hours old |
| badnet | Bad IPv4, IPv6 and domains aggregated from 17 public blocklists | Embedded in the CLI at build time, plus an overlay you refresh | Only by --fetch-definitions |
STIX and TweetFeed copies are cached in the OS temp directory. The badnet overlay is written to <user cache>/vulnetix/malscan/definitions (~/.cache/vulnetix/malscan/definitions on Linux), or to $VULNETIX_MALSCAN_DEFS_DIR when set, as bad-ipv4.txt, bad-ipv6.txt, bad-domains.txt and bad-emails.txt. Every scan loads the overlay on top of the embedded lists.
Refresh
vulnetix malscan --fetch-definitions
This downloads every enabled badnet feed, merges the result with the definitions already on disk (so a feed that is down this time loses nothing), writes the overlay, then force-refreshes the STIX index and TweetFeed. It scans nothing and exits.
Fetching malscan threat-intel definitions → /home/dev/.cache/vulnetix/malscan/definitions
ok dshield-block ipv4=20 ipv6=0 domains=0 emails=0
warn binarydefense-banlist context deadline exceeded
Definitions updated: ipv4=… ipv6=… domains=… emails=… (3 files changed)
Refreshing STIX feeds (vulnetix index + TweetFeed)…
STIX feeds refreshed (0 warning(s)).
The whole refresh has a 5-minute timeout. It fails only when no badnet feed could be fetched (no threat-intel feeds could be fetched … definitions left unchanged); a failed STIX refresh keeps the cached copy and prints a warning.
Run it on a schedule, for example in the job that builds your CI image, so scans do not depend on live downloads.
Offline and air-gapped
vulnetix malscan --no-ioc-feeds
skips the IOC scan entirely: no download, and no badnet or STIX matching. The detect and badhash families still run over manifests and install scripts. The report’s headline reads INCOMPLETE when no indicators were loaded unless you chose --no-ioc-feeds.
When the feeds cannot be reached, a scan falls back to the newest cached STIX copy with a stale-cache warning, and skips a feed it has never cached. To keep IOC matching in an air-gapped environment, run --fetch-definitions on a connected machine and copy the definitions directory to the same path on the target (or point VULNETIX_MALSCAN_DEFS_DIR at it). The STIX caches are in the OS temp directory, which is often cleared, so rely on the badnet set there.
The default badnet feeds
dshield-block, dshield-ipsascii, crowdsec-intelligence, dandelionsprout-antimalware, feodotracker-recommended, urlhaus-hosts, isc-intelfeed, isc-cloudips, firehol-level3, binarydefense-banlist, aper-phishing-reply, projecthoneypot-p-rss, projecthoneypot-rss, cinsscore-badguys, alienvault-generic, alienvault-data, bruteforceblocker.
Your own feeds
--feeds <file> replaces the default feed list for one --fetch-definitions run. It is refused without --fetch-definitions (--feeds is only used with --fetch-definitions). It changes the badnet refresh only; the STIX refresh is unchanged.
vulnetix malscan --fetch-definitions --feeds ./feeds.json
{
"schema_version": "badnet-feeds/v1",
"feeds": [
{
"key": "corp-blocked-hosts",
"name": "Security team blocked hosts",
"url": "https://intel.example.com/blocked-hosts.txt",
"parser": "hosts",
"enabled": true
}
]
}
| Field | Required | Meaning |
|---|---|---|
schema_version |
no | badnet-feeds/v1 |
feeds |
yes | Array of feed sources |
feeds[].key |
yes | Short id, shown in the fetch output. An entry without key or url is skipped. |
feeds[].url |
yes | HTTP or HTTPS URL |
feeds[].parser |
yes | One of the parsers below. An unknown parser fails the whole file. |
feeds[].enabled |
no | Default true. false keeps a source documented but unused. |
feeds[].name, feeds[].detail |
no | Labels; ignored by the fetcher |
A file with no enabled feed is an error (no enabled feeds).
Parsers
| Parser | For | Keeps |
|---|---|---|
iplist |
One IP per line, comments and trailing text tolerated | Single IPv4 and IPv6 addresses; CIDR ranges are skipped |
netset |
FireHOL .netset files |
As iplist |
hosts |
Hosts-file format (0.0.0.0 bad.example) |
The hostnames; the sinkhole IP is ignored |
rss |
RSS or XML feeds | IPs, domains and emails in titles, descriptions and CDATA |
emails |
Lists of email addresses | Email addresses |
mixed |
Free text | IPs, URLs (their hosts), domains and emails |
misp |
MISP event JSON | Event.Attribute[] values of types such as ip-src, ip-dst, domain, hostname, url, domain|ip, email-src, email-dst; attributes with to_ids: false or a benign reputation tag are skipped |
Every parsed value goes through the shared allowlist before it is kept: reserved and documentation IPs, localhost, and well-known registry, CDN and documentation domains are dropped, so a feed cannot make registry.npmjs.org an indicator.