A high-performance warninglist lookup engine that checks indicators of compromise (IOCs) against MISP warninglists. It identifies false positives by matching values against 120+ curated lists of known-good infrastructure: cloud provider IP ranges, top domain rankings, public DNS resolvers, certificate authorities, and more.
MISP Feedback runs as a daemon (misp-fbd) that loads all warninglists into memory once and serves sub-millisecond lookups over a Unix socket and/or HTTP. A CLI tool (misp-fb) provides convenient command-line access.
- Rust toolchain (1.70+) — install via rustup
- Git (for cloning and the warninglists submodule)
git clone --recurse-submodules https://github.com/MISP/misp-feedback.git
cd misp-feedback
cargo build --releaseThe binaries will be at target/release/misp-fbd (daemon) and target/release/misp-fb (CLI).
If you cloned without --recurse-submodules, fetch the warninglists separately:
git submodule update --initCopy the example config file:
cp config.toml.example config.tomlGenerate a config file interactively:
$ misp-fb config
MISP Feedback configuration
Unix socket path [/tmp/misp-fbd.sock]:
Enable HTTP listener? [y/N]: y
HTTP bind address [127.0.0.1:3000]:
Warninglists directory [./misp-warninglists/lists]:
Which warninglists to load?:
> All lists
Only selected lists
All except selected lists
--- Generated config ---
[daemon]
socket_path = "/tmp/misp-fbd.sock"
http_bind = "127.0.0.1:3000"
warninglists_path = "./misp-warninglists/lists"
[warninglists]
lists = []
Config written to config.tomlUse --output to write to a different path, and --warninglists-path if your lists directory is elsewhere:
$ misp-fb config --output /etc/misp-feedback/config.toml --warninglists-path /opt/misp-warninglists/listsWhen choosing "Only selected lists" or "All except selected lists", the tool scans the warninglists directory and presents a multi-select list of all available warninglists to pick from.
Or edit config.toml directly (copy from config.toml.example if you haven't already):
[daemon]
socket_path = "/tmp/misp-fbd.sock"
# http_bind = "127.0.0.1:3000" # uncomment to enable HTTP listener
warninglists_path = "./misp-warninglists/lists"
[warninglists]
# Empty list = load all warninglists
# Include mode: lists = ["amazon-aws", "google-gcp", "cloudflare"]
# Exclude mode: lists = ["!alexa", "!tranco", "!cisco_top1000"]
lists = []| Option | Default | Description |
|---|---|---|
daemon.socket_path |
/tmp/misp-fbd.sock |
Unix domain socket path |
daemon.http_bind |
(disabled) | TCP address for the HTTP listener (e.g. 127.0.0.1:3000) |
daemon.warninglists_path |
./misp-warninglists/lists |
Path to the warninglists directory |
warninglists.lists |
[] (all) |
Filter which lists to load. Prefix with ! to exclude. |
# Start with default config.toml in the current directory
./target/release/misp-fbd
# Start with a specific config file
./target/release/misp-fbd --config /etc/misp-feedback/config.tomlThe daemon logs to stderr. Control verbosity with the RUST_LOG environment variable:
RUST_LOG=debug ./target/release/misp-fbdThe daemon watches the warninglists directory for changes and automatically reloads when files are added, modified, or removed.
The CLI tool (misp-fb) communicates with a running misp-fbd daemon over the Unix socket.
$ misp-fb check 8.8.8.8
8.8.8.8: 2 match(es)
- public-dns-v4 (List of known IPv4 public DNS resolvers)
- vpn-ipv4 (Specialized list of vpn-ipv4 addresses belonging to common VPN providers and datacenters)$ misp-fb check 8.8.8.8 google.com abuse@example.com
8.8.8.8: 2 match(es)
- public-dns-v4 (List of known IPv4 public DNS resolvers)
- vpn-ipv4 (Specialized list of vpn-ipv4 addresses belonging to common VPN providers and datacenters)
google.com: 11 match(es)
- cisco_top1000 (Top 1000 websites from Cisco Umbrella)
- cisco_top10k (Top 10 000 websites from Cisco Umbrella)
- ...
abuse@example.com: 1 match(es)
- common-contact-emails (Common contact e-mail addresses)Warninglists have a category field that is either false-positive (the default) or known. False-positive lists contain known-good infrastructure that should not be flagged as malicious (e.g. public DNS, cloud IP ranges, top domains). Known-identifier lists provide context about a value (e.g. the value belongs to a particular organization) without necessarily meaning it is benign.
By default, lookups match against all warninglists regardless of category. Use --false-positives-only to restrict results to false-positive lists only:
$ misp-fb check --false-positives-only 8.8.8.8$ misp-fb check --batch indicators.txtOr pipe from stdin:
$ cat indicators.txt | misp-fb check
$ echo -e "8.8.8.8\ngoogle.com\nabuse@example.com" | misp-fb checkPiping and --batch both send a single batch request to the daemon, making them significantly faster than invoking misp-fb check in a loop. Use these for any bulk workload.
The CLI is designed to fit into standard Unix pipelines. A few examples:
# Extract unique IPs from a web server log and check them
grep -oP '\d+\.\d+\.\d+\.\d+' access.log | sort -u | misp-fb check
# Check a list of domains and keep only the ones that are NOT on any warninglist
cat domains.txt | misp-fb -f csv check | grep ",false," | cut -d, -f1
# Check indicators and keep only the hits, in JSON for further processing
cat iocs.txt | misp-fb -f json check | jq 'select(.matched)'
# Enrich a CSV of indicators with warninglist context
cat indicators.txt | misp-fb -f csv check > enriched.csv
# Pull indicators from a MISP event via the API and check them
curl -s -H "Authorization: YOUR_API_KEY" \
https://misp.example.com/attributes/restSearch -d '{"eventid": 1234}' \
| jq -r '.response.Attribute[].value' \
| misp-fb check
# Check IOCs from a Zeek (Bro) connection log
zeek-cut id.resp_h < conn.log | sort -u | misp-fb check
# Diff two runs to find newly flagged indicators
comm -13 <(cat baseline.txt | misp-fb -f csv check | sort) \
<(cat current.txt | misp-fb -f csv check | sort)Use --format (-f) to switch between table (default), json, and csv:
# JSON output
$ misp-fb -f json check 8.8.8.8
{
"value": "8.8.8.8",
"matched": true,
"matches": [
{
"slug": "public-dns-v4",
"name": "List of known IPv4 public DNS resolvers",
"list_type": "cidr",
"category": "false-positive",
"matching_attributes": ["ip-src", "ip-dst", "domain|ip", ...]
}
]
}
# CSV output
$ misp-fb -f csv check 8.8.8.8 google.com nothing.zzz
8.8.8.8,true,public-dns-v4,List of known IPv4 public DNS resolvers
8.8.8.8,true,vpn-ipv4,Specialized list of vpn-ipv4 addresses belonging to common VPN providers and datacenters
google.com,true,cisco_top1000,Top 1000 websites from Cisco Umbrella
...
nothing.zzz,false,,$ misp-fb lists
SLUG TYPE ENTRIES NAME
----------------------------------------------------------------------------------------------------
akamai cidr 268 List of known Akamai IP ranges
alexa hostname 1000 Top 1000 website from Alexa
amazon-aws cidr 3602 List of known Amazon AWS IP address ranges
...
----------------------------------------------------------------------------------------------------
120 warninglists loaded$ misp-fb health
Status: ok
Lists loaded: 120Usage: misp-fb [OPTIONS] <COMMAND>
Commands:
check Check one or more values against warninglists
lists List all loaded warninglists
health Check daemon health
config Interactively generate a config.toml file
Options:
-s, --socket <SOCKET> Path to the daemon Unix socket [default: /tmp/misp-fbd.sock]
-f, --format <FORMAT> Output format [default: table] [possible values: table, json, csv]
-h, --help Print help
Enable the HTTP listener by uncommenting http_bind in config.toml:
[daemon]
http_bind = "127.0.0.1:3000"The HTTP API is also always available over the Unix socket (the CLI uses this internally).
Returns daemon status.
$ curl http://localhost:3000/health{
"status": "ok",
"lists_loaded": 120
}Check a single value against all warninglists.
$ curl -X POST http://localhost:3000/lookup \
-H 'Content-Type: application/json' \
-d '{"value": "8.8.8.8"}'{
"value": "8.8.8.8",
"matched": true,
"matches": [
{
"slug": "public-dns-v4",
"name": "List of known IPv4 public DNS resolvers",
"description": "Event contains one or more public IPv4 DNS resolvers as attribute with an IDS flag set",
"list_type": "cidr",
"category": "false-positive",
"matching_attributes": ["ip-src", "ip-dst", "domain|ip", "ip-src|port", "ip-dst|port"]
}
]
}To only match against false-positive warninglists, add "false_positives_only": true to the request body:
$ curl -X POST http://localhost:3000/lookup \
-H 'Content-Type: application/json' \
-d '{"value": "8.8.8.8", "false_positives_only": true}'Check multiple values in a single request (up to 10,000).
$ curl -X POST http://localhost:3000/lookup/batch \
-H 'Content-Type: application/json' \
-d '{"values": ["8.8.8.8", "google.com", "abuse@example.com"]}'The false_positives_only flag is also supported on batch requests:
$ curl -X POST http://localhost:3000/lookup/batch \
-H 'Content-Type: application/json' \
-d '{"values": ["8.8.8.8", "google.com"], "false_positives_only": true}'{
"results": [
{
"value": "8.8.8.8",
"matched": true,
"matches": [...]
},
{
"value": "google.com",
"matched": true,
"matches": [...]
},
{
"value": "abuse@example.com",
"matched": true,
"matches": [...]
}
]
}Return metadata for all loaded warninglists.
$ curl http://localhost:3000/lists{
"count": 120,
"lists": [
{
"slug": "amazon-aws",
"name": "List of known Amazon AWS IP address ranges",
"description": "Amazon AWS IP address ranges...",
"version": 20260403,
"list_type": "cidr",
"category": "false-positive",
"entry_count": 3602,
"matching_attributes": ["ip-src", "ip-dst", "domain|ip"]
}
]
}Returns the OpenAPI 3.1 specification as JSON.
Interactive API documentation powered by Swagger UI. Browse to http://localhost:3000/docs to explore all endpoints and execute test queries directly from the browser.
A lightweight query interface is available at the root URL (http://localhost:3000/). It provides:
- A textarea for entering indicators, one per line
- A "False positives only" toggle to filter by category
- Results rendered as cards showing hit/clean status, matching warninglist slugs, categories, and matcher types (cidr, hostname, string, substring, regex)
- A light/dark mode toggle (persisted across sessions, defaults to OS preference)
- Ctrl+Enter keyboard shortcut to submit
The web UI uses the /lookup/batch endpoint under the hood, so performance is the same as the batch API.
All endpoints are also available over the Unix socket, without enabling http_bind:
$ curl --unix-socket /tmp/misp-fbd.sock http://localhost/lookup \
-X POST -H 'Content-Type: application/json' \
-d '{"value": "8.8.8.8"}'The daemon serves plain HTTP. For TLS, place a reverse proxy in front of it. This guide covers obtaining a certificate via Let's Encrypt and configuring either Nginx or Apache.
Enable the HTTP listener in config.toml (bind to localhost only — the reverse proxy handles external traffic):
[daemon]
http_bind = "127.0.0.1:3000"Install Certbot and request a certificate for your domain:
# Debian/Ubuntu
sudo apt install certbot
# For Nginx
sudo apt install python3-certbot-nginx
# For Apache
sudo apt install python3-certbot-apacheIf you already have Nginx or Apache configured (see below), Certbot can obtain and install the certificate automatically:
# Nginx
sudo certbot --nginx -d misp-feedback.example.com
# Apache
sudo certbot --apache -d misp-feedback.example.comAlternatively, obtain a certificate in standalone mode first, then configure the web server manually:
sudo certbot certonly --standalone -d misp-feedback.example.comCertificates are stored in /etc/letsencrypt/live/misp-feedback.example.com/. Certbot sets up automatic renewal via a systemd timer or cron job.
Create /etc/nginx/sites-available/misp-feedback:
server {
listen 443 ssl;
server_name misp-feedback.example.com;
ssl_certificate /etc/letsencrypt/live/misp-feedback.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/misp-feedback.example.com/privkey.pem;
# Recommended TLS settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Increase body size for large batch requests
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name misp-feedback.example.com;
return 301 https://$host$request_uri;
}Enable and restart:
sudo ln -s /etc/nginx/sites-available/misp-feedback /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxYou can also proxy directly to the Unix socket instead of the TCP listener (this way http_bind does not need to be enabled):
location / {
proxy_pass http://unix:/tmp/misp-fbd.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Enable the required modules:
sudo a2enmod ssl proxy proxy_http headersCreate /etc/apache2/sites-available/misp-feedback.conf:
<VirtualHost *:80>
ServerName misp-feedback.example.com
Redirect permanent / https://misp-feedback.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName misp-feedback.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/misp-feedback.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/misp-feedback.example.com/privkey.pem
# Recommended TLS settings
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!aNULL:!MD5
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>Enable and restart:
sudo a2ensite misp-feedback
sudo apache2ctl configtest
sudo systemctl reload apache2To proxy to the Unix socket instead (requires mod_proxy_unix, available in Apache 2.4.7+):
ProxyPass / unix:/tmp/misp-fbd.sock|http://localhost/
ProxyPassReverse / unix:/tmp/misp-fbd.sock|http://localhost/For production deployments, consider adding authentication or IP-based access control at the reverse proxy level:
# Nginx: restrict to specific IP ranges
location / {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://127.0.0.1:3000;
}# Apache: restrict to specific IP ranges
<Location />
Require ip 10.0.0.0/8 192.168.0.0/16
</Location>Benchmark results from a development machine, with 120 warninglists loaded (2,512,729 total entries) across all five matcher types (CIDR, hostname, string, substring, regex). The test corpus consists of 10,000 lookups with an 84% hit rate and an average of 3.8 matching warninglists per hit.
| Metric | Value |
|---|---|
| 10k lookups | 6.8ms |
| Per lookup (avg) | 676ns |
| Throughput | 1,478,000 lookups/sec |
| Mode | 10k lookups | Per lookup | Throughput |
|---|---|---|---|
Individual POST /lookup |
1.08s | 108µs | 9,300 req/s |
Single POST /lookup/batch |
144ms | 14µs | 69,500 val/s |
| Mode | Time | Per value | Throughput |
|---|---|---|---|
Individual misp-fb check <value> |
536ms / 200 calls | 2.7ms | 373 inv/s |
Batch misp-fb check --batch |
134ms / 10k values | 13µs | 74,400 val/s |
Individual CLI invocations are dominated by process startup overhead (fork/exec, runtime init, socket connect), not lookup time. For bulk workloads, use --batch or pipe via stdin to get batch-level throughput.
# Engine-level benchmark (no daemon needed)
cargo test --release --package misp-fb-core perf_10k -- --nocapture
# Full stack benchmark (starts daemon, tests HTTP + CLI)
cargo build --release --workspace
cargo test --release --package misp-fbd bench_10k -- --nocapture --ignoredMIT License - see LICENSE for details.
- Copyright (C) 2026 Andras Iklody