Only publish blocks that are still in force
docker-build-push / build-push (push) Successful in 4s

ipblock_registered and domain_blocked only ever meant "we posted this
block once" — they said nothing about whether it still exists. When the
API went live it was publishing 24 /32 ranges that had been removed by
hand on 2026-07-21, so ~17% of the IP list didn't block anything. That's
a bad thing to hand another admin an importer for.

app/reconcile_blocks.py fetches Mastodon's live Admin::IpBlock and
Admin::EmailDomainBlock lists and maintains a new block_active column on
both tables: off for rows whose block is gone or whose expires_at has
elapsed, back on for any that reappear. The API serves only active rows.
Re-registering a block reactivates its row, so a range the bot blocks
again after a manual cleanup comes back on its own.

A failed fetch changes nothing — not a 403 from a missing scope, not
Mastodon being down, not a half-failure where one list answers and the
other doesn't. An outage must never be read as "every block was
removed"; the list degrades to over-reporting rather than emptying
itself, and there's a test for each of those paths.

Needs admin:read:ip_blocks + admin:read:email_domain_blocks, added to
the live token in place (scopes updated on the existing Doorkeeper
token, matched by digest) rather than by minting and rotating, so the
token value is unchanged and .env needed no edit.
This commit is contained in:
pmb committed 2026-09-13 01:05:52 -07:00
1 parent 8966de2a1d
commit 8490048d2d
6 files changed
+370 -9

No files matched your search

+27
View File
@@ -301,6 +301,32 @@ the ipapi range cache by `bin/backfill-block-cidr.py`; ten rows with no cache
entry left are shown as their `/32`, since a narrower entry is a safer error
than claiming a range is blocked when it isn't.
### Staying accurate (`app/reconcile_blocks.py`)
`ipblock_registered` / `domain_blocked` only ever meant "we posted this block
once" — they say nothing about whether it's still there. A moderator clearing
entries by hand, or an `expires_at` elapsing, would leave the endpoint
publishing blocks that no longer block anything. (This was not hypothetical:
when the API first went live it was publishing 24 `/32` ranges that had been
removed on 2026-07-21.)
The reconcile pass fetches Mastodon's live `Admin::IpBlock` and
`Admin::EmailDomainBlock` lists and flips `block_active` off for rows whose
block is gone or expired, back on for any that reappear. The API serves only
active rows, so `?format=txt` hands an importer only blocks actually in force.
```bash
docker exec yttrx-welcomebot python -m app.reconcile_blocks --dry-run
docker exec yttrx-welcomebot python -m app.reconcile_blocks
```
Runs hourly by cron on admin.yttrx.com, alongside the suspicious-signup sweep.
It needs `admin:read:ip_blocks` and `admin:read:email_domain_blocks` on
`ABUSE_BOT_TOKEN`. **A failed fetch changes nothing** — a 403 from a missing
scope, or Mastodon being down, must never be read as "every block was
removed", so rows keep their existing state and the list degrades to
over-reporting rather than emptying itself.
Scope: this is the **bot's own record**, not Mastodon's full blocklist —
blocks a moderator added by hand in the admin UI are not in it. Entries appear
only once the block actually landed on the Mastodon side (a failed
@@ -312,6 +338,7 @@ only once the block actually landed on the Mastodon side (a failed
|---|---|
| `app/main.py` | The FastAPI app |
| `app/suspicious_sweep.py` | Scheduled sweep: suspends flagged signups with no activity (run via cron, not the webserver) |
| `app/reconcile_blocks.py` | Scheduled pass: marks blocks that no longer exist in Mastodon inactive (cron) |
| `Dockerfile`, `docker-compose.yml` | Container build + run |
| `.env.example` | Config template (copy to `.env`, never commit) |
| `nginx/hooks.yttrx.com.conf` | nginx site for admin.yttrx.com (webhook, healthz, blocklist API) |