Skip to content

fix(core): fold loopback hosts to 127.0.0.1 in tenant host normalization - #4784

Open
tonbistudio wants to merge 1 commit into
block:mainfrom
tonbistudio:fix/loopback-host-fold
Open

fix(core): fold loopback hosts to 127.0.0.1 in tenant host normalization#4784
tonbistudio wants to merge 1 commit into
block:mainfrom
tonbistudio:fix/loopback-host-fold

Conversation

@tonbistudio

Copy link
Copy Markdown

Problem

On any deployment with a loopback BUZZ_DOMAIN (e.g. the compose stack with BUZZ_DOMAIN=localhost), the desktop app connects fine but every managed-agent harness gets 404 Not Found on the WebSocket upgrade and exits with code 1.

The two sides disagree about loopback equivalence:

  • Clients canonicalize. buzz-core::relay::normalize_relay_url rewrites every loopback host (localhost, [::1], 127.0.0.0/8) to 127.0.0.1. The desktop runs the workspace relay URL through it for the managed-agent runtime key (ManagedAgentRuntimeKey::new) and passes the canonicalized URL to the spawned harness as BUZZ_RELAY_URL (managed_agents/runtime.rs). buzz-auth's NIP-42 verification applies the same equivalence (nip42.rs normalize_relay_url).
  • The relay compares literally. Host-based community resolution (buzz-core::tenant::normalize_host) does no loopback folding, so a community stored under localhost:3000 never matches a connection whose Host is 127.0.0.1:3000.

The repo already half-knows about this footgun — desktop/tests/helpers/seed.ts warns: "localhost != 127.0.0.1 to normalize_host — … or every /query 404s."

Reproduction: deploy deploy/compose with BUZZ_DOMAIN=localhost, add the community in the desktop app as ws://localhost:3000, start any managed agent. The app works; the agent harness logs initial relay connect failed with terminal error: WebSocket error: HTTP error: 404 Not Found and dies.

Fix

Fold loopback hosts (localhost, [::1], any 127.0.0.0/8 address) to the canonical 127.0.0.1 in normalize_host — the single normalization rule shared by storage (communities.host) and lookup — preserving explicit non-default ports. This mirrors the equivalence clients already apply, and it lands at the one choke point, so relay_url_authority (startup seeding, bind_deployment_community, buzz-admin) inherits it automatically.

Non-loopback hosts are unchanged; near-misses (localhost.example, 128.0.0.1) stay distinct tenants (covered by tests).

Tests

  • buzz-core: new normalize_host_folds_loopback_variants and normalize_host_does_not_fold_non_loopback; existing tests that pinned the unfolded behavior updated ([::1] cases now use non-loopback IPv6 literals to keep testing bracket preservation).
  • buzz-relay: new deployment_loopback_spellings_bind_to_one_community regression test — a community seeded from any loopback RELAY_URL spelling binds for all loopback connection hosts.
  • cargo test -p buzz-core and cargo test -p buzz-relay --lib tenant pass.

Compatibility note

An existing deployment whose community row is stored under a localhost host key will, after this change, be ensured/looked up under 127.0.0.1. That is the intended convergence, but operators of existing loopback deployments will see a fresh deployment community unless stored loopback host rows are rewritten. If preferred, I can add a migration that folds existing stored loopback hosts — happy to take guidance from maintainers on which shape you want.

An alternative fix (passing the un-normalized URL to spawned harnesses) was considered and rejected: it only fixes the desktop's own agents, while this fixes all clients that canonicalize, and keeps the NIP-42 equivalence consistent end to end.

🤖 Generated with Claude Code

Clients canonicalize loopback relay URLs to 127.0.0.1 before connecting:
buzz-core's normalize_relay_url rewrites localhost/[::1] for the managed
agent runtime key, the desktop passes that canonicalized URL to spawned
agent harnesses, and buzz-auth's NIP-42 check applies the same
equivalence. Host-based community resolution compared the literal Host
header, so a deployment with BUZZ_DOMAIN=localhost was half-reachable:
the desktop app connected fine while every agent harness got 404 on the
WebSocket upgrade and exited with code 1.

Fold localhost, [::1], and any 127.0.0.0/8 address to 127.0.0.1 in
normalize_host - the single normalization rule shared by storage and
lookup - preserving explicit non-default ports. Non-loopback hosts are
unchanged, including near-misses like localhost.example.

Note for existing loopback deployments: a community stored under a
localhost host key will be ensured under 127.0.0.1 on next startup;
operators should set BUZZ_DOMAIN/RELAY_URL to 127.0.0.1, or stored
loopback host rows can be rewritten by migration.

Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: ntombisol <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant