#21256: fix: treat ws:// to Tailscale addresses as secure when bind=tailnet
gateway
size: S
Cluster:
Gateway Resilience and Configuration
## Problem
The CWE-319 security check added in 2026.2.19 blocks **all** `ws://` connections to non-loopback addresses. This is correct for general network traffic, but breaks users who set `gateway.bind: "tailnet"`.
Tailscale connections (100.64.0.0/10 CGNAT range) are encrypted at the network layer via WireGuard (ChaCha20-Poly1305). The plaintext `ws://` is only at the application layer — the actual network traffic is always encrypted. This is materially different from plain LAN or public internet `ws://` which truly are cleartext.
### Impact
After upgrading to 2026.2.19, users with `gateway.bind: "tailnet"` cannot use:
- `openclaw status`
- `openclaw gateway` commands
- Any CLI tool that connects to the gateway
- Cron tool, gateway config tool, or any internal tool call
The **only** workaround is setting up TLS certs via `gateway.tls.enabled: true`, which is heavy for a connection that is already encrypted.
## Solution
Add an optional `bindMode` parameter to `isSecureWebSocketUrl()`. When `bindMode === "tailnet"` **AND** the address is in the Tailscale CGNAT range (verified via existing `isTailnetIPv4()`), treat the connection as secure.
This is a **targeted exemption**, not a relaxation:
- Default behavior (no `bindMode`) remains strict — Tailscale IPs are still blocked
- Only `bindMode: "tailnet"` + verified Tailscale CGNAT address = secure
- Non-Tailscale IPs with `bindMode: "tailnet"` still throw `SECURITY ERROR`
- LAN IPs with any bind mode still throw `SECURITY ERROR`
- All `wss://` and loopback behavior is unchanged
### Why Tailscale is special
Unlike regular LAN IPs (192.168.x.x, 10.x.x.x), Tailscale 100.x addresses are **only reachable through the WireGuard tunnel**. There is no unencrypted path to a Tailscale CGNAT address — the encryption is a property of the network, not the application.
## Changes
| File | Change |
|------|--------|
| `src/gateway/net.ts` | Add `bindMode` param to `isSecureWebSocketUrl`, import `isTailnetIPv4`, add tailnet exemption logic |
| `src/gateway/call.ts` | Pass `bindMode` to security check (1-line change) |
| `src/gateway/net.test.ts` | 6 new test cases covering tailnet exemption + edge cases |
| `src/gateway/call.test.ts` | Update tailnet test to expect success instead of throw |
**4 files changed, 55 insertions(+), 7 deletions(-)**
## Test Coverage
- ✅ `ws://100.64.0.1` with `bindMode="tailnet"` → secure
- ✅ `ws://100.127.255.254` with `bindMode="tailnet"` → secure
- ✅ `ws://100.64.0.1` without bindMode → still blocked (default strict)
- ✅ `ws://100.64.0.1` with `bindMode="lan"` → blocked
- ✅ `ws://192.168.1.100` with `bindMode="tailnet"` → blocked (not Tailscale)
- ✅ `ws://10.0.0.5` with `bindMode="tailnet"` → blocked (not Tailscale)
- ✅ Loopback still works with any bindMode
- ✅ All existing tests pass unchanged
## Context
Discovered after upgrading from 2026.2.17 → 2026.2.19 on a Mac Mini running OpenClaw with `gateway.bind: "tailnet"` for iPhone remote access via Tailscale. All CLI tools and internal tool calls immediately broke with `SECURITY ERROR`.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds Tailscale CGNAT exemption to the WebSocket security check to allow `ws://` connections to Tailscale addresses when `gateway.bind: "tailnet"`.
**Key changes:**
- Modified `isSecureWebSocketUrl()` to accept optional `bindMode` parameter and allow `ws://` to Tailscale CGNAT range (100.64.0.0/10) when `bindMode === "tailnet"`
- Updated `buildGatewayConnectionDetails()` in `call.ts` to pass `bindMode` to the security check
- Added comprehensive test coverage for the Tailscale exemption with 6 new test cases
- Updated existing test expectation in `call.test.ts` for Tailscale connections
**Rationale:** Tailscale connections use WireGuard encryption at the network layer, making the plaintext `ws://` at the application layer secure. The 100.x CGNAT addresses are only reachable through the encrypted WireGuard tunnel.
**Issue found:** The second security check in `GatewayClient.start()` (line 117 of `client.ts`) doesn't receive the `bindMode` parameter, creating an inconsistency where connection details validate successfully but the client still rejects Tailscale connections.
<h3>Confidence Score: 2/5</h3>
- This PR has a critical logic issue that prevents it from working as intended
- The implementation has the right idea and good test coverage, but there's a second security check in `GatewayClient.start()` that wasn't updated to support the Tailscale exemption. This means Tailscale connections will still fail at the client level even though they pass the initial validation. The fix is well-reasoned and the tests are thorough, but the incomplete implementation prevents it from achieving its stated goal.
- Pay close attention to `src/gateway/client.ts` - the security check on line 117 needs to be updated to match the changes in `call.ts`
<sub>Last reviewed commit: 1b9a88f</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#21772: [Bug]: Allow ws:// to Tailscale CGNAT addresses
by AIflow-Labs · 2026-02-20
85.9%
#21436: fix(gateway): plaintext ws:// blocked for Docker bind=lan (SECURITY...
by xinhuagu · 2026-02-19
80.9%
#22056: fix(gateway): use loopback for self-connections regardless of bind ...
by usedhonda · 2026-02-20
80.8%
#16300: fix(tui): respect gateway bind mode in TUI connection
by cortexuvula · 2026-02-14
79.7%
#22682: fix(gateway): [P0] status probe ignores gateway.tls.enabled — hardc...
by mahsumaktas · 2026-02-21
79.4%
#14564: fix(gateway): crashes on startup when tailscale meets non-loopback ...
by yinghaosang · 2026-02-12
79.2%
#22110: fix(tools): prefer loopback for internal tool-to-gateway RPC calls
by pierreeurope · 2026-02-20
78.8%
#20422: Fix/tailscale device pairing
by slagyr · 2026-02-18
76.9%
#21233: docs: clarify bind=lan non-loopback access requires wss or tunnel (...
by saurabhchopade · 2026-02-19
76.8%
#21741: fix(gateway): allow plaintext ws:// for Docker/private network addr...
by Joe3112 · 2026-02-20
75.9%