Expire auto-registered IP blocks after 24 hours
docker-build-push / build-push (push) Successful in 5s

Blocks were registered permanently. An IP block is a claim about who is
currently behind an address, and that goes stale: VPS addresses are recycled
between customers constantly, and now that the /24 ceiling means most blocks
cover a single address, a permanent block increasingly punishes whoever gets
that address next rather than the actor we saw.

24h is long enough to break a signup run -- the abuse arrives in bursts, not
as a steady trickle -- and short enough that the blocklist stays a live
picture rather than something that only ever grows.

Mastodon expires these itself and reconcile_blocks already reads an elapsed
expires_at as "gone", so expired blocks leave /api/blocks unaided. The one
piece that needed adding is an expiry on our own dedup record: reconcile runs
hourly, so between a block lapsing and the next pass, reputation_block_
installed would otherwise claim a block Mastodon had already dropped and skip
re-installing it.

expires_in is omitted rather than sent as 0 when the expiry is switched off.
Email domain blocks have no expiry in Mastodon and are untouched.
This commit is contained in:
pmb committed 2026-09-13 16:10:28 -07:00
1 parent 147673fd49
commit 6952fbadf6
3 files changed
+124 -15

No files matched your search

+38
View File
@@ -1224,6 +1224,44 @@ def reputation_api_tests():
active = {e["cidr"] for e in client.get("/api/blocks/ip-ranges").json()["items"]}
assert "198.51.100.0/24" in active, "re-installing republishes it"
# M. IP blocks are registered with an expiry, and our own dedup record
# lapses with them. Without that, a query between a block expiring and
# the next hourly reconcile would claim a block Mastodon has already
# dropped -- and skip re-installing it.
main.REPUTATION_API_ENABLED = True
params = main._ip_block_params("198.51.100.0/24", "test")
assert params["expires_in"] == main.IP_SCRUTINY_IPBLOCK_EXPIRES_IN, params
assert main.IP_SCRUTINY_IPBLOCK_EXPIRES_IN == 86400, "default is 24 hours"
saved = main.IP_SCRUTINY_IPBLOCK_EXPIRES_IN
main.IP_SCRUTINY_IPBLOCK_EXPIRES_IN = 0
assert "expires_in" not in main._ip_block_params("198.51.100.0/24", "test"), \
"a 0 expiry omits the field rather than sending 0"
main.IP_SCRUTINY_IPBLOCK_EXPIRES_IN = saved
# An expired record reads as not-installed, so the next query
# re-installs rather than assuming it's still there.
main.record_reputation_block("ip", "203.0.113.0/24", "datacenter", "Expiring Inc", 86400)
assert main.reputation_block_installed("ip", "203.0.113.0/24")
with main._db() as conn:
conn.execute("UPDATE reputation_block SET expires_at = ? WHERE target = ?",
("2020-01-01T00:00:00+00:00", "203.0.113.0/24"))
assert not main.reputation_block_installed("ip", "203.0.113.0/24"), \
"an expired block must not count as installed"
active = {e["cidr"] for e in client.get("/api/blocks/ip-ranges").json()["items"]}
assert "203.0.113.0/24" not in active, "an expired block must not be published"
# Email domain blocks don't expire in Mastodon, so they carry none here
# either: recorded with no expiry, they stay installed indefinitely.
main.record_reputation_block("email", "rep-forever.test", "", "")
assert main.reputation_block_installed("email", "rep-forever.test"), \
"email domain blocks have no expiry and stay installed"
with main._db() as conn:
row = conn.execute(
"SELECT expires_at FROM reputation_block WHERE target = ?",
("rep-forever.test",)).fetchone()
assert row[0] is None, row
main.REPUTATION_API_ENABLED = False