Two Identical Rows in Our Blocklist. One You Should Block, One You Shouldn't.

Here are two rows from the blocklist we publish. They went out on the same day, in the same lane, at the same confidence.
The first is 47.238.126.208. The second is 104.28.246.148. Both carry confidence 95, both are categorized as blocked, both are sourced from our own edge decoys. Both are true: we watched each of them ask our infrastructure for credentials.
One of them you should block. The other you should not, and if you do, you will hit thousands of people who have never attacked anyone.
Nothing in the file tells you which is which. That was our problem to fix, and we fixed it before publishing this.
What the two addresses actually are
47.238.126.208 is a virtual machine on Alibaba Cloud in Hong Kong. Somebody rented it. It is theirs until they stop paying for it, which means it is durable, it is attributable to whoever holds that account, and blocking it costs nobody else anything. That is actor infrastructure, and a blocklist is exactly the right response.
104.28.246.148 is a Cloudflare WARP exit. WARP is a consumer VPN — a free one, used by a very large number of ordinary people. The address is a shared doorway. Our own record shows the client behind it geolocated to Bangkok, and by now that exit has almost certainly been handed to somebody else entirely.
Blocking it does two things, both bad. It fails to stop the actor, who gets a different exit on their next connection. And it blocks every other person who happens to be routed through that exit — people reading your site, people buying from you, people with no connection to any of this.

We went and counted
We pulled the live published file — the actual artifact a subscriber downloads, not our internal index, because those are different populations by design — and resolved the network operator for every bare address in it. All 5,942 of them.
3,043 (51.2%) are actor infrastructure. Rented virtual machines, hosting, compromised servers. Block away.
53 (0.9%) are borrowed egress. Twenty-seven Cloudflare WARP exits, twenty-four mobile carrier addresses behind carrier-grade NAT, and two Tor exits. Every one of them shipping at or above the default confidence floor, which means a subscriber pulling our feed with default settings was getting all of them.
39 (0.7%) we could not resolve a network operator for at all.
2,807 (47.2%) we cannot classify.
That last number is the one we are least comfortable with, and it is the reason this post exists in the shape it does.
Two notes on precision, because a feed is a moving target. Those figures are from the census run that prompted this work; when we pulled the live file again an hour later to verify the shipped column, the borrowed-egress count read 52 rather than 53 — the feed is a rolling window and rows age out of it. And the live column reports a further 1,811 rows as unknown that the census never covered: those are network blocks rather than single addresses, which is a different question we have not answered here.
The honest headline is the 47%, not the 53
It would be easy to write this as "we found 53 bad rows and fixed them." That framing is flattering and it is not the most important fact.
Our classifier matches the network operator's name. That is weak evidence. It is not a measured verdict about behavior; it is pattern matching on a string that a registrar filled in. When the name does not clearly indicate either a hosting provider or a shared exit, we return unclassified rather than guessing.
We could have made that number look much better by defaulting unclear cases to actor infrastructure. The file would then have shown 98% confidently classified. It would also have been worth nothing, because the 47% would be a guess wearing a label.
So: just over half of what we publish, we can now say something useful about. Just under half, we cannot. That is the true state and we would rather publish it than dress it.
The classifier got it wrong, and here is how we found out
Before trusting any of the above, we read the output.
The first version of the classifier filed AS-GLOBALTELEHOST — GTHost as a mobile carrier. GTHost is a virtual private server provider. The reason is that our pattern looked for the string "lte" — as in the mobile standard — and the word "globaltelehost" contains it. Globaltelehost.
That single mistake would have told subscribers "do not block this, it is shared infrastructure" about a box an attacker had rented. It is the exact inverse of the error we were trying to fix.
The same pass also swept in Cable & Wireless Panama and Airtel Broadband Telemedia, on "wireless" and "airtel" — both fixed-line carriers, not carrier-grade NAT. We narrowed the patterns to word boundaries and to the mobile arms specifically, and pinned each of those three cases with a regression test.
This is a general hazard and we keep walking into it: reading an identifier's name instead of checking its behavior. We have criticized federal advisories for publishing GitHub and Cloudflare addresses as indicators of compromise. We then did a version of the same thing, caught it only because we looked at the list by hand, and we are telling you because the alternative is pretending our heuristics are measurements.
What we shipped instead of suppressing the rows
The tempting fix is to drop those 53 rows below the publishing threshold so they disappear from the default file. We did not do that, and the reason matters.
Every one of those observations is true. We watched those addresses probe us. A Tor exit or a WARP exit that attacks you has genuinely attacked you, and throwing the observation away destroys real information. We have a separate process that removes rows which are merely property assertions — "this address is a Tor exit" is a fact about a host, not an observation of malice — and its scope note says explicitly that an exit we observed attacking is different evidence that it never touches. We agree with our own rule.
So we published the missing dimension rather than deleting the row. The feed can now tell you, per address, whether it is the actor's own host or a doorway they borrowed. The confidence threshold stays where it has always been: your dial, not ours. We score honestly; you set your posture.
The column is opt-in, behind a query parameter, and that is deliberate. This file drops straight into OPNsense, pfSense and Suricata — we watch those user agents pull it daily — and a tool that parses by column position breaks when a sixth field appears. Default output is byte-identical to what it was yesterday. Nobody's firewall changes because we learned something.
The bug that nearly undid all of it
An automated review caught this before it shipped, and it is worth repeating because it inverted the entire intent.
The file is cached. The cache key included the day, the confidence floor, the row limit and the caller's tier — but not the new parameter. So the first request asking for the extra column would have stored a six-column file under the ordinary key, and every subsequent default pull would have received it.
We made the column opt-in specifically so positional parsers would not break. Without that one line, we would have broken all of them instead of none of them.
The tell was sitting four lines above the bug: a comment stating that the only per-caller input to the body was the tier. Our change made that sentence false and we did not update it. A stale comment next to a contract is a defect notice, and we walked past it.
If you consume our feed
Nothing you have changes. If you want the extra dimension, add the parameter and you get a sixth field reading actor-infra, shared-egress, unclassified or unknown.
Three things to hold onto whichever feed you use, ours or anyone's:
An address is not an actor. The question "is this host controlled by the attacker, or is it a shared exit they are passing through" determines whether blocking is effective or merely expensive. Very few feeds answer it, including ours until today.
Shared egress rotates. A VPN or CGNAT address that attacked you this morning belongs to somebody else this afternoon. The durable indicator is the behavior, not the number.
Ask what fraction of any feed is unclassified. If the answer is zero, something is being guessed.
The accounting
This post makes 21 checkable factual claims. Every number was measured against the live published artifact on 26 October 2026, with network operators resolved via Team Cymru: the 5,942 total, the four-way split, the 27/24/2 breakdown, and the three misclassifications we corrected.
Two things we changed during the work rather than after: the classifier false positives described above, and the cache key. Both are in the record because both were nearly shipped.
We cap ourselves at 95% on anything. The classification is a heuristic over operator names, so it is wrong somewhere in that 51% we did classify, and we will correct it at this address when we find out where.
The feed is free. The accuracy problems are ours to find before you do.
Every indicator in this post is in the feed. Free.
1.58M+ IOCs, STIX 2.1 / TAXII, 88% novel vs ThreatFox, exploited-CVE leads ahead of CISA. No credit card — a free API key in 30 seconds, and you can audit every claim above against the live endpoints.
Get your free key → analytics.dugganusa.com/stix/register
Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. If the box below does not load, the same question lives at https://analytics.dugganusa.com/nps.html?post=actor-infrastructure-or-borrowed-doorway-october-2026