#21436: fix(gateway): plaintext ws:// blocked for Docker bind=lan (SECURITY ERROR on private network)
gateway
size: S
trusted-contributor
Cluster:
Gateway Resilience and Configuration
## Problem
After 2026.2.18, `isSecureWebSocketUrl` rejects `ws://` to any non-loopback address. When `bind: lan` is configured (the documented Docker setup), the gateway URL resolves to a Docker bridge IP like `ws://172.18.0.2:18789`, which gets blocked:
```
SECURITY ERROR: Gateway URL "ws://172.18.0.2:18789" uses plaintext ws://
to a non-loopback address.
```
This breaks cron jobs, sub-agents, and config patches for all Docker deployments using `bind: lan`. Existing cron jobs still fire but cannot be modified. No workaround exists — switching to `bind: loopback` kills external connectivity since Docker maps ports to the container LAN IP.
## Root cause
`isSecureWebSocketUrl` only allows `ws://` for loopback. Docker bridge IPs (172.16.0.0/12) are private addresses that never leave the host, but the check does not distinguish locally-resolved URLs from user-provided ones.
## Fix
Add `allowPrivateNetwork` option to `isSecureWebSocketUrl`. When the URL was locally resolved from `bind=lan` config (not from a user override or remote URL), RFC1918 and CGNAT addresses are accepted for `ws://`. The existing `isPrivateOrLoopbackAddress` helper already covers these ranges.
User-provided URLs (`--url`, `gateway.remote.url`) still require `wss://` regardless.
5 files changed, 68 insertions(+), 12 deletions.
Closes #21065
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added `allowPrivateNetwork` option to `isSecureWebSocketUrl()` to allow plaintext `ws://` connections to RFC1918 and CGNAT private network addresses when the gateway URL is locally resolved from `bind=lan` configuration (common in Docker deployments). User-provided URLs via `--url` or `gateway.remote.url` still require `wss://` regardless of bind mode.
**Key changes:**
- `isSecureWebSocketUrl()` now accepts `allowPrivateNetwork` option that permits `ws://` to private addresses detected by `isPrivateOrLoopbackAddress()` (172.16.0.0/12, 192.168.0.0/16, 10.0.0.0/8, 100.64.0.0/10)
- `buildGatewayConnectionDetails()` sets `allowPrivateNetwork: true` only when URL is locally resolved AND `bind=lan` is configured
- `GatewayClient` passes through the `allowPrivateNetwork` option to its security check
- Tests updated to verify `ws://` to LAN IPs is allowed for locally-resolved `bind=lan` URLs
- Tests verify that `ws://` to public addresses remains blocked even with `allowPrivateNetwork: true`
**Security-critical behavior verified:**
- User-provided URLs (`--url`, `gateway.remote.url`) never get the private network exemption because `isLocallyResolved` is false when these are present
- `bind=tailnet` still requires TLS (no exemption) since Tailscale IPs can be accessed from other devices on the Tailnet
<h3>Confidence Score: 4/5</h3>
- Safe to merge with one minor test coverage gap
- The implementation correctly addresses the Docker `bind=lan` issue by adding `allowPrivateNetwork` option that only applies to locally-resolved URLs. Security-critical logic (user-provided URLs still require TLS) is correctly implemented via the `isLocallyResolved` check. Tests verify the main use case, but missing one edge case test for user-provided URLs with `bind=lan`.
- src/gateway/call.test.ts could benefit from additional test coverage for the user-provided URL + bind=lan scenario
<sub>Last reviewed commit: 96f42af</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21741: fix(gateway): allow plaintext ws:// for Docker/private network addr...
by Joe3112 · 2026-02-20
89.3%
#22056: fix(gateway): use loopback for self-connections regardless of bind ...
by usedhonda · 2026-02-20
85.1%
#22110: fix(tools): prefer loopback for internal tool-to-gateway RPC calls
by pierreeurope · 2026-02-20
85.1%
#21256: fix: treat ws:// to Tailscale addresses as secure when bind=tailnet
by jessewunderlich · 2026-02-19
80.9%
#21784: fix(security): restrict gateway ports to loopback by default
by smilekyra · 2026-02-20
80.8%
#21233: docs: clarify bind=lan non-loopback access requires wss or tunnel (...
by saurabhchopade · 2026-02-19
80.7%
#19026: fix(gateway): use loopback for local CLI-to-gateway connections
by Phineas1500 · 2026-02-17
80.7%
#21772: [Bug]: Allow ws:// to Tailscale CGNAT addresses
by AIflow-Labs · 2026-02-20
79.0%
#22343: fix(gateway): treat private LAN hosts as local direct
by AIflow-Labs · 2026-02-21
78.7%
#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws
by rjuanluis · 2026-02-20
78.3%