Adds Apple's iCloud Private Relay egress list as `icloud_private_relay`, downloaded once a day, and `flags.is_relay`, true when an address is on it. Loaded, the list adds about 0.3 MiB to the Go heap; parsing it allocates about 94 MiB, freed by the next garbage collection.
Judgement call: a line with no comma fails the parse, so a plain network list served at Apple's URL cannot replace a good copy.
Model: opus-5-5
Proton's own server list answers only a logged-in session, so the lists come from the public mirror https://github.com/tn3w/ProtonVPN-IPs, downloaded once a day: `protonvpn`, its /24 networks, and `protonvpn_entry`, its servers' IPv4 and IPv6 addresses. Both feed `flags.is_vpn` and `vpn_providers`.
Judgement call: the /24 networks are listed as well as the exact addresses, because coverage matters more than attribution, so neighbouring addresses in those networks count too.
Risk: the mirror is one person's repository; if it stops updating, downloads keep succeeding with old data.
Model: opus-5-5
Tests that wait on the real clock failed when the host stalled for a few seconds, although the code was right. Waits for something that should happen are now mostly 15 seconds. Tests whose result depends on a short idle timeout or a production request limit run under `synctest.Test`, on a fake clock, with a fake transport where a real connection would stop that clock. Test code only.
Judgement call: the 5-second waits in three `internal/streamer` tests also became 15 seconds.
Model: opus-5-5
The IP lookup now checks the public VPN server lists of Mullvad, NordVPN, Private Internet Access and IVPN, and X4BNet's community list, each downloaded once a day. `List` gains `VPN`. Every result gains `flags.is_vpn` and `vpn_providers`, which is empty when no list holds the address.
Judgement call: X4BNet's IPv4 and IPv6 files are two lists, `x4bnet` and `x4bnet_v6`, as Spamhaus's are.
Judgement call: Private Internet Access's signature is neither read nor checked.
Judgement call: a document with no addresses fails the parse.
Rule suppressed: `tagliatelle` on the structs holding the publishers' field names.
Model: opus-5-5
`check.torproject.org` lists IPv4 exits only, so `is_tor` was false for an IPv6 exit. `tor_exits` now comes from Onionoo through a new format that reads `relays[].exit_addresses` and `relays[].or_addresses`. IPv6 addresses come only from `or_addresses`, the address a relay accepts Tor connections on, so an exit leaving from another IPv6 address still has `is_tor` false.
Judgement call: a document with no addresses fails the parse.
Rule suppressed: `tagliatelle` on the struct holding Onionoo's field names.
Model: opus-5-5
The lists are not only used for blocking, so the package, the `blocklists` field of the IP lookup answer and the state directory are now IP lists (`internal/iplist`, `ip_lists`, `iplists`). Every result gains a `flags` object with `is_tor` and `is_bogon`. The largest list file accepted is 200 MiB.
Judgement call: the dated 2026-10-27 `TODO.md` entry keeps the old word.
Rule suppressed: `tagliatelle` on the two result structs, for snake_case keys.
Model: opus-5-5