Skip to content

feat: add --host to bind the desk beyond loopback for remote review - #63

Merged
ymansurozer merged 2 commits into
mainfrom
feat/host-flag
Jul 17, 2026
Merged

feat: add --host to bind the desk beyond loopback for remote review#63
ymansurozer merged 2 commits into
mainfrom
feat/host-flag

Conversation

@ymansurozer

Copy link
Copy Markdown
Owner

What

Adds a --host <addr> flag (with GALLEY_HOST env fallback) that binds the desk HTTP server to a given address, for remote-dev workflows — desk + agent on a tailnet-only server, reviewer's browser on another device — without a wrapper or proxy. The absolute default stays 127.0.0.1 (loopback-only): zero behavior change unless opted in.

How it composes with #51 (the origin lock)

The #51 Host/Origin guard is extended, never defeated:

  • A loopback bind trusts exactly the loopback authorities it always has (127.0.0.1/localhost/[::1]) — a foreign Host and DNS-rebinding still 403.
  • A non-loopback bind adds the machine's os.hostname(), the bound address (for a specific bind), and any GALLEY_ALLOWED_HOSTS (comma-separated) to the trusted set. So Host: devbox:41010 passes on a host named devbox, while Host: evil.example:41010 still 403s. GALLEY_ALLOWED_HOSTS covers names the hostname/address don't — e.g. a tailnet MagicDNS FQDN, which differs from the short hostname.

The derivation lives in a pure, exported resolveBinding(host, hostname, allowedHostsEnv) so the URL/authority logic is unit-tested without binding exotic addresses.

Two URLs

  • Browser URL (printed + opened): hostname-based when bound non-loopback (http://devbox:41010/).
  • Desk-lock URL (consumed by the same-machine agent subcommands await/comment/reload/status/stop): stays loopback-reachable (127.0.0.1) for loopback/wildcard binds, so the agent side is unaffected. A specific non-loopback bind — unreachable over loopback — uses that exact address for both. IPv6 literals are bracket-wrapped.

Binding beyond loopback prints a security warning: the desk API is unauthenticated (runs the configured editor command, mutates the git index, reads any repo file), so it should be exposed only on a fully trusted network.

Docs

README gets a "Reviewing on a remote machine" section with the honest security warning; the --host/GALLEY_HOST/GALLEY_ALLOWED_HOSTS flags are in --help, and src/spec.ts notes the lock URL stays loopback-reachable (per the CLAUDE.md rule to update the spec on flag changes).

Tests

  • Pure resolveBinding: loopback default (widens nothing, both URLs loopback), wildcard (hostname browser URL + loopback lock), specific IP + specific name, IPv6 bracketing, GALLEY_ALLOWED_HOSTS merge.
  • Integration: default bind still 403s the machine hostname (guards the "no widening" property); --host 0.0.0.0 accepts the hostname authority and loopback while a foreign Host still 403s; GALLEY_ALLOWED_HOSTS members pass and non-members 403.

All repo checks green locally: pnpm check, pnpm lint, pnpm test (209), pnpm build, pnpm smoke, pnpm perf-smoke.

🤖 Generated with Claude Code

ymansurozer and others added 2 commits July 17, 2026 16:23
The desk has always bound 127.0.0.1 only. Remote-dev setups (agent + desk
on a server, reviewer's browser on a laptop) needed a wrapper or proxy to
reach it. Add a --host <addr> flag (GALLEY_HOST env fallback) that binds
both listen sites (preferred port + the EADDRINUSE random fallback) to the
given address. Default stays 127.0.0.1 — zero behavior change unless opted in.

Composes with the #51 origin lock rather than defeating it: a non-loopback
bind EXTENDS the trusted authorities with the machine hostname, the bound
address (for a specific bind), and any GALLEY_ALLOWED_HOSTS (comma-separated,
for names the hostname/address don't cover, e.g. a tailnet MagicDNS FQDN). A
loopback bind widens nothing — a foreign Host (and DNS-rebinding) still 403s.

Two URLs now diverge: the printed/opened browser URL is hostname-based when
bound non-loopback, while the desk-lock URL stays loopback-reachable so the
same-machine agent subcommands (await/comment/reload/status/stop) are
unaffected. A specific non-loopback bind (unreachable over loopback) uses that
exact address for both. IPv6 literals are bracket-wrapped. Binding beyond
loopback prints a security warning — the desk API is unauthenticated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ymansurozer
ymansurozer merged commit 1635f4d into main Jul 17, 2026
1 check passed
@ymansurozer
ymansurozer deleted the feat/host-flag branch July 17, 2026 13:50
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