Project principles
Warden’s design is not a list of features picked from a survey. It’s a small set of constraints that the codebase honors everywhere. Those explain both what the daemon does and what it deliberately does not do.
No registration needed
No login, no subscription, no cloud account in the loop.
There is no account to create because there is no customer base — there is a community. Installing the binary is the whole onboarding. Nothing phones home: no telemetry, no usage data, no crash reports.
No service runs on your behalf, so there is nothing to bill for. Warden is funded by donations, not subscriptions.
- No login on the daemon, no login on the TUI, no vendor-issued API key.
- You choose your own blocklists. Subscribe to any URL that serves a domain list.
The policy you write never leaves the machine you wrote it on.
Open code
Free to download, run, fork, audit, modify.
Warden is released under the GNU AGPLv3: free to use, study, share, and modify — commercial use included. Any modified version you distribute, or run as a network service for others, must be offered under the same license.
- Fork the repository if you want to change a behaviour.
- The license already lets you redistribute your changes. One canonical Warden that everyone sharpens beats a scatter of private forks.
Single binary, zero dependencies
One static binary, one TOML file, one systemd unit. Runs on a Pi Zero 2 W in tens of megabytes.
There is no Redis, no PostgreSQL, no Docker requirement, no Elasticsearch, no scrape endpoint, no cloud account.
- Steady-state memory on a Raspberry Pi Zero 2 W is ~29 MB RSS, no swap touched.
- The install path makes no network calls beyond fetching the blocklists you configure.
- One systemd unit for the daemon, one TUI binary for ad-hoc inspection.
Built in Rust
Memory safety and predictable performance — built into the language, not bolted on at runtime.
The resolver sits on the network’s hot path. Rust is how that path stays both safe and flat.
| Property | What you get |
|---|---|
| Memory safety, no GC | Buffer overruns, use-after-free, and data races are caught at compile time |
| Predictable latency | No GC pause, no JIT warmup |
| Reload without stalls | Query-path state is ArcSwap — in-flight queries finish on the old map |
| Cheap lookups | CompactString keeps typical names off the heap; the common filter is one HashSet lookup |
| Trade | The whole blocklist lives in RAM. Tens of megabytes for a curated set, a few hundred for a multi-million-domain bundle |
You spend memory so the hot path never touches the disk.
Native TUI, not GUI
Inspection is a tool you launch when you need it, not a server that runs all the time.
There is no built-in web UI. A web UI would add an HTTP server, a session store, and attack surface to the daemon. The TUI uses none of that when it is closed.
- Launch it to inspect, close it when you are done. The resolver does not notice.
- No HTTP listener — one fewer thing to firewall.
- The TUI talks to the daemon over a Unix socket on the same machine. The boundary is a filesystem permission, not a network credential.
Security as a cornerstone
Not a feature list. How the daemon is built.
The named threats, the fail-closed checks, and the host sandbox are on Security by design. The inventory is the threat model.
The tree is open. You can read it, build it, and audit it yourself. Dependencies are CVE-scanned in CI.
Declarative configuration
git diff config.toml is the complete picture of what the daemon will do.
The file on disk is the policy. There is no live state that drifts from it. How that file splits, merges, and reloads: Declarative configuration.
Lightweight, file-based observability
JSON counters dumped to disk. Query logs as JSON-line files with daily rotation. No metrics daemon.
Stats live as atomic counters and snapshot to disk. Query logs rotate daily at UTC midnight.
- Read stats with
jq; graph with whatever you already use. - Retention defaults to seven days (
retention_days). - Prometheus export and external-database query logging are out of scope.
How to read the log: Query log.