A fraud dashboard flags a visitor as VPN, the analyst asks for a block, and the request reaches the platform team with nothing but a vendor boolean. That boolean is an estimate, and it fails in a different direction from a latency measurement. Before acting, separate what the request itself proves, what a reputation service asserts, and what timing merely suggests. Then pick a response you can reverse when the estimate turns out to be wrong.

Collect the request facts you can defend

The only address your server knows for certain is the TCP peer that completed the connection. When a CDN or reverse proxy terminates that connection, the peer is the edge, and the client address rides in a forwarding header that is trustworthy only when your own edge appended it. Read the entry your proxy wrote, never the leftmost string, because a client can send any value it likes in that position. Everything downstream has to preserve that distinction.

Two cheap enrichments turn the address into something reviewable: the autonomous system that announces it and the PTR record that points back to it. Both describe registration rather than behavior, and both are reproducible later.

IP=203.0.113.10
dig +short -x "$IP"
whois "$IP" | grep -iE "origin|netname" | head -5

A hosting ASN is a strong hint and not a verdict. Corporate VPN concentrators, cloud egress and some university networks also sit in datacenters, so the same signal that catches a commercial exit also catches a legitimate office. Keep the address only as long as the review needs it, because an IP is personal data in most privacy regimes. Delete it on a schedule rather than keeping it indefinitely.

Score the visitor before you restrict them

Run any new signal in observe mode first. Log the inputs and a reason code, let every request through, and compare the score distribution against accounts you know are legitimate. This produces a false-positive estimate before anyone loses access, which is the number that decides whether a rule is safe to enable.

SignalWhat it supportsHow it failsTypical action
Hosting ASNTraffic likely originated from infrastructureCorporate VPN and cloud egress look the sameAdd weight, not a block
Reputation verdictA vendor's dated classificationVendors disagree; self-hosted exits look cleanStep-up challenge
RTT versus baselineThe path is longer than usualAnycast, mobile networks, in-country exitsWeak weight only
Forwarded header consistencyWhether the edge saw the claimed clientMisconfigured proxies produce noiseDrop the value, do not score it

Map the total to three actions rather than two: allow, step-up challenge, manual review. A permanent block should require repeated abuse confirmed by a person, because a false positive on a corporate traveler costs a support ticket and a lost session, while a false negative on one request costs almost nothing.

Keep the reputation lookup server-side and bounded

Call the reputation service from the backend with a short timeout, cache the verdict for a fixed period, and treat a timeout as unknown instead of clean. Never put the API key in client-side code, and never let the lookup block the page render; a slow third party should degrade the score, not the site.

Cache per address rather than per range. Classifying a whole /24 from one lookup spreads a wrong verdict to neighbors you never measured. Record the vendor, the response date and the classification value alongside the reason code so a later review can tell which judgment was in force.

Two providers may classify one address differently within a day, which is why the verdict belongs in the evidence column and not in the identity column. It is also a processing relationship: sending every visitor's address to a third party needs a lawful basis, a retention rule and a line in the privacy notice.

Measure latency with a median, not one sample

Round-trip time describes the path at the moment you measured it. A nearby anycast edge can front a distant user, a mobile network adds delay that has nothing to do with geography, and a VPN exit in the visitor's own country looks local. City-level geolocation frequently reports the datacenter rather than the person.

Take several samples and use the median so one slow request does not move the result. The loop below prints the median of five.

for i in 1 2 3 4 5; do
  curl -o /dev/null -s -w "%{time_connect} %{time_starttransfer}\n" https://www.example.com/
done | sort -n | sed -n 3p

Compare the number against the distribution for known-good users instead of an absolute threshold, and combine it with the ASN. Two weak signals that agree are stronger than either alone, but the conclusion is still about a path, not about intent, so it belongs in a score rather than a ban.

Decide, record and roll back

Before enabling a rule, replay a day of traffic in observe mode and count what would have happened. Store the score inputs, the reason code and the rule version, but keep raw addresses only for the review window. If the rule fires on a legitimate account, the record should explain why without a second investigation.

Give every rule its own switch. When step-up completion drops or support tickets spike, disable that rule and keep the rest of the pipeline running; a single global kill switch turns a tuning problem into an outage. After the change, watch the challenge completion rate and the appeal queue, not just the block count.

Finally, keep the response proportionate to the loss you are preventing. For advertising fraud, VPN status is a weak proxy for a fake click, and behavioral signals such as conversion patterns and device consistency address the actual damage. A reversible challenge protects the revenue and the real user at the same time.

Amanqephe ahlobene