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:
- A reference for default RouterOS firewall rules (/firewall).
-
A distribution surface for community-curated
IP/CIDR blocklists (/lists) — pulled
by your router via
/tool fetch. - A distribution surface for supporter- contributed RouterOS scripts (/scripts) — pulled the same way.
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:
-
TLS-mandatory. The Cloudflare edge serves
https://mikrotikfilters.com/api/lists/<slug>.rsc; non-HTTPS requests get redirected./tool fetchwithmode=httpsvalidates the certificate. - 2-year HSTS preload (`Strict-Transport-Security` shipped at v0.32.2 + the launch-checklist hstspreload.org submission).
- Cloudflare-issued cert via Universal SSL; private key never leaves their infrastructure.
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:
- Passwordless magic-link sign-in. No password to phish. Tokens are random 32+ bytes, SHA-256 hashed at rest, 30- minute expiry, single-use.
- Optional TOTP 2FA on all accounts; mandatory consideration for moderator + admin roles.
- Sessions are HttpOnly + SameSite=Strict cookies; the raw session id is never visible to JS, the database stores only its SHA-256 hash.
- Per-IP rate-limit on magic-link issuance + onboarding (10/min and 1/min respectively). Brute-forcing email addresses for valid accounts is impractical.
- Suspended accounts — the v0.62.0 admin-suspend flow invalidates all sessions immediately; the v0.80.0 system- orphan gate refuses session validation, magic-link issuance, and API-key resolution for `is_system=true` rows.
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:
-
Every JS-built row uses
escapeHtml()beforeinnerHTML. User-content (donor-wall names, list descriptions, audit metadata, script bodies) goes throughtextContentorescapeHtmleverywhere it touches the DOM. -
Build-time inline-script lint (shipped at v0.67.6) parses
every
<script is:inline>block via V8'svm.compileFunction; SyntaxError findings fail the build. The 7-pattern self-test pinned inpnpm testincludes the v0.67.5 escapeHtml redeclaration regression so it can't recur. -
Strict CSP shipped at v0.83.1 —
default-src 'self';connect-src 'self'blocks third-party fetches;frame-ancestors 'none'+X-Frame-Options: DENYbelt-and-braces;object-src 'none'. -
Astro's auto-HTML-escape on text-content interpolation
(curly-braced text always escapes; only
set:html=opts out).
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:
-
SameSite=Strict on the session cookie. Cross-origin POSTs
from
evil.example.comdon't carry the cookie. -
form-action 'self'CSP directive. A form submitted tohttps://evil.example.comfrom the page is blocked at the browser. -
Cross-Origin-Opener-Policy: same-origincloseswindow.openerleaks.
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:
- Per-account submission rate-limits (30/min on lists; 5/day + 20-active cap on scripts; both shared across the user's API keys).
- Magic-link sign-in is per-email, and email-issuance is per-IP rate-limited. Mass-account creation requires mass- email-account access — a real cost for the attacker.
- Supporter-gating for script submissions adds a Stripe- verified payment-method requirement — at least one weak identity assertion before content flow.
- Moderator queue is sole-decider. No auto-publish path. Volume can't bypass the human review.
2.6. Payment-data leakage
Threat: Stripe identifiers, donation amounts, or card data ending up in error logs / Sentry / audit log.
Defence:
- Card data never reaches our infrastructure. Stripe Checkout handles all payment-instrument input.
-
v0.60.2
redactBeforeSendSentry filter is allowlist-based: drops request bodies, query strings, all headers except a tiny allowlist, all user fields except the integer user id, all tags except a curated low-card set, and any string content matching Stripe-id patterns (`cus_*`, `sub_*`, `pi_*`, etc., with `test_`/`live_` infix), currency-amount patterns, email patterns, or magic-link token patterns. v0.83.2 wires the SDK init. -
Privacy-by-construction transparency feed (PLAN.md §5.4):
the
public_aggregatestable never joins todonations/subscriptions; small- cohort suppression at k=5.
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:
- CSP nonce injection — drop the
'unsafe-inline'trade-off in §4.1. - DSAR procedure — written runbook for DSAR requests (the rights are documented in /privacy §8; the runbook is the operator's procedure).
- Independent review pass — third-party / paid pen-test or formal self-audit pass before commercial expansion. The first self-audit happens at v0.83.7.
- Launch checklist — go-live verification doc covering HSTS preload submission, Sentry DSN configuration, Stripe live-mode key swap, etc.
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.