← Back to PRs

#21436: fix(gateway): plaintext ws:// blocked for Docker bind=lan (SECURITY ERROR on private network)

by xinhuagu open 2026-02-19 22:52 View on GitHub →
gateway size: S trusted-contributor
## 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