feat(odoh): reject relay+target sharing an eTLD+1 #123

Merged
razvandimescu merged 1 commits from feat/odoh-etld1-check into main 2026-04-21 00:06:13 +08:00
razvandimescu commented 2026-04-20 23:47:09 +08:00 (Migrated from github.com)

Summary

  • Plain host-string equality caught the copy-paste-same-URL footgun, but let r.cloudflare.com + odoh.cloudflare.com through — two subdomains of the same operator collapse ODoH to ordinary DoH.
  • Add a second validation layer in UpstreamConfig::odoh_upstream(): compare registrable domains via the Public Suffix List (psl crate) after the exact-host check.
  • Fails open on IP literals and unparseable hosts; the exact-host check still runs in those cases. Runs once at config load (cold path).

Test plan

  • cargo fmt --check
  • cargo clippy -- -D warnings
  • cargo audit
  • cargo test (348 pass, including 4 new cases)
    • odoh_rejects_shared_registrable_domainr.cloudflare.com + odoh.cloudflare.com
    • odoh_rejects_shared_registrable_under_multi_label_suffixa.foo.co.uk + b.foo.co.uk
    • odoh_accepts_distinct_registrable_under_multi_label_suffixrelay.foo.co.uk + target.bar.co.uk
    • odoh_accepts_distinct_private_psl_suffix_subdomainsfoo.github.io + bar.github.io ✓ (PSL treats github.io as a public suffix)
  • Manual: confirm default pair (odoh-relay.numa.rs + odoh.cloudflare-dns.com) still boots cleanly
## Summary - Plain host-string equality caught the copy-paste-same-URL footgun, but let `r.cloudflare.com` + `odoh.cloudflare.com` through — two subdomains of the same operator collapse ODoH to ordinary DoH. - Add a second validation layer in `UpstreamConfig::odoh_upstream()`: compare registrable domains via the Public Suffix List (`psl` crate) after the exact-host check. - Fails open on IP literals and unparseable hosts; the exact-host check still runs in those cases. Runs once at config load (cold path). ## Test plan - [x] `cargo fmt --check` - [x] `cargo clippy -- -D warnings` - [x] `cargo audit` - [x] `cargo test` (348 pass, including 4 new cases) - `odoh_rejects_shared_registrable_domain` — `r.cloudflare.com` + `odoh.cloudflare.com` ✗ - `odoh_rejects_shared_registrable_under_multi_label_suffix` — `a.foo.co.uk` + `b.foo.co.uk` ✗ - `odoh_accepts_distinct_registrable_under_multi_label_suffix` — `relay.foo.co.uk` + `target.bar.co.uk` ✓ - `odoh_accepts_distinct_private_psl_suffix_subdomains` — `foo.github.io` + `bar.github.io` ✓ (PSL treats `github.io` as a public suffix) - [ ] Manual: confirm default pair (`odoh-relay.numa.rs` + `odoh.cloudflare-dns.com`) still boots cleanly
Sign in to join this conversation.