Highlights
🛡️ DNS rebinding protection (opt-in)
Strips private/special-use addresses from upstream answers so a public domain can't point your browser at your router, NAS, or localhost:
[server]
rebind_protect = trueCovers RFC 1918, loopback, link-local, CGNAT/Tailscale ranges, NAT64 and ULA — including glue records and HTTPS/SVCB address hints. Local zones, overrides and .numa domains are never touched.
Running split-horizon DNS (public names → LAN IPs, plex.direct, DNSBL zones)? Stripped queries show up tagged in the dashboard query log with a one-click allow; the allowlist persists across restarts. Also available via REST: GET/PUT /rebind, POST /rebind/allowlist. (#279, #287, #293, #295, #296)
🚫 Manual blocklist from the dashboard
Block any domain (and its subdomains) with one click from the query log — no config edit, no restart, survives restarts. Unblock the same way, or via POST/DELETE /blocking/blocklist. Closes #257. (#294)
Also notable
.numareverse proxy now enforcesallow_fromand resolves services to the per-client egress IP (#263, #264)- Windows: static CRT —
numa.exeruns without the VC++ Redistributable (#261, closes #236) - Release binary ~42% smaller (fat LTO + strip, single rustls crypto provider) (#269, #270)
- Dashboard uptime now matches systemd wall-clock (#283)
All changes
- fix(nix): refresh v0.20.0 vendor hash + automate it in release.sh by @razvandimescu in #259
- docs(recipes): dnsdist ACL + public-resolver caveat (#243) by @razvandimescu in #260
- fix(windows): static CRT so numa.exe runs without VC++ Redistributable (#236) by @razvandimescu in #261
- blog: numa as tailnet resolver by @razvandimescu in #262
- feat(proxy): resolve .numa services to the per-client egress IP by @razvandimescu in #263
- chore: linked blogpost into readme by @razvandimescu in #265
- fix(doh): log rejected peer on allow_from denial by @razvandimescu in #267
- build: shrink release binary via fat LTO + strip by @razvandimescu in #269
- chore(deps): bump rust from 1.95-alpine to 1.96-alpine in the minor-and-patch group by @dependabot[bot] in #271
- chore(deps): bump docker/setup-qemu-action from 3 to 4 by @dependabot[bot] in #272
- chore(deps): bump docker/metadata-action from 5 to 6 by @dependabot[bot] in #273
- chore(deps): bump nix-community/cache-nix-action from 6 to 7 by @dependabot[bot] in #274
- chore(deps): bump the minor-and-patch group with 10 updates by @dependabot[bot] in #275
- chore(deps): bump windows-sys from 0.59.0 to 0.61.2 by @dependabot[bot] in #276
- chore(deps): bump quinn-udp from 0.5.14 to 0.6.1 by @dependabot[bot] in #277
- build(deps): use ring as the sole rustls crypto provider by @razvandimescu in #270
- feat(proxy): enforce allow_from on the .numa proxy listeners by @razvandimescu in #264
- build(nix): bump cargoHash for current vendor tree by @razvandimescu in #280
- fix(stats): report wall-clock uptime so dashboard matches systemd by @razvandimescu in #283
- fix(install): end install on next-steps, suppress CA-tool noise by @razvandimescu in #284
- feat(rebind): DNS rebinding protection (static filter, off by default) by @razvandimescu in #279
- docs(config): document HaGeZi NSFW blocklist path in example config by @razvandimescu in #290
- docs(config): document [[client_policy]] in example numa.toml by @razvandimescu in #291
- fix(nix): vendor hash-free via cargoLock.lockFile; sync Cargo.lock by @razvandimescu in #292
- refactor(persist): shared JSON helpers + PersistedDomainList store by @razvandimescu in #288
- feat(rebind): runtime API, stripped-query visibility, stats counter by @razvandimescu in #287
- feat(persist): durable rebind + blocking allowlists by @razvandimescu in #293
- feat(rebind): cover CGNAT/Tailscale + NAT64 ranges, scrub all sections by @razvandimescu in #295
- feat(blocking): manual blocklist from the dashboard (closes #257) by @razvandimescu in #294
- feat(dashboard): surface rebind protection state and stripped queries by @razvandimescu in #296
- fix(release): drop obsolete cargoHash refresh by @razvandimescu in #297
Full Changelog: v0.20.0...v0.21.0