Integration guide
Feed IP reputation into Suricata
An IDS is the one place where a scored feed beats a blocklist. Suricata's iprep engine attaches a reputation value to an address and lets rules compare against it, so instead of "block or do not block" you can alert at one score, drop at another, and carry the reputation into the event record for whoever triages it later.
That means this guide uses the scored feed (feed.json), not the blocklist.
The categories file
Suricata needs a category before it will accept reputation data. The format is <id>,<short name>,<description>, with ids from 1 to 60:
# /etc/suricata/iprep/categories.txt
1,SciScope,SciScope Scanner Feed - scored scanners and attackers
The categories file is not reloaded on a ruleset reload. Write it once; if you ever change it, Suricata needs a full restart.
The reputation file
The format is <ip>,<category>,<score> with the score in the range 1–127. Our feed scores 0–100, so it maps directly with no scaling needed: a score of 87.4 becomes 87.
#!/bin/bash
# /usr/local/sbin/sciscope-iprep-update
# Convert the scored SciScope feed into a Suricata iprep file.
set -euo pipefail
KEY_FILE=/etc/sciscope/key
URL=https://api.sciscope.ee/v1/feeds/feed.json
IPREP=/etc/suricata/iprep/sciscope.list
CATEGORY=1
MIN_ENTRIES=500
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
curl -fsSL --max-time 60 --retry 3 --retry-delay 5 \
-H "Authorization: Bearer $(cat "$KEY_FILE")" \
-o "$tmp/feed.json" "$URL"
# One "ip,category,score" line per entry; score clamped into 1..127.
jq -r --argjson c "$CATEGORY" '
.indicators[]
| [ .ip, $c, ([[(.score | floor), 1] | max, 127] | min) ]
| @csv' "$tmp/feed.json" | tr -d '"' > "$tmp/sciscope.list"
count=$(wc -l < "$tmp/sciscope.list")
[ "$count" -ge "$MIN_ENTRIES" ] || {
echo "sciscope: refusing to install $count entries (< $MIN_ENTRIES)" >&2; exit 1; }
install -m 0644 "$tmp/sciscope.list" "$IPREP"
kill -USR2 "$(cat /var/run/suricata.pid)" # reload rules + reputation
echo "sciscope: installed $count reputation entries"
feed.json is a single object with a count, a generated_utc and an indicators array; each indicator carries ip, score, tags, first_seen, last_seen, signal_events_30d and coarse geo and ASN context. Only the first two fields are needed here, but the others are why the same file is worth sending to a SIEM as well.
kill -USR2 is the supported way to reload: it reloads the rules and the IP reputation data, and Suricata cleans up the previous reputation table once the new one is in place. Suricata keeps running throughout, so this is safe on an hourly timer.
Configuration
# suricata.yaml
reputation-categories-file: /etc/suricata/iprep/categories.txt
default-reputation-path: /etc/suricata/iprep
reputation-files:
- sciscope.list
Rules that use it
The iprep keyword takes a side, a category short name, an operator and a value:
# Anything with any SciScope reputation at all: visibility, not enforcement.
alert ip any any -> $HOME_NET any (msg:"SciScope: scored source"; \
iprep:src,SciScope,>,0; \
classtype:misc-activity; sid:9200001; rev:1;)
# Higher-confidence sources reaching a listening service.
alert ip any any -> $HOME_NET any (msg:"SciScope: high-confidence scanner"; \
iprep:src,SciScope,>,60; \
classtype:attempted-recon; sid:9200002; rev:1;)
# Reputation as a *modifier*: a generic signature matters more from a scored source.
alert http any any -> $HOME_NET any (msg:"SciScope: path traversal from scored source"; \
flow:established,to_server; http.uri; content:"../"; \
iprep:src,SciScope,>,40; \
classtype:web-application-attack; sid:9200003; rev:1;)
That third rule is the one that pays for the integration. Reputation used on its own turns into a second blocklist with extra steps; reputation used to qualify other signatures is what lifts a noisy generic rule into something worth waking a human for.
Keep your sids in a private range (Suricata reserves 1000000–1999999 for local rules; anything clearly your own and consistent works) so a ruleset update never collides.
Scheduling and verification
Reuse the systemd timer from the nftables guide, pointed at this script. Then confirm the data is really loaded rather than assuming it:
# entries in the file Suricata is reading
wc -l /etc/suricata/iprep/sciscope.list
# did the reload take? (look for the reputation reload lines)
sudo journalctl -u suricata --since "10 min ago" | grep -i -e iprep -e reload
# are the rules firing?
sudo grep -c '"signature_id":9200001' /var/log/suricata/eve.json
If the rules never fire, check the order of operations: an iprep rule matches only if the address is in the reputation file and the category name in the rule matches the short name in categories.txt exactly. That name is case-sensitive, and a typo there fails silently.
Passing reputation into your SIEM
Because the match happens in Suricata, the reputation is on the event: the alert carries the signature, the flow, and, with iprep in the rule, the fact that the source was scored. That travels into eve.json and onward without any enrichment step in your pipeline, which is considerably cheaper than looking every address up at search time.
Or let the feed do it for you
Keeping these lists current is exactly the work the SciScope Scanner Feed takes off your hands: every crawler above is screened out of the feed continuously, and the opt-in crawler-identity list tells you which of them you are looking at.
How the feed works or request a trial