Conversation
…T_ALIASES A community resolves from exactly one `communities.host`, and tenant binding rejects every other Host before NIP-98/42 runs. A deployment legitimately reachable on two addresses (e.g. a public CDN host plus an internal tailnet host) therefore cannot authenticate on the second address — the signed NIP-98 `u` tag / NIP-42 `relay` tag never matches (issues block#4952, block#4953). Add an opt-in `BUZZ_HOST_ALIASES` env var ("alias=canonical", comma-separated). An arriving request whose Host is a configured alias binds to the community of its canonical host, while `TenantContext` keeps the *arrival* host — so the existing `nip98_expected_url` / `nip42_expected_relay_url` helpers validate against exactly the URL the client signed, with no change to the verification logic and no wildcard or prefix matching anywhere. Guarantees: - Default (unset/empty) yields an empty map and the exact pre-existing code path — no behavior change for current deployments. - A real `communities.host` always wins; the alias map is consulted only on a resolver miss, then the canonical is resolved through the same fail-closed path, so an alias can never shadow a real community host. - Exact matching only, on the normalized host; startup validation rejects malformed pairs, duplicate aliases, self-aliases, alias chains, and any host that fails the existing `communities.host` grammar. - Unmapped alias and unmapped host return the identical generic rejection. Signed-off-by: obbax <robinniclasandersson@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 81995d7ed8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Ok(None) => Err(BindError::UnmappedHost), | ||
| Ok(None) => match host_aliases.get(&host) { | ||
| Some(canonical) => match resolver.resolve_host(canonical).await { | ||
| Ok(Some(community)) => Ok(TenantContext::resolved(community, host)), |
There was a problem hiding this comment.
Preserve canonical NIP-05 handles on alias requests
When a user whose profile contains alice@chat.example.com submits that kind-0 event through an alias such as internal.tailnet.example, this context exposes the alias as tenant.host(). handle_kind0_profile then validates the handle against that alias (handlers/side_effects.rs:1225-1229), converts the mismatch to an empty handle, and writes it to the user record, silently erasing the existing canonical NIP-05 identity. Preserve both the arrival authority and the community's canonical host, using the former for request-signature checks and the latter (or the complete configured host set) for persistent NIP-05 validation.
Useful? React with 👍 / 👎.
| let (alias, canonical) = entry | ||
| .split_once('=') | ||
| .expect("exactly one '=' checked above"); |
There was a problem hiding this comment.
Remove the production-path expect
This is a newly introduced expect() in startup configuration parsing, contrary to the repository's explicit requirement to use proper error propagation in production paths. Even though the preceding count check currently makes None unreachable, keep this parser fallible by converting a failed split into ConfigError::InvalidValue rather than adding a panic site.
AGENTS.md reference: AGENTS.md:L114-L117
Useful? React with 👍 / 👎.
Problem
A community resolves from exactly one
communities.host, and tenant binding rejects every otherHost(fail-closed,UnmappedHost) before NIP-98/NIP-42 runs. A deployment that is legitimately reachable on two addresses — e.g. a public CDN hostname plus an internal tailnet hostname — therefore cannot authenticate on the second address: the signed NIP-98utag / NIP-42relaytag is reconstructed from the tenant host and never matches the address the client actually hit.Concretely, a request to the internal address gets:
This is the root cause behind #4952 and #4953.
Approach
New opt-in env var
BUZZ_HOST_ALIASES— comma-separatedalias=canonicalpairs:A request whose
Hostis a configured alias binds to the community of its canonical host, butTenantContextkeeps the arrival host. Becausenip98_expected_url/nip42_expected_relay_urlalready derive fromtenant.host(), they now validate against exactly the URL the client signed — no change to the verification logic, and no wildcard or prefix matching anywhere.Guarantees (and how they're enforced)
config.rsempty-parse test;tenant.rsempty-map test.)communities.hostalways wins. The alias map is consulted only on a resolverOk(None), then the canonical is resolved through the same fail-closed path — an alias can never shadow a real community host. (Enforced in the resolution branch, not just documented; covered by a shadowing test.)communities.hostgrammar (reusesvalidate_host, no new grammar).Tests
New coverage in
tenant.rs,config.rs, andbridge.rs(13 cases), notably:ctx.host()stays the arrival host;cargo fmt --check,cargo clippy -p buzz-core -p buzz-relay --all-targets -- -D warnings, andcargo test -p buzz-core -p buzz-relayare clean (the only failing tests in my environment are pre-existing and Postgres-connectivity-related — reproduced identically on an unmodified base).Known tradeoffs (deliberate, flagged for review)
Closes #4952, closes #4953.