Skip to content

IOC Proxy

Package name: intrudect-iocfeed

intrudect-iocfeed is a standalone IOC (Indicator of Compromise) feed cache and HTTP feed service. It is the single downloader and proxy that fetches threat intelligence from upstream sources — MISP, Suricata/Snort .rules files, abuse.ch/Emerging Threats IP and domain blocklists, and the Tor relay list — and re-serves them as clean, deduplicated feeds over HTTP.

It writes last-good copies of every feed to disk, so upstream outages never take your feeds down, and it uses only the Go standard library with no database.

Important

The IOC Proxy service must be installed and running for the Intrudect Web UI to receive IOC and Tor feed data. The Web UI mirrors its feeds from this service (see its IOCFEED configuration block). If intrudect-iocfeed is not up and reachable, there is no IOC or Tor feed — the network agents will not receive updated blocklists or Tor relay lists.

Inputs:  MISP API, HTTP feeds (Suricata/Snort .rules, plain/hostfile/CSV
         ip & dns lists), Tor relay list (Onionoo)
Outputs: by-source feeds (misp/…, rules/…, tor/…) and merged by-category
         feeds (ip.txt, dns.txt, rules.rules)
Storage: /opt/intrudect-iocfeed/data

Files

/opt/intrudect-iocfeed/bin/intrudect-iocfeed
/opt/intrudect-iocfeed/etc/config.json
/opt/intrudect-iocfeed/etc/basic.htpasswd
/opt/intrudect-iocfeed/etc/bearer.tokens
/opt/intrudect-iocfeed/data/misp/
/opt/intrudect-iocfeed/data/rules/
/var/log/intrudect-iocfeed.log

The package installs the systemd unit, rsyslog config, and logrotate config, but does not auto-enable or auto-start the service.

Configuration

Configuration file is /opt/intrudect-iocfeed/etc/config.json. A working example (with the MISP server address and API key removed — fill in your own) looks like:

{
  "LISTEN": "127.0.0.1:8084",
  "DATA_DIR": "/opt/intrudect-iocfeed/data",
  "STALE_AFTER": "24h",
  "AUTH": {
    "BASIC_FILE": "/opt/intrudect-iocfeed/etc/basic.htpasswd",
    "BEARER_FILE": "/opt/intrudect-iocfeed/etc/bearer.tokens"
  },
  "MISP": {
    "ENABLED": true,
    "URL": "https://misp.example.com",
    "APIKEY": "",
    "APIKEY_ENV": "",
    "INTERVAL": "1h",
    "TIMEOUT": "60s",
    "MAX_BYTES": 67108864,
    "TAGS": ["Cobalt Strike", "C2", "gophish", "evilginx"],
    "TYPES": ["domain", "ip-dst"],
    "PUBLISH_TIMESTAMP": "7d",
    "ATTRIBUTE_TIMESTAMP": "62d"
  },
  "RULES": {
    "ENABLED": true,
    "INTERVAL": "6h",
    "TIMEOUT": "60s",
    "MAX_BYTES": 67108864,
    "FEEDS": [
      {
        "NAME": "Emerging Threats",
        "URL": "https://rules.emergingthreats.net/open/suricata-7.0/emerging-all.rules",
        "CATEGORY": "rules",
        "ENABLED": true
      },
      {
        "NAME": "Emerging Threats Block IPs",
        "URL": "https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt",
        "CATEGORY": "ip",
        "ENABLED": true
      },
      {
        "NAME": "Threat Fox DNS",
        "URL": "https://threatfox.abuse.ch/downloads/hostfile/",
        "CATEGORY": "dns",
        "FORMAT": "hostfile",
        "ENABLED": true
      },
      {
        "NAME": "Threat Fox IP",
        "URL": "https://threatfox.abuse.ch/export/csv/ip-port/full/",
        "CATEGORY": "ip",
        "FORMAT": "csv",
        "ENABLED": true
      }
    ]
  },
  "TOR": {
    "ENABLED": true,
    "URL": "https://onionoo.torproject.org/details",
    "INTERVAL": "12h",
    "TIMEOUT": "60s",
    "MAX_BYTES": 100000000
  }
}
  • LISTEN - IP address and port to bind. The service has no TLS of its own; bind it to an address the Web UI host can reach (a loopback, a management interface, or a Docker bridge such as 172.17.0.1:8084 when the Web UI runs in a container), and put a reverse proxy in front of it if you expose it externally.
  • DATA_DIR - directory where last-good feed files are written.
  • STALE_AFTER - a feed older than this is reported as stale in the status output.
  • AUTH - paths to the basic-auth and bearer-token credential files (see Auth).
  • DEBUG - set the top-level "DEBUG": true to log detailed outbound request and response diagnostics. MISP API keys are never logged.

MISP

  • URL / APIKEY - your MISP instance and its API key. Set the APIKEY here, or leave it empty and set APIKEY_ENV to the name of an environment variable that holds the key.
  • TAGS - only attributes carrying these tags are pulled.
  • TYPES - MISP attribute types to import (domain, ip-dst).
  • PUBLISH_TIMESTAMP / ATTRIBUTE_TIMESTAMP - only fetch events/attributes seen within these windows.
  • Set "ENABLED": false to disable MISP entirely.

Feed categories

Each entry in RULES.FEEDS is an HTTP feed described by these fields beyond NAME / URL / ENABLED:

Field Values Default Meaning
CATEGORY rules, ip, dns rules Indicator type produced; selects the merged feed (rules.rules, ip.txt, dns.txt) and the on-disk extension (.rules for rules, .txt for ip/dns).
FORMAT plain, hostfile, csv plain How the source is parsed (see below).
MIN_CONFIDENCE 0100 0 For csv feeds only: drop rows whose confidence_level is below this value. 0 keeps everything.

Formats:

  • plain — one indicator per line; # comments and blank lines are ignored. Matches Suricata .rules files and simple IP/domain blocklists.
  • hostfile — hosts-file lines such as 0.0.0.0 ads.example.com; the leading address and any trailing # comment are stripped. Requires CATEGORY: "dns".
  • csv — abuse.ch / ThreatFox CSV dumps. The ioc_value column is extracted; for CATEGORY: "ip" a trailing :port is removed. Combine with MIN_CONFIDENCE to keep only high-confidence rows.

For ip/dns feeds, extracted values are validated (invalid entries dropped), deduplicated and sorted before being written. Rule feeds are stored verbatim, and their URL must end in .rules (or .rules.zip).

Zip archives: if a feed URL path ends in .zip, the download is decompressed before parsing — the FORMAT then applies to the extracted content. The decompressed size is capped by MAX_BYTES as a zip-bomb guard.

HTTP API

Browser/help endpoint:

GET /

The root page shows service version, overall status, active problems, available feeds, endpoint examples, and recent feed events. It does not expose secrets, the MISP API key, or the auth files.

Open (no-auth) endpoints:

# By category (merged across sources)
GET /open/ip.txt          # MISP ip-dst + all ip HTTP feeds
GET /open/dns.txt         # MISP domain + all dns HTTP feeds
GET /open/rules.rules     # all rule feeds (same as rules/all.rules)

# By source
GET /open/misp/ip-dst.txt
GET /open/misp/domain.txt
GET /open/rules/all.rules
GET /open/rules/<feed-name>.rules
GET /open/rules/<feed-name>.txt
GET /open/tor/relays.json
GET /open/metadata

Basic-auth endpoints use the same paths under /basic/; bearer-auth endpoints under /bearer/.

Service endpoints:

GET /health
GET /ready
GET /api/v1/status      # primary machine-readable status
GET /api/v1/events      # recent feed events
GET /api/v1/metadata    # raw MISP/rules metadata

Category feeds

/open/ip.txt and /open/dns.txt are merged, deduplicated, sorted lists built at request time from every source of that category (the MISP ip-dst/domain files plus all HTTP feeds of the matching CATEGORY). /open/rules.rules serves the merged rule set. A category endpoint returns 404 until at least one of its sources exists on disk, and responses carry an ETag so If-None-Match requests return 304 when nothing changed.

Tor relay feed

/open/tor/relays.json serves the last-good Tor relay list fetched from the Onionoo details document.

Note

The Tor relay list is deliberately not part of the ip category and is never merged into /ip.txt. MISP, rules, and HTTP IP feeds are known-bad indicators, safe to block by default. Tor relay membership is a policy signal — a Tor IP is not inherently malicious — so it is kept as its own feed for consumers that explicitly want to act on it.

Auth

Both credential files use the same formats as the other Intrudect components.

Basic auth reads /opt/intrudect-iocfeed/etc/basic.htpasswd (formats {SHA}, $apr1$, and plaintext fallback), one username:hash per line:

demo:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=

Bearer auth reads /opt/intrudect-iocfeed/etc/bearer.tokens, one token per line.

FortiGate external resources support HTTP Basic auth with set username / set password. Bearer auth is provided for Intrudect and other clients that can send Authorization: Bearer headers.

FortiGate example

config system external-resource
    edit "Intrudect_MISP_IP"
        set type address
        set resource "https://feeds.example.com/open/misp/ip-dst.txt"
        set refresh-rate 60
        set server-identity-check full
    next
end

Start & enable service

systemctl restart intrudect-iocfeed
systemctl enable intrudect-iocfeed

Verify it is serving feeds:

curl -s http://127.0.0.1:8084/api/v1/status | head