Changelog

Every release. What shipped. What didn't.

Honest version history. Feature flags, design rollbacks, security fixes — all of it. If we shipped something only to walk it back, you'll see that here too.

v0.342026-05-29

feat Blocklist catalog 30 → 86 + marketing-claim integrity pass

Added 56 community blocklists across 5 new categories (AI scrapers, safety, additional regional/language sets). Closes the "smaller catalog than NextDNS" gap that was a real spec-sheet objection.

Integrity pass on the marketing site: removed claims we couldn't back — "ISO 27001 certified" (we're not), "HIPAA BAA on every plan" (we don't offer one), "FedRAMP in process" (we aren't), "4,200+ security teams worldwide" (zero customers today), the fabricated company timeline with the $74M Series B, and the named testimonial from a fictional Director of Security. Replaced with honest current-state copy and a design-partner call. Pre-customer / pre-revenue is the truth; the site now says so.

v0.332026-05-28

feat Hardened headed Playwright + viewer-role test + QA seed

End-to-end test now reads DOM nodes directly (not regex over innerText), asserts exactly 3 QA devices with expected DoH URL shape, clicks the device "copy" button and reads navigator.clipboard back, parses the expanded audit row's <pre> as JSON, and runs a separate viewer-role test that PUT /policies/filtering must 403.

Added scripts/qa_data_seed.sh so the 24h analytics window stays full across days.

v0.322026-05-28

fix Audit table nested-template broke x-for row iteration

Alpine's x-for requires a single root per iteration. A leftover nested <template> meant no rows ever rendered, only the header. Replaced with <tbody> per row (HTML allows multiple tbody per table). Caught by the hardened Playwright spec from v0.33.

v0.312026-05-28

security Fix viewer-role write bypass in require_scope

The scope gate returned Ok unconditionally for any human JWT and assumed "role checks apply instead" — but no policy handler called require_admin. A viewer JWT could PUT /policies/filtering, /policies/custom-rules, etc.

Fixed: human admins still pass every scope; viewers now only pass :read scopes. :write from a non-admin JWT returns 403. Added a regression unit test and the viewer Playwright test from v0.33 that surfaced it.

v0.302026-05-28

fix Per-device queries silently dropped from 9 analytics endpoints + new /version

Since v0.23 introduced per-device DoH (<tenant_uuid>--<slug>), the where_client helper's exact-match check missed every per-device row. Summary, top-domains, top-clients, top-ai-tools, blocked, by-reason, latency, block-rate all undercounted. Switched to LIKE '<tenant_uuid>%'.

Also: new GET /version returns git SHA + build timestamp so ops can verify what's actually running.

v0.292026-05-27

feat Dashboard gains Devices, Threats (DGA+Typosquat), Policies read-only views

Three new read-only screens for surfaces that previously only existed in the API:

  • Devices: per-device DoH URL with copy-to-clipboard button + 24h activity counts
  • Threats: now includes the v0.27 DGA classifier rankings and typosquat detections against your protect-list
  • Policies (read-only): filtering state, default-deny mode, NRD blocking, blocked TLDs, custom rules, protect-list entries, DNS rewrites, blocklist subscriptions

Bundled with the v0.23 device routing fix (AGH rejected / in ClientID; switched separator to --).

v0.282026-05-27

feat Reconcile cron + x-required-scopes + 5 MCP workflow tools

The 5-minute reconcile cron now keeps the AGH global filter pool aligned with per-tenant subscriptions automatically (the v0.25 endpoint was admin-only manual). OpenAPI spec gains x-required-scopes on every secured operation. New high-level MCP workflow tools wrap common multi-step operations (onboard-tenant, triage-typosquat) for AI agents.

v0.272026-05-26

feat Threat-intel v2 — transparent DGA + Typosquat classifiers

Two inline classifiers run at ingest with no vendor feeds:

  • DGA scoring (~280 LoC, src/dga.rs): entropy + ngram + vowel-ratio feature scoring. Surfaces via /analytics/top-dga; tunable threshold (default 0.7); no auto-block.
  • Typosquat detection (~230 LoC, src/typosquat.rs): Damerau-Levenshtein ≤ 2 with homoglyph normalization, length-band pruning. Only scores queries for tenants with a non-empty protect-list. Surfaces via /analytics/top-typosquat.

"Scenario A" — chosen over a vendor-feeds option for explainability + zero per-query cost. Future scenarios B/C remain on the table for higher-cost / higher-recall workloads.

v0.262026-05-26

fix DB-driven feature_flags table (replaces hardcoded filter ids)

The filter-list IDs that drive NRD/TLD/parked toggles were hardcoded in policies.rs. Moved to a feature_flags table so they can be migrated, re-numbered, and disabled per environment without a redeploy.

v0.252026-05-26

feat POST /blocklists/reconcile — drift-correct AGH from Postgres truth

AGH's loaded filters[] is a single global list shared across tenants; per-tenant subscriptions live in Postgres. Reconcile diffs them and applies adds/removes idempotently. Admin-only (tenant 1). Pairs with the cron in v0.28.

v0.242026-05-26

feat Allowlist-only (default-deny) mode

Per-tenant toggle that flips DNS resolution from "block what's bad" to "allow only what's explicitly permitted." Implemented via a catch-all wildcard block rule with the tenant suffix, layered under existing per-tenant allow entries. The "Fortune-500 healthcare desktop" mode.

v0.232026-05-26

feat Per-device identification

Devices are first-class: each gets a unique slug + display name, and AGH ClientID becomes <tenant_uuid>--<slug>. Every analytics endpoint can now answer "which device looked up X?" Pairs with the dashboard Devices view shipped in v0.29.

Note: v0.23 originally used / as the separator, which AGH rejects as an invalid hostname-label character. Fixed in v0.29 to use --; per-device queries were SERVFAIL until then. Caught by end-to-end QA, not by a customer.

v0.222026-05-26

feat HMAC-signed outbound webhooks

Every audit event can fan out to a webhook endpoint. Bodies signed with HMAC-SHA256 over the payload using a per-webhook secret; timestamp + event-name headers; wildcard event matching (policy.*, threat.*, etc.). Backed by a single delivery worker with retry-with-backoff.

v0.212026-05-25

feat Logs anonymization + per-tenant retention + bypass-bypass

Three GDPR/DPDP-flavored controls per tenant:

  • Anonymize client IPs at ingest — drop the source IP from ClickHouse rows for tenants who don't want device-level identification
  • Per-tenant retention (1-365 days) instead of one global TTL
  • Bypass-bypass blocklists: subscribe to the Hagezi DoH-bypass and Tor-exit lists to block DNS-over-HTTPS evasion paths
v0.202026-05-25

feat Curated MCP tool descriptions

All 33 MCP tools now carry hand-written when / why / sharp edges descriptions instead of auto-generated "Executes GET /api/v1/..." fallbacks. Measurably improves how AI agents (Claude, Cursor, Continue) pick the right tool on the first try.

v0.192026-05-25

breaking Dashboard is now read-only

The four configuration screens (Policies, Categories, Users, Settings) are gone. Configuration happens via REST API or via the MCP server — agents do the typing, humans review via the Audit Log.

  • New: Audit Log viewer with actor pills (human session vs specific agent token), filterable by actor type
  • Net −326 LoC of dashboard code
  • Sidebar's "Configure" group now points users at the REST API + MCP endpoint
  • Three follow-up fixes: CSP allowing inline-script removal, structural HTML repair, pre-login 401 noise suppression + post-login hard-reload
v0.182026-05-25

feat Threat-intel toggles — NRD + TLD blocking

One-click toggles wrap existing primitives. Turn on newly_registered_domains to subscribe to the Hagezi NRD blocklist; pass a TLD list to block whole suffixes via wildcard rules.

Future toggles (parked-domain, DGA classifier, typosquat) are reserved in the response shape but return null — they need real classifiers and feeds, landing in a future release.

v0.172026-05-25

feat Per-tenant DNS rewrites

The ControlD-style Redirect feature: override any DNS answer for any domain. Five kinds — A, AAAA, CNAME, NXDOMAIN, REFUSED. Coexists cleanly with custom block/allow rules.

Implemented via AGH's $dnsrewrite modifier joined to the existing per-tenant client tag.

v0.162026-05-25

feat Blocklist catalog expanded 5 → 30 · CSV/NDJSON query log export

  • 25 new curated lists across 14 categories (threat, phishing, ads, tracking, adult, gambling, social, AI, fakenews, telemetry, NRD, TLDs, bypass)
  • New GET /api/v1/analytics/export?format=csv|ndjson for SOC handover and audit prep. Cap 10M rows per call
v0.152026-05-25

feat Full OpenAPI annotation · MCP exclusions for sensitive ops

All 35 API endpoints documented in the OpenAPI 3.1 spec at /api/v1/openapi.json. MCP server uses FastMCP's RouteMap(MCPType.EXCLUDE) to hide eight humans-only operations (login, password change, key mint/rotate/delete, tenant create/delete, user delete) from agents regardless of scope grant.

v0.142026-05-25

feat MCP server live at mcp.olladns.com

Agent-native config plane. FastMCP sidecar auto-generates tools from the OpenAPI spec, forwards the caller's X-API-Key on every upstream call, and respects per-key scopes end-to-end. Both stdio (Claude Desktop, Cursor) and Streamable HTTP transports.

v0.132026-05-25

feat OpenAPI 3.1 spec generation (utoipa)

Single source of truth for the API. Drives MCP tool generation, external SDK codegen, and human-readable docs.

v0.122026-05-24

feat Agent-completeness API gaps closed

  • GET /tenants/me — caller's own tenant (UUID, DoH URL, endpoints)
  • PATCH /tenants/:id — rename + contact_email
  • POST /auth/change-password — self-service, JWT-only
  • fix doh_url no longer leaks :8443
v0.112026-05-24

feat sec Scoped API keys + actor-attributed audit log

Foundation for agent delegation. Twelve resource:action scopes (analytics:read, policies:write, etc.); optional expiry; one-call rotation. Every audit row records actor type (user / api_key / system) and actor id — every config change is now traceable to a specific human session or a specific agent token.

Pre-v0.11 keys are auto-promoted to a legacy:full backstop scope for backward compatibility.

v0.102026-05-24

feat Dashboard SPA · api.olladns.com split

Alpine.js + fetch single-page app on login.olladns.com. API and dashboard now live on separate hostnames with proper CORS.

v0.92026-05-23

sec Hardening · DNSSEC · fail2ban · drop public :53

DNSSEC validation enabled on every resolver. sysctl hardening. Cloudflare token scoped to the single zone. fail2ban watching Caddy 4xx storms. Plain port 53 no longer exposed publicly — DDoS amplification surface eliminated.

Earlier versions (v0.1–v0.8) covered the initial DNS plane, AGH integration, ClickHouse ingest, per-tenant policy/blocklist subscriptions, audit logging, and the original v0.7 switch from Vector to a Rust AGH poller. See the git history for the full record.