# Edge reputation blocking

**Block the networks reputable third parties have listed, with the listing shown. Free, keyless, no account, nothing on your request hot path, and it does not block anything until you tell it to.**

## What it is

A pre-resolved snapshot of listed address space that your own web server or resolver consults locally. There is no API call per request, no sidecar, no account, and no key. You fetch one file on a timer and your server answers from memory.

It exists because the useful version of this is the one an operator can install in an afternoon without talking to us. A reputation service you have to integrate with, authenticate to and pay for is a service most of the people who need it will never install.

## The default is *not* blocking

This is the part worth reading twice. In `log` mode, which is the default in all three web-server modules, the module writes the verdict it *would* have acted on into your logs and acts on nothing:

```
127.0.0.1 200 verdict=high
```

and, with the citable evidence attached, so the answer to "why would you have blocked this?" is in your own logs rather than in a support ticket:

```
2026/10/09 09:30:28 [warn] 3943026#3943026: *1 whisper edge: WOULD DENY 127.0.0.1, verdict high,
evidence SBL-EXAMPLE-EVIDENCE-12345 abuse@example.net (mode is log, request allowed; 1 would-deny so
far, 1 since the last line), client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host:
"127.0.0.1:8098"
```

Both are real captures from the module running inside a real server in its own acceptance gate, which is why the client is the loopback and the evidence id is the gate's synthetic one. Against your traffic the address is your visitor's and the evidence is the real listing id and abuse contact the snapshot carries for it.

Run it that way against your own traffic for a week. Read what we say about your real visitors. Then turn on `deny` yourself, if you want to. A false positive that takes a stranger's site off the air ends this experiment, and the people who install it will have heard about it from a peer rather than from us.

## Four servers, one core

A single C core owns the snapshot format, the integrity check, the loading, the atomic swap, the lookup and the deny rule. The web-server modules are thin shims over it, so the hot path is written once and measured once rather than three times with three sets of bugs.

| | nginx | Apache httpd | OpenLiteSpeed / LiteSpeed | BIND |
|---|---|---|---|---|
| Default mode | `log` | `log` | `log` | n/a (a zone you choose to load) |
| Finest control | per `location` | per `<Location>` | **per virtual host** | per view |
| Verdict exposed as | `$whisper_edge_verdict` | `%{WHISPER_EDGE_VERDICT}n` | `%{WHISPER_EDGE_VERDICT}e` | your resolver's RPZ log |
| Threading | one thread per worker | **prefork, worker and event** | threaded | n/a |

LiteSpeed is not Apache-module-compatible. It reads Apache *directives*, which makes it easy to assume it loads Apache `.so` modules too, and it does not, so it gets a real LSIAPI module rather than a confident README. Its control granularity really is per site rather than per location, which is a property of the server and not of this code.

## For a resolver instead

The same policy ships as a Response Policy Zone, which protects every device behind the resolver rather than one web server. Two lines of `named.conf` and a zone file:

```
curl -fsS -o /var/named/rpz.zone https://whisper.online/edge/rpz.zone
```

We prove that claim against a real BIND rather than against our reading of the RPZ specification: the acceptance gate loads the generated zone into `named` and asserts what BIND actually does with it.

## What it will never block

A published hold-harmless list of 248 distinct prefixes that this feed will never flag, each with the date and the method by which it was established: the DNS root and TLD nameservers, the large public resolvers, NTP, the CA, ACME, OCSP and CRL endpoints, the RIR and RPKI infrastructure, and RFC-reserved space.

The test for inclusion is **un-occupiable**, not operated-by-us. An exemption anyone can rent an address inside is a published bypass of the feed, with instructions, so our own agent egress prefix is *not* on that list and our nameservers appear as individual host addresses rather than as the cloud prefixes around them. A reputation feed exempting its own vendor's address space is the oldest conflict of interest in this business.

## It fails open, always

No data, bad data, an unreadable file, a snapshot past its own validity window, or no configuration at all: every one of those means allow. The validity bound travels *inside* the snapshot, so a feed nobody is maintaining any more lapses rather than hardening, and that decision cannot be lost in somebody's configuration.

## Audit us rather than trust us

Every published snapshot is anchored in our transparency ledger, which is append-only and whose checkpoints are cosigned by independent witnesses. The anchor is keyless like the rest of the feed:

```
$ curl -s https://whisper.online/edge/snapshot.ledger.json
{
  "generation": 497650,
  "node": "ns1-ams",
  "sha256": "2fcc426e87499a5a0494044bdd9a6ebbd4a88a8a38d52b23ebad27dfe9004445",
  "entryId": "0873c748cf9bef5c92fe7b036a4fe1bab6db17e89b6930f0268741639101955f",
  "proof": "/inclusion?leaf=0873c748cf9bef5c92fe7b036a4fe1bab6db17e89b6930f0268741639101955f"
}
```

Follow `proof` and you get an RFC 6962 inclusion proof against the checkpoint the witnesses signed: leaf hash, audit path, tree size and the signed note. Fold the leaf up the path to the root and compare it with the note. A snapshot we later changed our mind about is still in the log, and a snapshot that is not in the log is not one we published.

The `entryId` is published rather than left for you to derive, because deriving it would mean reimplementing our leaf encoding, and a claim only checkable by people willing to do that is not really checkable.

Both nameservers anchor independently, so one build hour normally has two leaves, one per node, sitting next to each other in the same tree. That is deliberate: the log records what each node actually served rather than one tidy number that would be wrong for whichever node you fetched from.

And the snapshot is signed. `snapshot.bin.sig` is a detached Ed25519 signature over the whole file, and `edge-signing-key.pub` is the key that verifies it:

```
$ curl -fsS https://whisper.online/edge/edge-signing-key.pub -o edge.pub
$ curl -fsS https://whisper.online/edge/snapshot.bin.sig -o snapshot.bin.sig
$ openssl pkeyutl -verify -pubin -inkey edge.pub \
      -rawin -in snapshot.bin -sigfile snapshot.bin.sig
Signature Verified Successfully
```

Four checks, and they are not the same check. The digest says the bytes did not get corrupted on the way. The transport says they came from whisper.online at the moment you asked. The ledger says we published exactly those bytes and have not rewritten the claim. The signature is the only one that still works when the file reaches you from somewhere else, which for a file this cheap to pass around is how it will usually arrive.

Fetch the key once and keep it. And check the key itself on that first fetch, because trusting a published key because it arrived is the one real weakness of publishing one: the key is signed with our release key, the same one `install.sh` verifies every CLI download against, so you can break that circle with something you already have.

```
$ curl -fsS https://as219419.net/whisper-release.asc | gpg --import
$ curl -fsS https://whisper.online/edge/edge-signing-key.pub.asc -o edge.pub.asc
$ gpg --verify edge.pub.asc edge.pub
gpg: Good signature from "Whisper Security ... AS219419 <security@whisper.security>"
```

Stated rather than implied: **the module does not verify the signature in-process.** Whoever can write the snapshot file decides what your server blocks, and the loader already refuses a group- or world-writable one, so the attacker an in-process check would add cover for is root, who can replace the module or your server binary anyway. The alternative costs either a vendored Ed25519 implementation inside a file you are told to copy into your build, or a libcrypto dependency in a core whose whole build instruction is one command with no dependencies. A verification bug in either would be worse than no signature, because this page would claim verification. So the check lives in the refresh recipe, as one command you can read.

## The latency claim, honestly

Looking up a verdict makes no syscall, takes no lock and allocates nothing. That is checked by disassembling the compiled lookup at three optimisation levels on both x86_64 and aarch64, rather than asserted.

**No address in the IPv4 space can take more than six binary-search probes** on the current build, computed over all 65,536 index buckets rather than sampled, with the builder refusing to publish a snapshot where that number is exceeded. That is the figure worth quoting, because it covers every address rather than the ones a benchmark happened to draw. A sampled mean on an idle machine is around 10 ns per lookup; we do not quote a percentile from that measurement, because it is derived from batch means and is not a sound tail.

## Next

The snapshot, the policy zone, the manifest and the hold-harmless list are all served under [whisper.online/edge/](https://whisper.online/edge/) with no key and no account. The modules, their worked configurations and their acceptance gates ship as source you can read before you run it.
