Cap how wide a single auto-registered IP block may be
docker-build-push / build-push (push) Successful in 5s
docker-build-push / build-push (push) Successful in 5s
ipapi.is reports the flagged address's whole ASN route, and register_ip_block blocked it whatever its width. That is the right unit for hosting space, where a repeat bad actor comes back from a different address in the same VPS block. It is badly wrong for an ISP aggregate, and the route cache holds routes as wide as /8, /9 and /11: one `abuser` flag on a residential or mobile address inside one of those would have walled off millions of consumer addresses. That was not hypothetical -- 197.211.32.0/19 is registered against GLOMOBILE-GPRS, a Benin mobile carrier, from a single `abuser` flag: 8,192 addresses of consumer mobile space. Anything wider than /24 (v4) or /48 (v6) now falls back to blocking the single address. This costs blocking strength against large hosting allocations -- an OVH /16 blocks one address now, so a repeat offender from that block needs a fresh block each time -- and in exchange makes the collateral-damage case impossible. Both ceilings are env-tunable. The cap applies to what gets BLOCKED, not to what gets CACHED: the wide route is still the right cache key, since it genuinely belongs to one operator and one lookup answers for the whole of it. Narrowing the cache key would multiply provider lookups for no benefit. Applied at registration time, so blocks already in force are untouched.
This commit is contained in:
1 parent
57234ca548
commit
147673fd49
3 files changed
+131
-4
No files matched your search
@@ -443,6 +443,51 @@ def range_cache_tests():
|
||||
assert cached4["cidr"] == "203.0.113.10/32", cached4
|
||||
|
||||
|
||||
def block_width_cap_tests():
|
||||
"""capped_block_cidr: the ASN route is the right unit to block for hosting
|
||||
space and the wrong one for an ISP aggregate. ipapi.is reports routes as
|
||||
wide as /8, so without a ceiling one `abuser` flag on a residential or
|
||||
mobile address would wall off millions of consumer addresses."""
|
||||
main.IP_SCRUTINY_MAX_BLOCK_PREFIX_V4 = 24
|
||||
main.IP_SCRUTINY_MAX_BLOCK_PREFIX_V6 = 48
|
||||
|
||||
# A. At or narrower than the ceiling: block the route, as before.
|
||||
assert main.capped_block_cidr("5.255.96.30", "5.255.96.0/24") == "5.255.96.0/24", \
|
||||
"the ceiling itself is allowed, not excluded"
|
||||
assert main.capped_block_cidr("5.255.96.30", "5.255.96.0/28") == "5.255.96.0/28"
|
||||
|
||||
# B. Wider than the ceiling: fall back to the single address. A /11 is a
|
||||
# consumer ISP aggregate — blocking it would take out 2m addresses.
|
||||
assert main.capped_block_cidr("98.45.12.7", "98.32.0.0/11") == "98.45.12.7/32"
|
||||
assert main.capped_block_cidr("197.211.45.9", "197.211.32.0/19") == "197.211.45.9/32"
|
||||
# ...including the VPS-sized ranges the bot used to block wholesale.
|
||||
assert main.capped_block_cidr("51.222.9.9", "51.222.0.0/16") == "51.222.9.9/32"
|
||||
assert main.capped_block_cidr("194.163.140.1", "194.163.128.0/18") == "194.163.140.1/32"
|
||||
|
||||
# C. Same rule for v6, on its own ceiling (/48 = the standard end-site
|
||||
# allocation, the rough v6 equivalent of a v4 /24).
|
||||
assert main.capped_block_cidr("2001:db8:1::5", "2001:db8:1::/48") == "2001:db8:1::/48"
|
||||
assert main.capped_block_cidr("2001:db8:1::5", "2001:db8::/40") == "2001:db8:1::5/128"
|
||||
assert main.capped_block_cidr("2001:db8:1::5", "2001:db8::/29") == "2001:db8:1::5/128"
|
||||
|
||||
# D. Missing or unparseable input degrades to the single address rather
|
||||
# than to something wide.
|
||||
assert main.capped_block_cidr("203.0.113.5", "") == "203.0.113.5/32"
|
||||
assert main.capped_block_cidr("203.0.113.5", "not-a-cidr") == "203.0.113.5/32"
|
||||
|
||||
# E. The cap is applied to what gets BLOCKED, not to what gets CACHED:
|
||||
# the wide route is still the right cache key, since the whole route
|
||||
# genuinely belongs to one operator and one lookup answers for all of
|
||||
# it. Narrowing the cache key instead would multiply provider lookups.
|
||||
intel = {"is_datacenter": False, "is_vpn": False, "is_proxy": False,
|
||||
"is_tor": False, "is_abuser": True, "org": "Big ISP"}
|
||||
main.cache_ip_intel("100.64.5.5", intel, route="100.64.0.0/10")
|
||||
assert main.cached_ip_intel("100.64.200.200") is not None, \
|
||||
"a wide route must still answer from cache for its whole range"
|
||||
assert main.cached_ip_intel("100.64.200.200")["cidr"] == "100.64.0.0/10"
|
||||
assert main.capped_block_cidr("100.64.200.200", "100.64.0.0/10") == "100.64.200.200/32"
|
||||
|
||||
|
||||
def combined_signal_tests():
|
||||
"""Drive process_signup's combined IP+email immediate-suspend path
|
||||
(SUSPICIOUS_COMBINED_*), independent of the OR-based hourly sweep."""
|
||||
@@ -1188,6 +1233,7 @@ if __name__ == "__main__":
|
||||
ip_scrutiny_tests()
|
||||
email_scrutiny_tests()
|
||||
range_cache_tests()
|
||||
block_width_cap_tests()
|
||||
combined_signal_tests()
|
||||
strong_ip_suspend_tests()
|
||||
suspicious_watch_tests()
|
||||
|
||||
Reference in new issue
Block a user