High severityNVD Advisory· Published Mar 18, 2026· Updated Mar 25, 2026
OpenClaw < 2026.2.22 - Allowlist Bypass via Command Substitution in system.run
CVE-2026-22179
Description
OpenClaw versions prior to 2026.2.22 in macOS node-host system.run contain an allowlist bypass vulnerability that allows remote attackers to execute non-allowlisted commands by exploiting improper parsing of command substitution tokens. Attackers can craft shell payloads with command substitution syntax within double-quoted text to bypass security restrictions and execute arbitrary commands on the system.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.22 | 2026.2.22 |
Affected products
1Patches
190a378ca3a9efix(macos): block quoted shell substitution in allowlist checks
9 files changed · +53 −6
apps/macos/Sources/OpenClaw/ExecCommandResolution.swift+7 −5 modified@@ -194,11 +194,13 @@ struct ExecCommandResolution: Sendable { continue } + if !inSingle, self.shouldFailClosedForShell(ch: ch, next: next) { + // Fail closed on command/process substitution in allowlist mode, + // including inside double-quoted shell strings. + return nil + } + if !inSingle, !inDouble { - if self.shouldFailClosedForUnquotedShell(ch: ch, next: next) { - // Fail closed on command/process substitution in allowlist mode. - return nil - } let prev: Character? = idx > 0 ? chars[idx - 1] : nil if let delimiterStep = self.chainDelimiterStep(ch: ch, prev: prev, next: next) { guard appendCurrent() else { return nil } @@ -216,7 +218,7 @@ struct ExecCommandResolution: Sendable { return segments } - private static func shouldFailClosedForUnquotedShell(ch: Character, next: Character?) -> Bool { + private static func shouldFailClosedForShell(ch: Character, next: Character?) -> Bool { if ch == "`" { return true }
apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift+20 −0 modified@@ -80,6 +80,26 @@ struct ExecAllowlistTests { #expect(resolutions.isEmpty) } + @Test func resolveForAllowlistFailsClosedOnQuotedCommandSubstitution() { + let command = ["/bin/sh", "-lc", "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\""] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\"", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.isEmpty) + } + + @Test func resolveForAllowlistFailsClosedOnQuotedBackticks() { + let command = ["/bin/sh", "-lc", "echo \"ok `/usr/bin/id`\""] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "echo \"ok `/usr/bin/id`\"", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.isEmpty) + } + @Test func resolveForAllowlistTreatsPlainShInvocationAsDirectExec() { let command = ["/bin/sh", "./script.sh"] let resolutions = ExecCommandResolution.resolveForAllowlist(
apps/macos/Tests/OpenClawIPCTests/GatewayChannelConfigureTests.swift+4 −0 modified@@ -70,6 +70,10 @@ import Testing handler?(Result<URLSessionWebSocketTask.Message, Error>.success(.data(response))) } + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { if self.helloDelayMs > 0 { try await Task.sleep(nanoseconds: UInt64(self.helloDelayMs) * 1_000_000)
apps/macos/Tests/OpenClawIPCTests/GatewayChannelConnectTests.swift+4 −0 modified@@ -53,6 +53,10 @@ import Testing } } + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { let delayMs: Int let msg: URLSessionWebSocketTask.Message
apps/macos/Tests/OpenClawIPCTests/GatewayChannelRequestTests.swift+4 −0 modified@@ -62,6 +62,10 @@ import Testing } } + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { let id = self.connectRequestID.withLock { $0 } ?? "connect" return .data(Self.connectOkData(id: id))
apps/macos/Tests/OpenClawIPCTests/GatewayChannelShutdownTests.swift+4 −0 modified@@ -47,6 +47,10 @@ import Testing } } + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { let id = self.connectRequestID.withLock { $0 } ?? "connect" return .data(Self.connectOkData(id: id))
apps/macos/Tests/OpenClawIPCTests/GatewayConnectionControlTests.swift+4 −0 modified@@ -15,6 +15,10 @@ private final class FakeWebSocketTask: WebSocketTasking, @unchecked Sendable { func send(_: URLSessionWebSocketTask.Message) async throws {} + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { throw URLError(.cannotConnectToHost) }
apps/macos/Tests/OpenClawIPCTests/GatewayProcessManagerTests.swift+4 −0 modified@@ -64,6 +64,10 @@ struct GatewayProcessManagerTests { handler?(Result<URLSessionWebSocketTask.Message, Error>.success(.data(response))) } + func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) { + pongReceiveHandler(nil) + } + func receive() async throws -> URLSessionWebSocketTask.Message { let id = self.connectRequestID.withLock { $0 } ?? "connect" return .data(Self.connectOkData(id: id))
apps/macos/Tests/OpenClawIPCTests/MacGatewayChatTransportMappingTests.swift+2 −1 modified@@ -13,7 +13,8 @@ import Testing configpath: nil, statedir: nil, sessiondefaults: nil, - authmode: nil) + authmode: nil, + updateavailable: nil) let hello = HelloOk( type: "hello",
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
5- github.com/openclaw/openclaw/commit/90a378ca3a9ecbf1634cd247f17a35f4612c6ca6ghsapatchWEB
- github.com/advisories/GHSA-9p38-94jf-hgjjghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-9p38-94jf-hgjjghsathird-party-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-22179ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-allowlist-bypass-via-command-substitution-in-system-runghsathird-party-advisoryWEB
News mentions
0No linked articles in our index yet.