OpenClaw < 2026.2.21 - Insecure Control UI Authentication over Plaintext HTTP
Description
OpenClaw versions prior to 2026.2.21 contain an authentication bypass vulnerability in the Control UI when allowInsecureAuth is explicitly enabled and the gateway is exposed over plaintext HTTP, allowing attackers to bypass device identity and pairing verification. An attacker with leaked or intercepted credentials can obtain high-privilege Control UI access by exploiting the lack of secure authentication enforcement over unencrypted HTTP connections.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.21 | 2026.2.21 |
Affected products
1Patches
140a292619e1ffix: Control UI Insecure Auth Bypass Allows Token-Only Auth Over HTTP (#20684)
4 files changed · +32 −7
CHANGELOG.md+1 −0 modified@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Gateway/Security: require secure context and paired-device checks for Control UI auth even when `gateway.controlUi.allowInsecureAuth` is set, and align audit messaging with the hardened behavior. (#20684) thanks @coygeek. - macOS/Build: default release packaging to `BUNDLE_ID=ai.openclaw.mac` in `scripts/package-mac-dist.sh`, so Sparkle feed URL is retained and auto-update no longer fails with an empty appcast feed. (#19750) thanks @loganprit. - Signal/Outbound: preserve case for Base64 group IDs during outbound target normalization so cross-context routing and policy checks no longer break when group IDs include uppercase characters. (#5578) Thanks @heyhudson.
src/gateway/server.auth.e2e.test.ts+24 −4 modified@@ -687,7 +687,7 @@ describe("gateway server auth/connect", () => { }); }); - test("allows control ui without device identity when insecure auth is enabled", async () => { + test("rejects control ui without device identity even when insecure auth is enabled", async () => { testState.gatewayControlUi = { allowInsecureAuth: true }; const { server, ws, prevToken } = await startServerWithClient("secret", { wsHeaders: { origin: "http://127.0.0.1" }, @@ -702,13 +702,32 @@ describe("gateway server auth/connect", () => { mode: GATEWAY_CLIENT_MODES.WEBCHAT, }, }); - expect(res.ok).toBe(true); + expect(res.ok).toBe(false); + expect(res.error?.message ?? "").toContain("secure context"); ws.close(); await server.close(); restoreGatewayToken(prevToken); }); - test("allows control ui with device identity when insecure auth is enabled", async () => { + test("rejects control ui password-only auth when insecure auth is enabled", async () => { + testState.gatewayControlUi = { allowInsecureAuth: true }; + testState.gatewayAuth = { mode: "password", password: "secret" }; + await withGatewayServer(async ({ port }) => { + const ws = await openWs(port, { origin: originForPort(port) }); + const res = await connectReq(ws, { + password: "secret", + device: null, + client: { + ...CONTROL_UI_CLIENT, + }, + }); + expect(res.ok).toBe(false); + expect(res.error?.message ?? "").toContain("secure context"); + ws.close(); + }); + }); + + test("does not bypass pairing for control ui device identity when insecure auth is enabled", async () => { testState.gatewayControlUi = { allowInsecureAuth: true }; testState.gatewayAuth = { mode: "token", token: "secret" }; const { writeConfigFile } = await import("../config/config.js"); @@ -753,7 +772,8 @@ describe("gateway server auth/connect", () => { ...CONTROL_UI_CLIENT, }, }); - expect(res.ok).toBe(true); + expect(res.ok).toBe(false); + expect(res.error?.message ?? "").toContain("pairing required"); ws.close(); }); } finally {
src/gateway/server/ws-connection/message-handler.ts+6 −2 modified@@ -341,7 +341,9 @@ export function attachGatewayWsMessageHandler(params: { isControlUi && configSnapshot.gateway?.controlUi?.allowInsecureAuth === true; const disableControlUiDeviceAuth = isControlUi && configSnapshot.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true; - const allowControlUiBypass = allowInsecureControlUi || disableControlUiDeviceAuth; + // `allowInsecureAuth` is retained for compatibility, but must not bypass + // secure-context/device-auth requirements. + const allowControlUiBypass = disableControlUiDeviceAuth; const device = disableControlUiDeviceAuth ? null : deviceRaw; const hasDeviceTokenCandidate = Boolean(connectParams.auth?.token && device); @@ -428,7 +430,9 @@ export function attachGatewayWsMessageHandler(params: { if (isControlUi && !allowControlUiBypass) { const errorMessage = "control ui requires HTTPS or localhost (secure context)"; - markHandshakeFailure("control-ui-insecure-auth"); + markHandshakeFailure("control-ui-insecure-auth", { + insecureAuthConfigured: allowInsecureControlUi, + }); sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, errorMessage); close(1008, errorMessage); return;
src/security/audit.ts+1 −1 modified@@ -351,7 +351,7 @@ function collectGatewayConfigFindings( severity: "critical", title: "Control UI allows insecure HTTP auth", detail: - "gateway.controlUi.allowInsecureAuth=true allows token-only auth over HTTP and skips device identity.", + "gateway.controlUi.allowInsecureAuth=true is a legacy insecure-auth toggle; Control UI still enforces secure context and device identity unless dangerouslyDisableDeviceAuth is enabled.", remediation: "Disable it or switch to HTTPS (Tailscale Serve) or localhost.", }); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/openclaw/openclaw/commit/40a292619e1f2be3a3b1db663d7494c9c2dc0abfghsapatchWEB
- github.com/advisories/GHSA-3cvx-236h-m9fjghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-3cvx-236h-m9fjghsathird-party-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-32034ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-insecure-control-ui-authentication-over-plaintext-httpghsathird-party-advisoryWEB
- github.com/openclaw/openclaw/pull/20684ghsaWEB
News mentions
0No linked articles in our index yet.