Skip to content

feat(web): Let the console bind to a configured host - #133

Merged
4ndreello merged 15 commits into
mainfrom
feat/web-console-host
Sep 25, 2026
Merged

4ndreello merged 15 commits into
mainfrom
feat/web-console-host

Conversation

@4ndreello

Copy link
Copy Markdown
Owner

Problem

The web console only listens on 127.0.0.1 (listen() in src/web/server.ts), and src/web/security.ts rejects every Host except 127.0.0.1:<port> and localhost:<port>. It can't be opened from another device, for example over Tailscale.

Change

  • web.host in the config (an IPv4 or IPv6 address; default 127.0.0.1, unchanged) and codedeck ui --host <ip> for one launch. A value that isn't an IP is ignored with Ignoring invalid web.host in config: .... --host exits 1 with --host must be an IP address.
  • The daemon web child (--host), the in-process fallback and the daemon autostart all bind to the resolved host.
  • web.ensure takes host (explicit, from ui --host) and preferredHost (from web.host). These follow the port rule: an explicit host is never moved by a later review/usage --web/setup, and a config host moves only a child that was started from config. The result now includes the active host. A non-IP host is rejected with WEB_BAD_HOST before the running child is touched.
  • Host allowlist on non-loopback binds: the bound IP, current interface IPs, the machine hostname, and <hostname>.<tailnet>.ts.net (MagicDNS). Anything else gets 403. The token, cookie, Origin and /api checks are unchanged. On 127.0.0.1, 0.0.0.0 and :: binds, localhost still redirects to 127.0.0.1.
  • On 0.0.0.0/:: the CLI prints an Also on http://<ip>:<port>/?t=... line for each non-internal IPv4 address. It warns whenever the bind isn't loopback, because the console is plain HTTP.
  • Docs in README.md and docs/protocol.md. Spec, tasks, validation and run report in .specs/features/web-host/.

To use it over Tailscale, set "web": { "host": "0.0.0.0" } (or run codedeck ui --host 0.0.0.0) and open the printed Also on http://<tailscale-ip>:7777/?t=... link once per browser.

Validation

Scoped vitest batches (--no-cache), after rebasing on main:

  • tests/web-port.test.ts tests/web-security.test.ts: 41 passed
  • tests/web-server.test.ts tests/web-child.test.ts: 36 passed
  • tests/web-supervisor.test.ts tests/daemon-web.test.ts: 53 passed
  • tests/web-launch.test.ts tests/web-cli.test.ts tests/web-host-docs.test.ts: 46 passed
  • npx tsc --noEmit -p .: clean

Mutation probe by an independent verifier: 21/21 behavior faults killed after two remediation rounds (validation.md, Verdict: PASS).

Smoke with a built child on 0.0.0.0, curled on the machine's real Tailscale IP:

tailscale IP, no token:   403
tailscale IP, with token: 303 -> http://100.120.77.101:36769/
tailscale IP, cookie:     200
localhost bookmark:       302 -> http://127.0.0.1:36769/
evil Host:                403 forbidden

The full suite was not run. Not covered: a real second device, :: with bindv6only=1, IPv6 address normalization in the host comparison.

4ndreello and others added 15 commits September 25, 2026 03:24
Co-Authored-By: Codex <noreply@openai.com>
Retain the loopback checks while accepting local interface and machine host names on other binds.

Co-Authored-By: Codex <noreply@openai.com>
Keep the loopback default and derive advertised URLs from the selected bind host.

Co-Authored-By: Codex <noreply@openai.com>
Preserve the loopback default when no child host argument is supplied.

Co-Authored-By: Codex <noreply@openai.com>
Compare the requested bind host during child reuse and advertise a host-specific URL.

Co-Authored-By: Codex <noreply@openai.com>
Log invalid web.host values and keep daemon startup on the resolved bind host.

Co-Authored-By: Codex <noreply@openai.com>
Resolve configured hosts, accept the ui override, and print trusted-network links and warnings.

Co-Authored-By: Codex <noreply@openai.com>
Describe the web.host setting, CLI override, child argument, and protocol parameter.

Co-Authored-By: Codex <noreply@openai.com>
Separate caller-selected binds from the resolved config preference. This keeps ordinary web commands from moving a child explicitly bound by `ui --host`.

Co-Authored-By: Codex
Keep localhost bookmarks on the canonical loopback URL for wildcard binds while skipping the redirect for specific hosts.

Co-Authored-By: Codex
Accept the selected bind IP even if interface enumeration omits it, and limit machine hostname suffixes to Tailscale MagicDNS names.

Co-Authored-By: Codex
Validate both host request fields before the supervisor changes a running child.

Co-Authored-By: Codex
Return the selected bind address with web.ensure so warnings and alternate links
match the child that is actually running.

Co-Authored-By: Codex <noreply@anthropic.com>
Add WH-12 tests for hostile MagicDNS names and wildcard bind Hosts.

Record the focused gate and mutation results.

Co-Authored-By: Codex <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@4ndreello
4ndreello merged commit d3e6ebf into main Sep 25, 2026
4 checks passed
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