DOCS / THREAT MODEL

Threat model.

Last updated:

What this site defends against, what it doesn't, and where the trade-offs sit. Honest-by-construction — naming the limits clearly is the only way you can make an informed decision about importing our lists or scripts onto a router that handles other people's traffic.

1. What this site is

Three things, layered:

All three are content the operator + community curate. None of them is a managed service. Your router executes the content; the trust boundary is at /import on your end.

2. Threats we defend against

2.1. Tampering with the lists in transit

Threat: an on-path attacker injecting malicious entries into the .rsc file your router pulls on its daily fetch. Could blackhole legitimate traffic (denial-of-service-by-config) or whitelist attacker infrastructure.

Defence:

Residual risk: a Cloudflare-side compromise (full edge takeover) bypasses TLS. Outside the operator's control; mitigated only by Cloudflare's own posture.

2.2. Account takeover

Threat: an attacker impersonating a moderator (could approve malicious submissions) or a supporter (could submit malicious scripts that pass through moderation faster if their account has reputation).

Defence:

Residual risk: an attacker with read access to the user's email account can sign in. Phish-the- email is the realistic threat. TOTP closes this for users who enable it; the operator + moderators are advised to.

2.3. Cross-site scripting (XSS)

Threat: malicious content in user submissions, donor-wall display names, or community-script bodies executing in the browser of a moderator viewing the admin queue.

Defence:

Limit: CSP script-src includes 'unsafe-inline' as a documented v1.0 trade-off (the <script is:inline> hydrators with define:vars={...} would need nonce injection that's a v1.x follow-up). The trade-off is named in _headers; the listed escape-pipeline + build- time lint are the containment.

2.4. CSRF / cross-origin write

Threat: a malicious site triggering a state-changing request to /api/admin/* while a moderator is signed in.

Defence:

2.5. Submission-flood / list pollution

Threat: an attacker creates accounts en masse and submits IPs / scripts to either (a) overwhelm the moderation queue, or (b) sneak a malicious entry through via volume.

Defence:

2.6. Payment-data leakage

Threat: Stripe identifiers, donation amounts, or card data ending up in error logs / Sentry / audit log.

Defence:

2.7. Per-subscriber tracking

Threat: the operator (or anyone with operator-level DB read) can correlate a specific IP to a specific subscriber, reverse-engineer pull patterns to infer router-deployment topology, etc.

Defence: see /privacy §3.2. Pull-event rows store SHA-256(salt, /24-IPv4 / /48-IPv6) cohort hashes — not full IPs. The truncation happens before hashing; the salt rotates on deploy. Even an operator-side compromise can't reconstruct individual IPs from cohort buckets without access to the Cloudflare server logs (which are Cloudflare's, not ours, and have their own retention).

2.8. SQL injection

Threat: user input ending up un-escaped in a SQL query.

Defence: every D1 query goes through Drizzle ORM. No raw string concatenation; bind-parameters throughout. The SAST CI gate (semgrep, shipped at v0.67.7) runs `p/javascript + p/typescript + p/owasp-top-ten` rules with --severity=ERROR --error on every push; injection-shaped patterns fail the build.

2.9. Subscription-replay / webhook spoofing

Threat: an attacker forges a Stripe webhook event to grant themselves supporter status without paying.

Defence: the POST /api/stripe/webhook handler verifies the Stripe-Signature header against STRIPE_WEBHOOK_SECRET. Missing secret → 503 (service-not-configured); failed verification → 401. Replay protection: every event id is upserted into stripe_webhook_events before handler dispatch; `status='processed'` rows no-op + return `duplicate`.

3. Threats we don't defend against

Honesty matters more here than completeness. Things this site does NOT protect you from:

3.1. Cloudflare-side compromise

The whole stack runs on Cloudflare. A Cloudflare-side compromise (edge takeover, Workers runtime escape, D1 read access) bypasses every defence in §2. Outside the operator's control. Trust boundary: Cloudflare's published security posture + their certification regime (SOC 2, ISO 27001).

3.2. Stripe-side compromise

Card data lives in Stripe's vault. A Stripe breach exposes card data we never had. Trust boundary: Stripe's PCI DSS Level 1 certification + their published incident response.

3.3. The operator going rogue

A solo-dev project means the operator is the trust root. Database read access lets them see things the site structurally shouldn't expose (raw donation amounts, individual submission histories, etc. — though not card data per §3.2). The privacy-by-construction architecture (PLAN.md §5.4) makes accidental leakage hard, but deliberate insider abuse remains a risk that no architectural choice fully eliminates. Your trust in the site IS your trust in the operator.

3.4. List quality

Community-submitted IPs are moderated, but a moderator is a human applying judgement under time pressure. Some false-positives will get through; some legitimate malicious IPs will be missed. We do not warrant the lists' completeness or accuracy. Always test on a non- production router first; understand what each list is for before applying it.

3.5. Script quality

The community-scripts library moderates submissions but moderation is a sanity check — not a formal audit, not a security review, not a guarantee. Scripts execute privileged commands on YOUR router. Read every line before you /import, especially anything flagged HIGH or DESTRUCTIVE. The risk-banner + heuristic-flagger surfaces are advisory; the read-pass is yours.

3.6. Targeted nation-state actor

A well-resourced state actor with the ability to compromise Cloudflare, our DNS, our domain registrar, OR coerce the operator can subvert any of the above. This site is not built to withstand that threat model. If you're protecting assets that warrant nation-state-resistant infrastructure, use it instead of (or alongside) us.

3.7. RouterOS bugs

A bug in RouterOS itself — in /tool fetch, /import, the firewall engine, or anywhere else — is upstream's problem to fix. We follow MikroTik's published advisories and adjust our recommendations accordingly (v6/v7 quirks are documented inline on the firewall pages), but we're not RouterOS maintainers.

3.8. Your local environment

An attacker who can read the browser session of a moderator-on-a-public-Wi-Fi can act as that moderator. An attacker with shoulder-surfing access to the moderator's laptop can exfiltrate the donor wall's pre-moderation queue. We can't defend against compromised endpoints. Moderators + admins are advised to use a dedicated browser profile + a hardware-backed second factor.

4. The trade-offs

Some defences come at the cost of usability or operational simplicity. Where we made a trade-off, here's the rationale:

4.1. 'unsafe-inline' in CSP

Documented in _headers + the v0.83.1 changelog. Static-build Astro with per-page-defined hydrators makes nonce injection a v1.x project. The escape-pipeline in §2.3 is the containment; a v1.x nonce-injection follow-up closes this.

4.2. Hand-rolled Sentry capture

~200 KiB SDK avoided in favour of a 326-line hand-shaped capture path (v0.83.2). Trade-off: we lose Sentry's auto- breadcrumbs, request-context, and span tracking; we gain bundle thrift, full control over what's sent, and a tight coupling to the v0.60.2 redaction policy that fails closed on new event-shape additions.

4.3. Magic-link instead of password

No password to phish, no password-reuse blast radius from other sites' breaches. Trade-off: an attacker with email- account access has full account access. TOTP closes this for users who enable it; we recommend it for any account with elevated privileges (mod / admin / superadmin).

4.4. SameSite=Strict on session cookies

Closes the CSRF class. Trade-off: links from external sites (a tweet, a blog post) into /admin/* don't carry the session — the user has to navigate from mikrotikfilters.com directly. Acceptable for admin surfaces where deep-linking is rare; the public read surfaces (/scripts, /lists) are anon and unaffected.

4.5. Cohort hashes instead of full IPs

We sacrifice "this exact IP is doing weird things" debugging visibility for the no-per-subscriber-tracking commitment. Trade-off: cohort-level abuse visible at the /24 v4 / /48 v6 bucket level; an attacker hiding among many legitimate subscribers in the same cohort gets some cover. Acceptable given the privacy upside.

4.6. Closed-source

Source is not public — reasoning around accidental-leak risks of open-sourcing a payment + auth system written by a solo dev with limited security budget. Trade-off: no independent code review by random commenters; the operator + this self-audit + the v0.83.2 Sentry visibility + future paid review passes are the substitutes.

5. What's queued

Highlights still pending v1.0:

6. How to report something

If you find a bug, a missed defence, an unclear claim in this document, or anything that contradicts how the site actually behaves — report via /security. The disclosure address lives there along with the operator's PGP key when one is available. We treat security reports as their own queue, separate from feature submissions.