Integration guide
Load an IP blocklist into MikroTik RouterOS address lists
RouterOS has a firewall address list, a fetch tool and a scheduler, which is everything this needs. What it does not have is a native importer for a plain list of addresses: /import expects RouterOS script commands, not one address per line. So the script below fetches the plain list and converts it on the router.
Tested against RouterOS 7. On RouterOS 6 the same approach works but :parse and some string functions behave differently; upgrade if you can.
Set the firewall rule up first
Create the rule before the list exists. An address list with no members matches nothing, so this is safe to install ahead of time:
/ip firewall raw
add action=drop chain=prerouting src-address-list=sciscope \
comment="SciScope Scanner Feed" place-before=0
The raw table is the right place for a volume blocklist: it drops before connection tracking, so blocked traffic costs you almost nothing. Use /ipv6 firewall raw for the v6 equivalent if you take the v6 list.
The update script
# /system script, name: sciscope-update
:local key "YOUR_KEY"
:local url "https://api.sciscope.ee/v1/feeds/blocklist.txt?key=$key"
:local listName "sciscope"
:local tmpName "sciscope-new"
:local minEntries 500
# 1. fetch to a file; abort on any failure, leaving the current list alone
:do {
/tool fetch url=$url mode=https dst-path="sciscope.txt"
} on-error={
:log error "sciscope: fetch failed, keeping existing list"
:error "fetch failed"
}
:delay 2s
:local content [/file get "sciscope.txt" contents]
:local added 0
# 2. build the new list under a temporary name
/ip firewall address-list remove [find list=$tmpName]
:local pos 0
:local len [:len $content]
:while ($pos < $len) do={
:local nl [:find $content "\n" $pos]
:if ([:typeof $nl] = "nil") do={ :set nl $len }
:local line [:pick $content $pos $nl]
:set pos ($nl + 1)
# skip blanks and comments
:if ([:len $line] > 6 and [:pick $line 0 1] != "#") do={
:do {
/ip firewall address-list add list=$tmpName address=$line \
comment="sciscope"
:set added ($added + 1)
} on-error={}
}
}
# 3. only swap if the fetch produced a credible list
:if ($added < $minEntries) do={
/ip firewall address-list remove [find list=$tmpName]
:log error "sciscope: only $added entries, keeping existing list"
:error "too few entries"
}
/ip firewall address-list remove [find list=$listName]
:foreach i in=[/ip firewall address-list find list=$tmpName] do={
/ip firewall address-list set $i list=$listName
}
:log info "sciscope: installed $added entries"
Note what the script does not do: it never removes the live list until it has a complete, credible replacement. Steps 2 and 3 are the whole reason it is longer than a one-liner. A naive version that clears sciscope and then adds addresses one at a time leaves the router unprotected for however long the loop runs, and on a modest router, with several thousand entries, that loop is not instant.
The rename in step 3 is not atomic the way ipset swap is; RouterOS has no such primitive. It is, however, fast (a metadata change per entry, with no fetching or parsing in between) and it happens with a fully built list already in hand.
Schedule it
/system scheduler
add name=sciscope-update interval=1h on-event="/system script run sciscope-update" \
start-time=startup comment="Refresh the SciScope blocklist"
start-time=startup gives you a run shortly after boot, which matters because address lists do not survive a reboot.
Watch the memory
This is the real constraint on small hardware. Every address-list entry costs memory, and a router with 64 MB of RAM will not hold tens of thousands of them alongside a routing table and a connection-tracking table. Check before and after:
/system resource print
/ip firewall address-list print count-only where list=sciscope
If the entry count is more than your router can carry comfortably, do not clip the list arbitrarily: ask us for a smaller high-confidence slice, which is a scoring threshold change on our side rather than a truncation on yours.
Checking it
# how many entries are loaded
/ip firewall address-list print count-only where list=sciscope
# is a specific address on it?
/ip firewall address-list print where list=sciscope address~"192.0.2"
# is the rule actually dropping anything?
/ip firewall raw print stats
A note on the key in the URL
/tool fetch can send custom headers on RouterOS 7 (http-header-field="Authorization: Bearer ..."), and that is the better option if your version supports it. The key then stays out of the URL. Either way the key sits in the script body, which means it is in every router export and backup you take. Treat those as secrets, and mail hello@sciscope.ee for a rotation if one goes somewhere it should not have.
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