Threat model
Warden’s security posture is stated in the open. This page names every threat the daemon is built to resist, the ones it deliberately does not cover, and the switches an operator must turn on to claim the full posture.
Where this comes from
This is the public rendering of the
SECURITY.md threat model that ships in the Warden source tree. When the two disagree, the source tree wins.In-scope threats
Fourteen named classes of risk, each with an independent control rather than one perimeter that has to do everything.
| # | Threat | Control |
|---|---|---|
| 1 | Ad / tracker / malware domain resolution | Multi-format blocklist engine (domain, AdGuard ||domain^, hosts) with per-client profiles, subdomain-walk matching, and CNAME deep inspection |
| 2 | External list supply-chain attacks | Sandboxing — external lists cannot use @@ allow rules, $important overrides, or regex; only your own config.toml rules have those powers |
| 3 | SSRF via list downloads | Hardened HTTP client: HTTPS-only on the initial URL and every redirect, literal private / CGNAT / loopback / link-local IPs rejected, body capped at lists.max_body_bytes |
| 4 | DNS amplification / reflection | Response Rate Limiting per /24 (v4) or /48 (v6) prefix, plus always-on RFC 8482 ANY-query refusal (a single HINFO answer instead of a multi-KB response) |
| 5 | Query flooding / per-client abuse | Atomic token-bucket rate limiter per client IP (queries_per_second, with a burst allowance for page loads) |
| 6 | DNS tunneling (data exfiltration) | Three per-query heuristics: per-label length, Shannon entropy across labels, and per-domain unique-subdomain rate |
| 7 | DoH / DoT bypass | Anti-bypass module blocks resolution of 40+ known public resolver domains (Google DNS, Cloudflare, Quad9, NextDNS, …) plus operator extra_domains |
| 8 | Memory DoS (unique-source flooding) | Every stateful security module uses a bounded map (100k-entry cap, approximate-LRU eviction) — a flood from unique IPs cannot pin unbounded memory |
| 9 | Open-resolver misconfiguration | Fail-closed validator: Warden refuses to boot when it binds 0.0.0.0 / [::] with an empty server.allow_from |
| 10 | Profile hijacking via IP spoofing | MAC enforcement — with server.enforce_device_mac (default on), the resolver cross-checks the ARP table at query time; a mismatch strips the device’s own overrides and re-enters the profile cascade at the subnet level (a matching subnet profile wins, else the default profile, else refused) |
| 11 | API and IPC brute-force | Bearer-token auth with constant-time comparison and per-IP lockout after 10 failures; the IPC socket adds a three-tier ReadOnly / Mutating / Admin gate |
| 12 | DNS rebinding | The query validator flags labels shaped like four consecutive octets (e.g. 192.168.1.1.evil.com) |
| 13 | Cache poisoning | Cache keys include (domain, record_type, dns_class); blocked responses are never cached; entries are weighted to blunt negative-cache flooding |
| 14 | PID-file race conditions | flock(LOCK_EX|LOCK_NB) on the PID file — the kernel enforces mutual exclusion, so a second instance refuses instead of racing |
Out of scope
Named on purpose, so the trade-offs are visible. These belong to a different layer — Warden does not pretend to cover them.
- ARP spoofing / poisoning — Warden reads the OS ARP table but does not validate it. Use 802.1X, static ARP entries, or a managed switch with port security.
- IDN homoglyph attacks — domains are matched as ASCII after IDNA encoding; visually similar Unicode (e.g.
gооgle.comwith a Cyrillic “о”) is a browser / list-curation concern. - Shared-user privilege escalation — anyone who can read
config.tomlor the token file shares admin access. Use OS-level user separation. - Encrypted DNS from clients — a client with a hardcoded resolver IP talking its own DoH/DoT cannot be stopped at the DNS layer. Redirect or block outbound 53 / 443 / 853 at the firewall (
warden firewall-rulesgenerates the templates). - Physical network access — Warden can only filter queries it receives. Use switch-level (802.1X / MAC) port authentication.
- Compromise of the Warden host — shell access to the machine means all bets are off. Harden the host OS, keep it patched, restrict SSH.
Operator Configuration Contract
Warden filters out of the box. To also resist active abuse, the operator must turn on all eight of these.
| Requirement | Config | Why |
|---|---|---|
Set allow_from when binding a non-loopback address | server.allow_from = ["10.10.1.0/24"] | Prevents open-resolver abuse |
| Generate an API/IPC token | warden token generate | Gates config changes and admin commands |
| Pin client MACs | [[devices]] with mac = "AA:BB:…" | Prevents IP-spoofing profile hijack |
Keep enforce_device_mac enabled | server.enforce_device_mac = true (default) | Cross-checks the ARP table at query time |
| Block outbound DNS at the firewall | warden firewall-rules | Stops clients from bypassing Warden |
| Use a restrictive default posture | leave server.default_profile unset (refuse unknown clients), or set a broadly-denying default profile | Unknown clients are refused or filtered, never served openly |
| Rotate tokens periodically | warden token regenerate | Limits exposure from a leaked token |
| Keep lists updated | lists.update_interval_secs (default 43200, 12h) | Stale lists miss new threats |