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.mode=httpson its own does not validate the certificate — RouterOS defaultscheck-certificatetono— so every snippet we publish setscheck-certificate=yesexplicitly. Seethe quickstart note. - The list URLs are terminal — they answer
200directly, with no canonical-host or trailing-slash redirect./tool fetchfollows0redirects by default, so a redirect anywhere on the path would fail the pull silently. - 2-year HSTS preload (`Strict-Transport-Security` in place, plus 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 admin-suspend flow invalidates all sessions immediately; the 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 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 (list descriptions, audit metadata, script bodies) goes throughtextContentorescapeHtmleverywhere it touches the DOM. - Build-time inline-script lint parses every
<script is:inline>block via V8'svm.compileFunction; SyntaxError findings fail the build. The 7-pattern self-test pinned inpnpm testincludes a prior escapeHtml redeclaration regression so it can't recur. - Strict CSP in place —
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 withdefine: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 Paddle- 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.
- Honeypot list corroboration: the one automated ingest path (the
mikrotik-bruteforcehoneypot list, which has no per-report human review) requires an attacker IP to be reported by two independent contributors before it renders into the public drop-list — except for the operator's own honeypots. Counting is by contributor, not by API key, so a single subscriber can't self-corroborate by minting extra keys. An ingest-time denylist also rejects public DNS resolvers / critical anycast and the site's own infrastructure, so a poisoned report can't blackhole those on subscriber routers. A predefined set of sensitive ranges (large cloud / hosting / CDN blocks, where a wrong drop has high collateral damage) — extensible by the operator — bypasses auto-approval entirely and is held for human moderation before it can render.
2.6. Payment-data leakage
Threat: Paddle identifiers, donation amounts, or card data ending up in error logs / Sentry / audit log.
Defence:
- Card data never reaches our infrastructure. Paddle is Merchant of Record and its in-page Paddle.js checkout overlay (loaded from
cdn.paddle.com) handles all payment-instrument input; card data stays in Paddle's vault. - The
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 Paddle-id patterns (`ctm_*`, `sub_*`, `txn_*`, etc.), currency-amount patterns, email patterns, or magic-link token patterns.
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, in place) 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 Paddle webhook event to grant themselves supporter status without paying.
Defence: the POST /api/billing/webhookhandler verifies the Paddle-Signature header — an HMAC-SHA256 computed over ts:rawBody — againstPADDLE_WEBHOOK_SECRET, comparing in constant time and rejecting stale timestamps. Missing secret → 503 (service-not-configured); failed verification → 401. Replay protection: every Paddle event id is upserted intopaddle_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. Paddle-side compromise
Card data lives in Paddle's vault — Paddle is the Merchant of Record and the only party that ever touches the payment instrument. A Paddle breach exposes card data we never had. Trust boundary: Paddle'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 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 read the pre-moderation submission 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 and the 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. 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 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 frommikrotikfilters.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 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 is already complete.
- Launch checklist — go-live verification doc covering HSTS preload submission, Sentry DSN configuration, Paddle live-mode (sandbox→production) 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.