# Copy to .env and fill in. Do NOT commit .env. # HMAC secret shown when you create the webhook in Mastodon # (Administration -> Webhooks). Used to verify X-Hub-Signature. WEBHOOK_SECRET= # Access token for the bot account that sends the welcome DMs. # Create a bot account on yttrx, then Preferences -> Development -> # New application with scope: write:statuses (copy "Your access token"). BOT_ACCESS_TOKEN= # Base URL of the Mastodon instance (no trailing slash). MASTODON_BASE_URL=https://yttrx.com # Welcome message template. {acct} is replaced with the new user's handle. # The bot mentions @{acct} so a "direct" status reaches them. WELCOME_MESSAGE=👋 Welcome to yttrx, @{acct}! Glad to have you here. If you have any questions, check out https://help.yttrx.com or just reply to this message. # Only welcome accounts local to this instance (recommended: true). LOCAL_ONLY=true # Path to the sqlite dedup store inside the container (matches the volume). DB_PATH=/data/welcomed.db # --- Abuse-bot (report.created handling) ----------------------------------- # Access token for a DEDICATED MODERATOR bot account. That account must hold a # role with the "Manage Users" + "Manage Reports" permissions, and the app # token must request these scopes: # admin:write:accounts admin:read:reports read:statuses write:statuses # Leave blank to disable report handling entirely. ABUSE_BOT_TOKEN= # Master switch for report handling (also off if ABUSE_BOT_TOKEN is blank). ABUSE_ENABLED=true # Rollout safety: when true, log + DM what WOULD happen but take no action. # Recommended for the first days in production; flip to false once happy. ABUSE_DRY_RUN=true # Moderation action: "silence" (reversible, agreed default) or "suspend". ABUSE_ACTION=silence # Only auto-act on accounts local to yttrx. ABUSE_LOCAL_ONLY=true # Account tiers (in days): # young = oldest post newer than ABUSE_YOUNG_MAX_DAYS # dormant = newest post older than ABUSE_DORMANT_MIN_DAYS ABUSE_YOUNG_MAX_DAYS=30 ABUSE_DORMANT_MIN_DAYS=30 # Required number of DISTINCT reporter accounts (open reports) to auto-act: # young / dormant / no-posts -> ABUSE_SOURCES_NEWDORMANT # normally-active -> ABUSE_SOURCES_ACTIVE ABUSE_SOURCES_NEWDORMANT=2 ABUSE_SOURCES_ACTIVE=3 # Safety cap on the backward status scan (40 statuses per page). ABUSE_MAX_STATUS_PAGES=5 # Automatically never auto-act on accounts holding a staff role # (Admin/Owner/Moderator), read from the report payload. Keep this true. ABUSE_SKIP_PRIVILEGED=true # Extra comma-separated handles (acct, no leading @) never to auto-act on — # a backstop on top of ABUSE_SKIP_PRIVILEGED, and for non-staff special # accounts. e.g. ABUSE_ALLOWLIST=waffles,tommertron,davis,bot ABUSE_ALLOWLIST= # Handle (acct, no @) to DM with a review summary after an auto-action. # Leave blank to disable the DM. The DM is sent from the moderator bot. MOD_ALERT_ACCT= # Help/appeals page the silenced user is pointed to. ABUSE_HELP_URL=https://welcome.yttrx.com/posts/account-limited/ # DM sent to the silenced user ({acct} + {help_url} substituted; must mention # @{acct}). Leave blank to disable the user DM. ABUSE_USER_DM=Hi @{acct} — your yttrx account has been temporarily limited while we review some reports. This is reversible and a moderator will take a look. Here's what happened and how to appeal: {help_url} # --- IP-based signup scrutiny ----------------------------------------------- # Classifies each new signup's IP (Admin::Account.ip, delivered free on the # account.created webhook) via RDAP org lookup, and treats non-residential/ # non-mobile ("datacenter"/hosting/VPN) signups with more scrutiny. Master # switch: IP_SCRUTINY_ENABLED=true # Rollout safety: when true, classify + DM a moderator but take NO action # (no held welcome, no ip_blocks write). Recommended for the first days in # production; flip to false once you've reviewed the false-positive rate. IP_SCRUTINY_DRY_RUN=true # Hold the welcome DM for a flagged signup until account.approved fires # (a human clears the existing approval-required registration gate), instead # of welcoming immediately like every other signup. IP_SCRUTINY_HOLD_WELCOME=true # Distinct-reporter threshold used instead of the tier's usual # ABUSE_SOURCES_* threshold (whichever is lower) when the reported account's # signup IP was flagged as datacenter/hosting. IP_SCRUTINY_ABUSE_THRESHOLD=1 # Auto-register a flagged IP into Mastodon's native Admin::IpBlock. Requires # the ABUSE_BOT_TOKEN to carry the admin:write:ip_blocks scope (see # CLAUDE.md's moderator-token-gotcha section) — without it this 403s and is # logged as an error, but nothing else in the bot is affected. IP_SCRUTINY_AUTO_IPBLOCK=true # Severity applied to auto-registered blocks: sign_up_requires_approval (soft, # recommended), sign_up_block (hard — no queue, cannot appeal via this bot), # or no_access (blocks all access, not just signups). IP_SCRUTINY_IPBLOCK_SEVERITY=sign_up_requires_approval # Case-insensitive regexes matched against the RDAP org/ASN description. # Anything matching neither is treated as "residential" (never flagged). # Only IP_SCRUTINY_HOSTING_RE matches are flagged; IP_SCRUTINY_MOBILE_RE is # informational only (mobile carriers are never scrutinized). IP_SCRUTINY_HOSTING_RE=amazon|aws|google|microsoft|azure|digitalocean|ovh|hetzner|linode|vultr|contabo|choopa|m247|scaleway|leaseweb|hostinger|namecheap|godaddy|cloudflare|oracle|alibaba|tencent|akamai|fastly|packet|vpn|proxy|hosting|datacenter|data center|colo(?:cation)?|server IP_SCRUTINY_MOBILE_RE=mobile|wireless|cellular|verizon|t-mobile|tmobile|at&t|att mobility|vodafone|telstra|sprint|three\b|orange mobile|deutsche telekom|o2\b # check-mail.org disposable/high-risk email domain scrutiny (roadmap item B, # anti-abuse.md). Domain-only query (never the full email) against # POST https://api.check-mail.org/v2/, Authorization: Bearer . # Free tier: 1,000 req/month. CHECK_MAIL_ENABLED=true CHECK_MAIL_API_KEY= # Log/DM only — no held welcome, no email_domain_block write, no # report-triggered suspend. Keep true for rollout until you've watched the # false-positive rate for a while (same role as IP_SCRUTINY_DRY_RUN / # ABUSE_DRY_RUN). CHECK_MAIL_DRY_RUN=true # A domain is flagged if check-mail.org marks it is_disposable OR its risk # score (0-100) is at or above this threshold. CHECK_MAIL_RISK_THRESHOLD=80 # Hold the welcome DM for a flagged signup until account.approved fires, same # semantics as IP_SCRUTINY_HOLD_WELCOME. CHECK_MAIL_HOLD_WELCOME=true # Auto-register a flagged signup's email domain into Mastodon's native # Admin::EmailDomainBlock. Requires ABUSE_BOT_TOKEN to carry the # admin:write:email_domain_blocks scope (see CLAUDE.md) — without it this # 403s and is logged as an error, but nothing else in the bot is affected. # Also gates the report-triggered suspend+block override in handle_report(). CHECK_MAIL_AUTO_DOMAIN_BLOCK=true # --- Suspicious-signup sweep (app/suspicious_sweep.py, run on a schedule) --- # Watches every account flagged by EITHER IP-scrutiny or email-domain # scrutiny at signup. Once the account goes live (account.created if open/ # auto-approved, account.approved if this instance requires moderator # approval — same dual handling the welcome flow already uses), a baseline # post/follow count is recorded. A scheduled sweep (cron on admin.yttrx.com, # `docker exec yttrx-welcomebot python -m app.suspicious_sweep`) then acts on # any watch past SUSPICIOUS_GRACE_HOURS with zero new posts AND zero new # follows since that baseline. Master switch: SUSPICIOUS_SWEEP_ENABLED=true # Hours of grace given to a flagged signup to show organic activity (a new # post or a new follow) before it's considered dormant/bot-like. Lowered from # the original 24 to 1 (2026-07-06) — the datacenter/hosting-IP signal alone # is a strong enough spam indicator that a full day of grace was mostly just # delaying an inevitable suspend; paired with a tighter sweep cadence (see # CLAUDE.md) so real latency stays close to this value. SUSPICIOUS_GRACE_HOURS=1 # Moderation action taken on a flagged signup with no activity in the grace # window: "suspend" (agreed default — a stronger signal than the report- # driven abuse-bot's default "silence", since this is zero organic activity, # not just reports) or "silence". SUSPICIOUS_ACTION=suspend # Rollout safety switch, same role as ABUSE_DRY_RUN — ships "false" (live) # here by design; flip to "true" to pause without redeploying. SUSPICIOUS_DRY_RUN=false # Requires ABUSE_BOT_TOKEN to additionally carry the admin:read:accounts # scope (fetches current post/follow counts + role/suspended state via # GET /api/v1/admin/accounts/:id) — see CLAUDE.md's moderator-token-gotcha # section. Without it the sweep 403s per-account (logged as an error, that # account is retried next sweep) but nothing else in the bot is affected. # Reuses ABUSE_SKIP_PRIVILEGED and ABUSE_ALLOWLIST above — no separate vars.