Tracked deferral from up --auto-forward (spec 015-auto-forward-ports, task T057; shipped in #188). v1 binds loopback only; this adds an explicit opt-in to bind a non-loopback address.
Goal / acceptance
An explicit opt-in binds host listeners on 0.0.0.0 (or a chosen interface) so other machines on the LAN can reach the forwarded service directly, with a documented security review.
Hidden issues / gotchas
- It's a deliberate security downgrade. v1's
127.0.0.1-only bind (crates/core/src/port_forward/registry.rs::try_reserve, hard-coded "127.0.0.1") is what keeps a container's services off the network. Binding 0.0.0.0 exposes them to anyone routable to the host. Must be off by default, opt-in, and loud.
- Privileged-port story still applies. We always remap
<1024 to >=1024 to avoid host root; LAN exposure doesn't change that.
- Most "remote access" use cases don't need this. Reaching a forwarded port from a laptop is better done over SSH (
ssh -L laptop:127.0.0.1:host you@devhost) — SSH resolves the target on the remote host's loopback, so the existing loopback bind already works and stays private. LAN bind is only for direct, un-tunneled access by other hosts (teammate on the same network, a phone testing a mobile build).
- Event-reporting/contract:
host_ip in PORT_EVENT and the registry/contracts/*.schema.json assume 127.0.0.1; a non-loopback bind must thread the real bind address through reporting (the stderr mapping line and PortEvent.host_ip).
Recommended path forward
- Add an explicit flag, e.g.
--auto-forward-bind <127.0.0.1|0.0.0.0|IP> (default 127.0.0.1), threaded into registry::allocate/try_reserve and the daemon's dial/report paths.
- Emit a one-time WARN when binding non-loopback, and document the exposure + the SSH-tunnel alternative in
README.md / SECURITY.md (extend the existing "Dynamic port forwarding" section).
- Update
PORT_EVENT.host_ip + the stderr mapping line + registry schema docs to carry the actual bind address.
- Docker integration test: bind
0.0.0.0, assert reachable via a non-loopback host IP; assert default stays loopback-only.
Security
Requires security review per the spec's Out-of-Scope note. Recommend defaulting off and gating behind the explicit flag only (no env-var/implicit enablement).
Tracked deferral from
up --auto-forward(spec015-auto-forward-ports, task T057; shipped in #188). v1 binds loopback only; this adds an explicit opt-in to bind a non-loopback address.Goal / acceptance
An explicit opt-in binds host listeners on
0.0.0.0(or a chosen interface) so other machines on the LAN can reach the forwarded service directly, with a documented security review.Hidden issues / gotchas
127.0.0.1-only bind (crates/core/src/port_forward/registry.rs::try_reserve, hard-coded"127.0.0.1") is what keeps a container's services off the network. Binding0.0.0.0exposes them to anyone routable to the host. Must be off by default, opt-in, and loud.<1024to>=1024to avoid host root; LAN exposure doesn't change that.ssh -L laptop:127.0.0.1:host you@devhost) — SSH resolves the target on the remote host's loopback, so the existing loopback bind already works and stays private. LAN bind is only for direct, un-tunneled access by other hosts (teammate on the same network, a phone testing a mobile build).host_ipinPORT_EVENTand the registry/contracts/*.schema.jsonassume127.0.0.1; a non-loopback bind must thread the real bind address through reporting (the stderr mapping line andPortEvent.host_ip).Recommended path forward
--auto-forward-bind <127.0.0.1|0.0.0.0|IP>(default127.0.0.1), threaded intoregistry::allocate/try_reserveand the daemon's dial/report paths.README.md/SECURITY.md(extend the existing "Dynamic port forwarding" section).PORT_EVENT.host_ip+ the stderr mapping line + registry schema docs to carry the actual bind address.0.0.0.0, assert reachable via a non-loopback host IP; assert default stays loopback-only.Security
Requires security review per the spec's Out-of-Scope note. Recommend defaulting off and gating behind the explicit flag only (no env-var/implicit enablement).