A small, honeypot-confirmed IP blocklist. Every address here attacked a self-operated Cowrie/Heralding sensor directly — SSH, FTP, telnet, MySQL, VNC — and ages off automatically once it goes quiet.
- 541 IPs · 331 credential-tier · 177 scanner-tier · 33 loader-tier · updated
2026-11-06T07:00:01Z - Formats:
blocklist.txt(fail2ban/iptables drop-in) ·blocklist.json·blocklist.csv·blocklist.misp.csv(header-less, for MISP/OpenCTI positional ingestion) - Canonical source: https://jacobrakai.org/feed/ — regenerated hourly. This repo is a periodic snapshot; pull the URL if you want current data.
Because this one is original sensor data, not a re-aggregation. Every IP here hit my honeypot directly.
46.7% of the current list appears on no major public blocklist. Re-measured
2026-09-08 with scripts/overlap.py against the live 413-entry feed (previous
measurements: 2026-08-30, 231 entries, 52.4% novel; 2026-08-24, 153 entries,
64.7% novel; 2026-07-22, 181 entries, 63.5% novel):
| List | 2026-09-08 | 2026-08-30 | 2026-08-24 | 2026-07-22 |
|---|---|---|---|---|
| firehol_level1 | 8.2% | 14.3% | 22.2% | 13.8% |
| spamhaus_drop | 8.2% | 14.3% | 22.2% | 13.3% |
| firehol_level3 | 4.6% | 10.4% | 17.6% | 19.9% |
| blocklist_de | 44.6% | 32.5% | 9.8% | 14.4% |
| dshield | 1.0% | 1.3% | 0.0% | 1.7% |
| any of the above | 53.3% | 47.6% | 35.3% | 36.5% |
So just over half are attackers the big aggregates haven't listed. That's the point of a live sensor — and it's also the honest ceiling on this feed's value: it is small and it is one vantage point, so treat it as a supplement to the large lists, not a replacement. The novelty figure moves as both the feed and the aggregates change, so trust the measurement date above, not the number alone.
Reproduce it yourself: scripts/overlap.py.
AbuseIPDB reputation novelty is a separate check — see
cmd/blocklist-abuseipdb; overlap.py stays
aggregates and peer honeypot feeds only.
Worth knowing before you use this, because it determines whether the list is relevant to you. Distinct published IPs by the protocol they attacked, measured 2026-08-30 against the current 30-day observation window:
| Protocol | 2026-08-30 | 2026-07-22 |
|---|---|---|
| ssh | 95 | 33 |
| telnet | 74 | 126 |
| vnc | 35 | — |
| mysql | 22 | 26 |
| ftp | 11 | 6 |
| http | 4 | — |
(An IP that attacked several protocols is counted once per protocol, so the column sums to more than the 231 published addresses.)
The composition has flipped since mid-July. On 2026-07-22 telnet dominated (126 addresses, ~4× SSH's 33) — the IoT-botnet signature of many compromised devices each doing modest volume. As of 2026-08-30 SSH is the largest single protocol (95 addresses) and telnet has dropped to second (74): over that window the sensor's attacker population shifted from telnet/IoT sweeps toward SSH bruteforce. The window rolls, so check the measurement date before relying on the mix.
Comparing against other operators' honeypot feeds confirms the shift — overlap with dataplane.org's SSH feed has risen to 51.6%, now well above the 11.6% overlap with their telnet feed. On 2026-08-24 the ratio pointed the other way (5.2% SSH vs 33.3% telnet):
| Peer honeypot feed | 2026-09-08 | 2026-08-30 | 2026-08-24 | 2026-07-22 |
|---|---|---|---|---|
| dataplane sshpwauth (SSH) | 51.6% | 35.5% | 5.2% | 9.4% |
| dataplane telnetlogin (telnet) | 11.6% | 22.1% | 33.3% | 45.3% |
| greensnow | 39.0% | 26.4% | 6.5% | 3.3% |
| dataplane vncrfb (VNC) | 10.9% | 14.3% | 12.4% | 1.7% |
| any of the above | 75.5% | 72.7% | 52.9% | — |
So: this is currently an SSH-led list that also carries a large telnet/IoT population. If you are here for SSH bruteforcers, the feed now covers them well; if you are here only for telnet/IoT botnets, that tier is still substantial but is no longer the majority.
(Peer feeds are queried for measurement only. dataplane.org is non-commercial and prohibits redistribution — none of their data is in this feed, which remains entirely original sensor output.)
Three tiers. All require real attack activity within the last 30 days; dormant entries decay off on their own.
credential — connected to a decoy service and submitted login credentials
repeatedly. Honeypot-confirmed, never inferred.
scanner — submitted no credentials at all, but probed at abusive,
sustained volume against a single quiet host. This tier exists because
high-volume protocol scanners (pure VNC screen-scrapers, say) never submit
credentials and so never reach the credential bar, despite being the noisiest
traffic the sensor sees. The floors are 50 credential attempts for
credential tier and 1 000 connection events (in-window) for scanner tier;
both are published in the blocklist.txt header and blocklist.json meta and
enforced by the compiled validator in CI.
loader — delivered a payload. The address fetched a file into a honeypot
session (a wget/curl dropper stage) or uploaded one over SFTP/SCP (a
persistence key, a script). There is no volume floor: one payload is enough,
because a single login that drops a loader is more dangerous than a thousand
that do not. An address that also meets the credential bar is listed as
credential; loader is for the ones that would otherwise be missed. The
payloads themselves are never published.
- Private, reserved, loopback, link-local, CGNAT and multicast ranges.
- Known benign internet-survey scanners — Censys, Shodan, Rapid7 Sonar, Shadowserver, BinaryEdge. This filter matters most for the scanner tier, which benign surveys would otherwise land in by definition.
- Any IP that did submit credentials but isn't confirmed yet is held out of the scanner tier rather than published as credential-less.
ip, tier, bans, attempts, first_seen, last_seen, first_banned, asn.
This feed is IPv4-only. Every address published is a global IPv4 address,
and the compiled blocklist-validator refuses to publish anything else. This
is a contract, not an accident of the data: the ipset/iptables recipes below create family inet
sets, which reject an IPv6 address rather than blocking it — a v6 entry would
land in a feed you already trust and silently leave you unprotected. If the
sensors ever observe v6 worth publishing, it will arrive as a deliberate change
with the consumer recipes updated alongside it, not as a surprise row.
attempts is lifetime credential attempts for credential-tier entries, and
in-window connection events for scanner- and loader-tier ones. bans counts
credential-tier ban cycles; it is always 0 for scanner- and loader-tier
entries because their ban cycles are tracked separately.
attemptsfor a listed address may keep rising afterfirst_banned. That is expected, not a bookkeeping error — do not readfirst_bannedas "traffic stopped".
first_seen and last_seen are both observations — attack activity inside
the current window — so first_seen <= last_seen always holds. first_banned
is bookkeeping: when the address was first ban-listed. It may predate the
window or fall after last_seen, and is null for scanner- and loader-tier
entries. Don't use it to reason about recency.
meta.schema_version is MAJOR.MINOR, currently 1.0.
Column order is load-bearing: blocklist.misp.csv is header-less and MISP and
OpenCTI address its columns positionally, so a reordered feed does not fail
for them — it silently lands every field in the wrong place. The version is how
a consumer notices before that happens.
- MINOR bump — a column was appended. Existing readers keep working.
- MAJOR bump — a column was renamed, removed, or reordered. Pin on MAJOR and refuse a feed whose MAJOR you do not recognise, rather than parsing it anyway.
The compiled validator fails CI on an unrecognised MAJOR and warns on MINOR drift. During its shadow period, CI also requires exact stdout and exit-code parity with the frozen Python validator on the current publication corpus.
Repository validation is read-only:
go run ./cmd/blocklist-validator .
make checkThe blocklist moves from sensor to consumer through four stages:
- Sensor generates — the Cowrie/Heralding sensor records attack events and
the generator emits updated
blocklist.txt,blocklist.json,blocklist.csv, andblocklist.misp.csvfiles. - Commit — the generated files are committed to this repository (hourly snapshots are pushed without human review; structural changes go through PRs).
- CI validates — GitHub Actions runs
make check, which compiles and runs the Go validator against the committed publication files, checks format parity with the frozen Python oracle, rejects secret-like material, enforces the compiled-language-only rule, and runs the Go vulnerability scanner. CI is the gate between the generator and every consumer. If it fails, the committed files are not safe to consume. - Live — if CI passes, the files are served at
https://jacobrakai.org/feed/(mirrored fromraw.githubusercontent.com). Consumers pulling the canonical URL always get the latest CI-validated snapshot.
The build badge at the top of this README reflects the latest validation run. If it is red, do not update your firewall rules. Instead:
- Check the open issue labelled
ci-failure— it contains the failing commit SHA, the workflow run link, and the validator output. - Keep using your last known-good blocklist until the issue is closed and the badge turns green. CI automatically closes the issue when validation passes again.
The sensor-side generator does not run the full publication validator before
committing. Validation is CI-only: the Go validator (cmd/blocklist-validator)
and the Python parity oracle (validate.py) run against the committed files
during make check in GitHub Actions. The scripts/overlap.py script is an
analysis tool for measuring feed novelty against public blocklists, not a
pre-commit gate. This means a bad generator push will land in the repo but will
be caught by CI before consumers pull it — and the failure-alert step above
ensures it is visible immediately rather than silently waiting for someone to
notice.
See configs/fail2ban-example.md. Point your
refresh at https://jacobrakai.org/feed/blocklist.txt rather than at this repo,
and refresh hourly. The recipes use an atomic swap pattern — each refresh
replaces the ipset/nftables set rather than appending, so IPs that have aged
off the feed are automatically removed.
blocklist.txt works as a freetext feed with no configuration — it is bare
one-IP-per-line, the same shape MISP already ingests from blocklist.de.
To keep the per-IP metadata, use the header-less CSV as a csv feed with
value: 1, delimiter: ,:
https://jacobrakai.org/feed/blocklist.misp.csv
Use that URL rather than blocklist.csv: MISP's CSV parser skips only
#-prefixed lines, so the normal file's header row would be ingested as a data
row and produce a junk attribute on every refresh.
Point a CSV Feed ingester at blocklist.misp.csv with a CSV Mapper. OpenCTI
addresses columns by letter index and skips the first line, so the header-less
variant is the correct target there too.
ip, tier, bans, attempts, first_seen, last_seen, first_banned, asn
Both MISP and OpenCTI map columns positionally, not by name. This order is therefore permanent — new columns are only ever appended on the right, never inserted or reordered. The compiled validator enforces both the order and exact value equality with the headed CSV in CI.
This feed is original sensor data. It is complementary to AbuseIPDB, not a re-aggregation of it, and AbuseIPDB listings are never merged back into the published files.
The sensor dataset already informs Jacob's AbuseIPDB reporting (user 263386). This repository ships operator tooling only — it does not replace sensor-side cron:
go run ./cmd/blocklist-abuseipdb check -dry-run
go run ./cmd/blocklist-abuseipdb check -limit 5
go run ./cmd/blocklist-abuseipdb report
go run ./cmd/blocklist-abuseipdb report -submitcheck and report -submit read ABUSEIPDB_API_KEY from the environment
(or a GitHub Actions secret of the same name, if you add a private workflow).
The key is sent as the Key header and is never logged. report is dry-run
unless -submit is set. This repo's CI does not call AbuseIPDB.
Standard daily limits are 1000 checks and 1000 reports, resetting at 00:00 UTC.
The same IP should not be reported more often than every 15 minutes; -submit
records recent posts in /tmp/honeypot-blocklist-abuseipdb-state.json by
default (-state to override).
Published JSON has no protocol field, so category mapping uses tier:
credential → 18,22 (SSH brute-force); scanner → 14,15; loader → 15,19
(payload evidence is the tier). An optional protocol field is honored if
you point -path at richer local JSON.
This project is not listed on AbuseIPDB's integrations page and does not need to be. API reference: https://docs.abuseipdb.com/.
IPs are dynamic and get reassigned. If your address is here in error, email jacob@jacobrakai.org — entries also expire automatically as attacks stop.
CC0-1.0 — public domain, provided as-is, no warranty. Verify before blocking.
Donate / Support Jacobrakai Foundation — JACOBRAKAI FOUNDATION 501(c)(3)
JACOBRAKAI FOUNDATION (Jacobrakai Foundation) is a 501(c)(3) public charity. EIN 33-3382083 · effective Feb 11, 2025. No Letter 947 PDF URL is available to publish.