# A VPN and relay policy that does not punish real people

Relay users are ordinary Apple customers and VPN users are often paying ones. How privacy_service, is_relay and risk_signals let you challenge instead of block.

Date: 2026-09-04

Blocking VPN traffic used to be a cheap way to cut fraud. It is now a cheap way to lose customers. Security-conscious people, remote workers on a corporate tunnel, travellers on hotel networks, and every iPhone owner who turned on iCloud Private Relay arrive through an anonymising network, and most of them are exactly who you want. The question is no longer "is this a VPN" but "which one, and what should that change". This guide lays out a policy that answers it with the fields in one lookup.

## Three anonymisers, three different people

The response separates the networks that hide an address into three flags, because they carry three different meanings.

`is_tor` is a listed Tor exit, from the Tor Project's own bulk exit list (https://check.torproject.org/torbulkexitlist, checked 2026-09-04). Tor is a general-purpose anonymity network with a real population of journalists, researchers and people in censored countries, and a real population of abuse. It carries the highest weight, 70, because the operator of an exit has no relationship with the person behind it and no way to hold them accountable.

`is_vpn` is a known commercial VPN exit. Three providers publish their server lists and are matched by name, Mullvad, NordVPN and Private Internet Access, with `privacy_service` naming the one that matched; the open X4BNet list covers the rest without a name. A VPN customer has an account, often a paid one, with a provider that can be served legal process. That is a very different person from a Tor exit, which is why the weight is 30.

`is_relay` is an iCloud Private Relay egress address, from the list Apple publishes at https://mask-api.icloud.com/egress-ip-ranges.csv, complete with the city Apple assigns to each range. Private Relay is on by default for iCloud+ subscribers in Safari and hides the user's address from websites while still placing them in roughly the right region (Apple's description: https://support.apple.com/en-us/102602, checked 2026-09-04). A relay user is an ordinary Apple customer who did nothing but pay for storage. The weight is 10, and it comes with two rules that keep it honest.

## The two relay rules

Relay egress runs on CDN infrastructure, so the same addresses sit inside datacenter ranges. Without a rule, every relay user would score 40 for datacenter plus 10 for relay and land in the medium band as a suspected bot. So the datacenter weight is skipped when `is_relay` is true. The `is_datacenter` flag itself still reports true, because the address really is on hosting infrastructure; only the arithmetic changes, and `risk_signals` shows it:

```json
"is_datacenter": true,
"hosting_provider": null,
"is_vpn": false,
"is_relay": true,
"privacy_service": "iCloud Private Relay",
"is_anonymous": true,
"risk_score": 10,
"risk_level": "low",
"risk_signals": [
  { "signal": "datacenter", "weight": 0, "dataset": "datacenter-ranges" },
  { "signal": "relay", "weight": 10, "dataset": "relay-ranges" }
]
```

The second rule concerns the aggregate VPN list, which classifies Apple's relay ranges as VPN space. Apple's own list is the first-party source, so on relay egress `is_vpn` answers false unless a VPN provider's own list names the address. A relay user is therefore never counted as a VPN user by accident.

## A policy in four tiers

Write the policy against the flags, and use `is_anonymous`, which is true when any of the three is, only as the coarse switch for "apply the anonymiser tier at all".

- **Nothing anonymised, `is_anonymous: false`.** No friction. This is the large majority of traffic.
- **Relay, `is_relay: true`.** No friction either. Treat the location as regional rather than exact, since Apple assigns the city, and never rate-limit by address: many relay users share each egress address, so one hot address is a crowd, not an attacker.
- **Named VPN, `is_vpn: true` with a `privacy_service`.** Allow the action, and step up only where the action is sensitive: a payment from a new device, a password change, a withdrawal. Use a challenge you already have, such as an email confirmation or a one-time code, rather than a refusal. A paying customer on Mullvad should never see "VPN detected, access denied".
- **Tor, `is_tor: true`.** Decide per product. A publisher may want Tor readers; a marketplace may require a verified email before listing anything; a bank may refuse. Whatever the answer, make it a deliberate rule for Tor alone, not a side effect of a VPN block.

Unnamed VPN ranges, `is_vpn: true` with `privacy_service: null`, come from the aggregate list, which classifies whole networks. Treat them like a named VPN for stepping up, but expect more false positives, and let a confirmed email or a completed payment clear the address for that account.

## Challenge, do not block

Every step-up above shares a property: a person can pass it and a script usually cannot. That is the whole design. A block tells a legitimate visitor nothing they can act on and tells an attacker exactly which network to switch to. A challenge lets the person through at the cost of a minute and leaves the attacker holding a burner mailbox.

Pick the challenge by what the action puts at risk, not by the flag:

| Action | Anonymised visitor | What to ask for |
| --- | --- | --- |
| Reading, browsing, pricing | any | nothing |
| Free signup | relay | nothing |
| Free signup | VPN or Tor | a confirmed email before the trial starts |
| First payment | VPN | the card check you already run |
| First payment | Tor | your policy for Tor, applied consistently |
| Withdrawal or payout | any anonymiser | a second factor |

Record the flags with the decision, so a support agent looking at a complaint can see that the account was challenged because `is_tor` was true on a payout, not because a score crossed a line nobody can explain. `risk_signals` is built for exactly that log line.

## What the flags cannot see

Residential proxies rent out addresses on home connections. They look like a person on a home line in every field of the response: `asn_type: "isp"`, `is_datacenter: false`, no anonymiser flag. No open dataset can tell them apart, and this API does not claim to. If your abuse is coming through residential proxies, the network flags will be clean and the evidence will be behavioural: velocity, address reuse across accounts, a located country that never matches the billing country. Read the [datacenter guide](/guides/block-datacenter-and-bot-signups-with-an-ip-lookup) for the complementary check, and keep the anonymiser tier for what it can actually see.

## Keep the location honest for VPN users

A VPN exit's location is the exit's, not the person's. When Mullvad or NordVPN place the exit in Tirana, the response says `country: "AL"` with `location_source: "operator"`, because the provider's own server list is the authority on where its exit is. That is the right answer to "where does this visitor appear to be" and the wrong answer to "where does this visitor live". For currency, consent and time zone defaults, prefer the account's stored preferences over the lookup whenever `is_anonymous` is true; the [consent and defaults guide](/guides/gate-consent-and-defaults-by-country-from-an-ip) covers the rest of that decision.

## What to do next

- Read the [IP intelligence guide](/docs/ip-intelligence) for the field list, the risk arithmetic and worked examples.
- Try a Mullvad exit and an iCloud Private Relay address on the [product page](/products/ip-intelligence) to see the two rules in the response.
- Pair this policy with [block datacenter and bot signups with one IP lookup](/guides/block-datacenter-and-bot-signups-with-an-ip-lookup) for the traffic that is not a person at all.
- Check the [endpoint reference](/docs/api/lookup-ip) for the exact shape of `risk_signals` and `sources`.

Reference: https://spaw.co/guides/a-vpn-and-relay-policy-that-does-not-punish-real-people
