#22682: fix(gateway): [P0] status probe ignores gateway.tls.enabled — hardcoded ws:// + self-signed cert rejection
gateway
cli
commands
size: XS
Cluster:
Gateway and TLS Enhancements
## Severity: P0 — Breaks core CLI functionality for all TLS users
`openclaw gateway status` **always** reports `RPC probe: failed` when `gateway.tls.enabled: true`. This affects every user with TLS enabled (including the default self-signed cert setup). The gateway itself runs fine — only the CLI status/probe is broken, which is confusing and makes users think the gateway is down.
## Root Cause
Two independent bugs that combine to make TLS status probing completely non-functional:
### Bug 1: Probe URL construction ignores `gateway.tls.enabled`
Six code sites construct WebSocket URLs with hardcoded `ws://` instead of checking the TLS config:
| File | Function/Location | What's hardcoded |
|------|-------------------|-----------------|
| `src/cli/daemon-cli/status.gather.ts` | `probeUrl` construction | `` `ws://${probeHost}:${daemonPort}` `` |
| `src/commands/gateway-status/helpers.ts` | `resolveTargets()` | `` `ws://127.0.0.1:${port}` `` |
| `src/commands/gateway-status/helpers.ts` | `buildNetworkHints()` | `localLoopbackUrl`, `localTailnetUrl` |
| `src/commands/gateway-status.ts` | SSH tunnel URL | `` `ws://127.0.0.1:${tunnel.localPort}` `` |
| `src/commands/gateway-status.ts` | Discovery beacon `wsUrl` | `` `ws://${host}:${port}` `` |
| `src/cli/gateway-cli/register.ts` | `discover --json` wsUrl | `` `ws://${host}:${port}` `` |
The correct pattern already exists in `src/gateway/call.ts` (`buildGatewayConnectionDetails`):
```typescript
const tlsEnabled = config.gateway?.tls?.enabled === true;
const scheme = tlsEnabled ? "wss" : "ws";
```
These six sites simply never got the same treatment.
**Effect:** When TLS is enabled, probing `ws://` against a `wss://` listener either triggers a `SECURITY ERROR` (blocked by `isSecureWebSocketUrl()`) or fails to connect.
### Bug 2: `GatewayClient` rejects self-signed certs without fingerprint
In `src/gateway/client.ts`, `rejectUnauthorized: false` is only set when `tlsFingerprint` is provided:
```typescript
// Before: only accepts self-signed certs WITH fingerprint pinning
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
wsOptions.rejectUnauthorized = false;
// ... fingerprint verification
}
// No else → Node.js default rejectUnauthorized=true → self-signed cert rejected
```
Since the gateway auto-generates self-signed certs by default and `tlsFingerprint` is optional (and most users don't configure it), the WebSocket handshake fails with `1006 abnormal closure` for every default TLS installation.
## Fix
**Bug 1** — Read `gateway.tls.enabled` to select `ws://` vs `wss://` at each site:
```typescript
const scheme = cfg.gateway?.tls?.enabled === true ? "wss" : "ws";
```
**Bug 2** — Accept self-signed certs when no fingerprint is configured:
```typescript
} else if (url.startsWith("wss://")) {
wsOptions.rejectUnauthorized = false;
}
```
## Changes
| File | Lines | Description |
|------|-------|-------------|
| `src/cli/daemon-cli/status.gather.ts` | +2 −1 | TLS-aware `probeUrl` |
| `src/commands/gateway-status/helpers.ts` | +5 −3 | TLS-aware `resolveTargets()` + `buildNetworkHints()` |
| `src/commands/gateway-status.ts` | +4 −2 | TLS-aware SSH tunnel + discovery beacon URLs |
| `src/cli/gateway-cli/register.ts` | +2 −1 | TLS-aware `discover --json` output |
| `src/gateway/client.ts` | +6 −0 | Accept self-signed certs without fingerprint |
| **Total** | **+19 −7** | |
## Verified
Tested locally with `gateway.tls.enabled: true`, `gateway.bind: "lan"`, no `tlsFingerprint`:
**Before:**
```
Probe target: ws://192.168.1.39:28643
RPC probe: failed
SECURITY ERROR: Gateway URL "ws://192.168.1.39:28643" uses plaintext ws://
```
**After:**
```
Probe target: wss://192.168.1.39:28643
RPC probe: ok
```
`openclaw gateway health` also confirms the gateway is healthy.
## Related
- Fixes #22678
- Supersedes #14277 (fixes only `status.gather.ts`, stale since Feb 11)
- Supersedes #21842 (fixes only `register.ts` discover JSON path)
- This PR covers all six affected sites + the self-signed cert issue that neither PR addresses
## Non-breaking
- When `gateway.tls.enabled` is `false` or unset, all URLs remain `ws://` — no behavior change for non-TLS users
- The `rejectUnauthorized: false` fallback only applies to `wss://` connections without fingerprint pinning; fingerprint verification is unaffected
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a critical P0 bug where `openclaw gateway status` always fails when TLS is enabled. The fix correctly addresses the root cause by reading `gateway.tls.enabled` to construct `wss://` URLs instead of hardcoded `ws://` URLs across 6 code sites.
**What changed:**
- TLS-aware URL scheme selection (`wss://` vs `ws://`) in status probe, gateway discovery, and network hints
- Self-signed certificate acceptance for `wss://` connections without fingerprint pinning
**Critical security issue found:**
The `rejectUnauthorized: false` change in `src/gateway/client.ts:161-166` introduces a MITM vulnerability for non-loopback connections. When connecting over LAN or tailnet without fingerprint pinning, any attacker on the network can intercept credentials. The fix should scope `rejectUnauthorized: false` to loopback addresses only (using `isLoopbackHostname()`), requiring valid certs or fingerprint pinning for network connections.
**Other observations:**
- The `register.ts` change correctly prioritizes the beacon's `gatewayTls` field over local config, which is appropriate for discovery
- All TLS scheme logic follows the established pattern from `buildGatewayConnectionDetails()`
<h3>Confidence Score: 2/5</h3>
- This PR has a critical security vulnerability that must be addressed before merging
- Score reflects the MITM vulnerability introduced by disabling certificate validation for all non-loopback `wss://` connections. While the URL scheme fixes are correct and necessary, the security regression in `client.ts` creates a vulnerability that could expose user credentials to network attackers. The issue is fixable by scoping `rejectUnauthorized: false` to loopback addresses only.
- `src/gateway/client.ts` requires immediate attention to fix the MITM vulnerability before this can be safely merged
<sub>Last reviewed commit: 4029a97</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22716: fix: gateway status probe uses wss:// when TLS enabled; accept self...
by Fratua · 2026-02-21
89.5%
#21842: fix(gateway-cli): use wss:// scheme when gatewayTls is enabled
by hydro13 · 2026-02-20
84.8%
#14277: fix(CLI): Use wss for probeUrl, when gateway has tls enabled
by tha80 · 2026-02-11
83.3%
#22056: fix(gateway): use loopback for self-connections regardless of bind ...
by usedhonda · 2026-02-20
80.5%
#7654: feat(security): zero-trust localhost auth with DNS rebinding protec...
by joncode · 2026-02-03
80.1%
#22453: fix(tui): resolve and pass tlsFingerprint for secure connections [A...
by captmoss · 2026-02-21
79.9%
#22110: fix(tools): prefer loopback for internal tool-to-gateway RPC calls
by pierreeurope · 2026-02-20
79.7%
#23735: Gateway: add first-class wss validation and remote TLS guidance
by bmendonca3 · 2026-02-22
79.7%
#11455: fix(gateway): default gateway.mode to local when unset
by AnonO6 · 2026-02-07
79.5%
#21256: fix: treat ws:// to Tailscale addresses as secure when bind=tailnet
by jessewunderlich · 2026-02-19
79.4%