116 lines · 8.0 KB

Abuse handling

Reports land in one queue (abuse_reports, migration 0027_abuse_reports.sql) from three sources:

Source Entry point Reporter identity (HMAC only) Weight
web /abuse form (submitAbuseReport, src/fns/abuse.ts) signed-in user, else client IP user 2, IP 1
site POST /__gitfor/report on any hosted site client IP 1
email mail to abuse@ routed to the Worker (src/server/abuse-email.ts) sender domain (addresses are free to mint) 2 with aligned DMARC pass, 0

Every new report notifies the site admins. The /abuse form and the site endpoint share the REPORT_LIMITER binding (5 per minute per IP). The site endpoint also requires an Origin header equal to the site's own origin, so another page can't file reports in its visitors' names.

Email reports only count toward auto-pause when the topmost Authentication-Results header (prepended by Cloudflare Email Routing) shows dmarc=pass aligned with the From domain. Unauthenticated mail is still filed for review but has weight 0, since its sender is trivially forged.

Targets

Each report is linked to what its URL points at (resolveAbuseTarget, src/server/abuse-targets.ts):

Kind URL
site site label, preview label, then a site custom domain
pages Pages owner subdomain or custom domain (via the KV slot pointer)
user /<account> on the forge
repo /<owner>/<repo>/...
issue /<owner>/<repo>/issues/<n> (or pull, discussions)
comment an issue or pull URL with #comment-<id>

Repositories, accounts, issues, pull requests, comments, review comments and discussion comments have a Report link that opens the form prefilled (#comment-, #review-comment- and #discussion-comment- anchors). The link is hidden only from the account that owns the reported object; site and repository admins see it. Reports about other hosts are kept but not linked.

Auto-pause

Only hosted sites auto-pause (sites.disabled_reason = 'Paused pending abuse review', sites.paused_at set, KV meta republished so the dispatcher returns 451). Reporters are counted once per site (their highest weight) over open reports from the last seven days:

Site Counted reporters Any category Phishing or malware
Scratch weight >= 1 3 2
Owned weight >= 2 (signed-in users, DMARC-verified mail) 20 (10 users) 8 (4 users)

Anonymous IPs never pause a claimed site; below the owned threshold an admin decides. Admins are notified on every auto-pause. Sites already disabled for another reason (expiry, repository deleted) keep that reason.

Review

Site admins (users.is_admin) get Site admin in the user menu:

  • /admin: users, orgs, repos, sites and abuse counters, organizations ranked by open reports, recent sign-ups.
  • /admin/abuse: the queue. Pause site / Keep paused / Restore site as before. Hide (repo, issue, comment, review comment, discussion comment) or Take down Pages hides that single object from everyone but site admins (hidden_at / pages_sites.taken_down_at, migrations 0045 and 0046); Unhide / Restore Pages reverses it. Acting closes every open report for the same target; Dismiss closes one report. Moderate by URL does the same without a report.
  • /admin/audit: every admin action (abuse decisions, hide/unhide, Pages takedowns, hosting suspend/resume, account disable/enable) with actor, target, reason and time (admin_audit_log).

A hidden repository answers 404 to non-admins on the web, API, MCP and git, and its Pages project answers 451. A taken-down Pages project answers 451 within the serve cache window (60 s per isolate).

Takedown latency

A pause, an account disable or a hosting suspension is a D1 write plus a KV write, but serving reads KV with an edge cache, so a takedown is not instant everywhere:

What Where Lag
Site meta and slot pointers (pause, disable) src/server/sites/dispatch.ts, cacheTtl: 30 up to 30 s
Pages pointers and custom-host map src/server/pages/serve.ts, cacheTtl: 60 up to 60 s
Hosting suspended / capped flags ownerHostingAllowed, cacheTtl: 60 up to 60 s
Repository owner (transfers, suspension owner) per-isolate cache in pages/serve.ts up to 60 s
Browser caches of Pages and site assets cache-control: public, max-age=600 on non-HTML up to 10 min
HTML max-age=0, must-revalidate next request
Avatar takedown check per-isolate cache in avatarTakenDown up to 60 s

KV itself is eventually consistent across locations, which can add seconds to a minute on top. suspendHostingFor also asks Cloudflare to purge the cache of the account's hosts when CF_API_TOKEN (with Cache Purge) is set (src/server/pages/cache-purge.ts): Pages owner subdomains and every custom domain (Pages and Sites, Cloudflare for SaaS) through PAGES_ZONE_ID, and Sites labels (production, previews, PR aliases) under SITES_DOMAIN through SITES_ZONE_ID. Worker responses are not normally stored by the zone cache, so this is belt-and-braces; it cannot reach browser caches, which simply expire. Accounts disabled by an admin (reason admin) also stop serving their repositories, including public ones, to everyone but site admins (repoTakenDown in src/server/repos.ts), and so do their profile, activity, followers and avatar; a payment dispute only makes them read-only. Avatar images already in a browser cache (immutable) can't be recalled.

Operator tasks

  • SITES_DOMAIN (for example gitforstatic.dev) must be on the Public Suffix List before Sites, and scratch sites in particular, are opened to the public. See docs/sites-design.md "PSL submission".

Email setup (Cloudflare Email Routing)

  1. Enable Email Routing on the forge zone and verify the operator's destination address: wrangler email routing addresses create you@example.com.

  2. Set ABUSE_FORWARD_TO in wrangler.jsonc to that address. The Worker forwards every abuse@ message there before filing reports, so nothing is lost if parsing fails.

  3. After the Worker is deployed, route abuse@ to it:

    wrangler email routing rules create <zone> --name "abuse to worker" --match-type literal --match-field to \
      --match-value abuse@<zone> --action-type worker --action-value <worker name>
    

    Until then a plain forward rule to the operator works, but no reports are filed and nothing auto-pauses.

What hiding removes

Hidden issues drop out of issue lists, search, hovercards, milestones, pinned and sub-issue lists, and their inbox notifications are deleted. Hidden comments drop out of the thread and of the parent's comment count (restored on unhide), and their authors can no longer edit them. Account exports keep everything.