register_ip_block always blocked the exact signup IP as a /32 (or /128
for v6), which is close to pointless for the datacenter/VPN/proxy space
these flags fire on: a repeat bad actor from the same provider almost
never reuses the exact same address, but very often reuses a different
one in the same block.
classify_signup_ip now returns a fourth value, block_cidr — ipapi.is's
asn.route CIDR when it's a valid network that actually contains the
signup IP (already being tracked in ipapi_range_cache purely for lookup
caching), falling back to the address's own /32 or /128 when no usable
route exists. register_ip_block blocks that instead of always deriving
a /32 itself.
Added range_cache_tests() covering the cache/fallback logic directly
(pure sqlite + ipaddress, no network), and updated the existing
ip_scrutiny_tests()/combined_signal_tests() mocks for the new 4-tuple
classify_signup_ip return and register_ip_block arity.
The hourly OR-based sweep (SUSPICIOUS_GRACE_HOURS) still handles a
single-signal flag as before. This adds an additional check in
process_signup(): a signup flagged by BOTH IP-scrutiny and
email-domain scrutiny at once is a stronger signal, so it's suspended
right away instead of waiting out the grace period, skipping the
hold/welcome path and the suspicious_watch entry entirely.
Gated behind new SUSPICIOUS_COMBINED_* env vars, dry-run first per the
usual rollout convention (unlike SUSPICIOUS_DRY_RUN, which shipped
live by design). Falls back to the normal held-welcome path if the
suspend API call fails, and respects ABUSE_ALLOWLIST.
Uses asn.route from ipapi.is to bucket the classification cache by
network block (IPv4 and IPv6), so signups from the same datacenter/ASN
block no longer each burn a separate lookup against the 1000/day quota.
Falls back to a single-address range when route is missing or doesn't
actually contain the queried IP.
fetch_account_counts() was hitting the public /api/v1/accounts/:id
endpoint, which 404s for any account that hasn't confirmed its email
yet. Most flagged signups haven't confirmed at the moment
account.created/approved fires, so the baseline snapshot silently
failed and the account never entered suspicious_watch -- 6 of 8
IP-flagged signups to date missed the sweep entirely. Switch to the
admin endpoint suspicious_sweep.py already uses successfully.
Deploy target was cleaned up to just docker-compose.yml + .env (+ .env.bak-*
snapshots) after switching to registry-pulled images. Fixes references to
git pull, chown waffles, and app/main.py.bak rollback that no longer apply.
Verified from the actual job container logs: act_runner bind-mounts
/var/run/docker.sock into every job container unconditionally, even before
privileged mode was enabled. The previous step's nested dockerd never
actually got used.
act_runner now runs privileged, and the job image ships docker+dockerd, so
the build can happen directly in the job container. Needs vfs as the
storage driver since overlay2 doesn't nest inside overlay2 on this host.
account.approved never fires for a signup that went through open
registration without ever being queued (no prior block on that
address). process_signup was waiting on that event forever, so the
first flagged signup on any new bad IP/email domain never got welcomed
or watched by the suspicious-sweep. Now checks Admin::Account.approved
from the account.created payload and acts immediately if it's already
true.
With the sweep now running every 10 minutes, the per-run 'N due' summary
and per-account skip/clear log lines (already-limited, staff, cleared by
activity, not found) were mostly noise drowning out the actual suspend/
silence actions. Those states are still recorded in suspicious_watch.status
for anyone querying the DB directly; only the taken action, dry-run
simulation, and error paths still log.
Datacenter/hosting-IP-flagged signups are a strong enough spam signal on
their own that a full day of grace was mostly just delaying an inevitable
suspend. Cron cadence tightened from hourly to every 10 minutes so real
latency stays close to the new 1h window.
Watches every account flagged by IP-scrutiny or email-domain scrutiny at
signup and, once it goes live, records a baseline post/follow count. A
scheduled sweep (app/suspicious_sweep.py, run via cron on admin.yttrx.com)
suspends any watch past SUSPICIOUS_GRACE_HOURS with zero new posts and
zero new follows since that baseline; any activity clears the watch.
Works whether yttrx is open-registration or requires moderator approval,
since the watch starts at whichever event actually makes the account live
(account.created vs account.approved), same dual handling the welcome flow
already uses. Needs ABUSE_BOT_TOKEN re-minted with admin:read:accounts.
Classifies each signup's email domain (domain-only, GDPR-friendly) alongside
the existing IP scrutiny signal, with matching held-welcome and auto
email_domain_block behavior. A report against an account with a flagged
domain suspends immediately (no reporter-count threshold) and blocks the
domain, classified live from the report payload rather than any signup-time
record so it also covers pre-existing accounts. Ships CHECK_MAIL_DRY_RUN=true
by default, independent of ABUSE_DRY_RUN, so the new report-triggered suspend
path stays inert until watched.