From 410e6db74c5b45cd9ee016aab4a9bc2fab3a1ea0 Mon Sep 17 00:00:00 2001 From: Andi Liao <31417269+liaoandi@users.noreply.github.com> Date: Sat, 11 Jul 2026 18:12:32 +0800 Subject: [PATCH] fix(status): avoid false shell-wrapper audit warnings (#81778) --- CHANGELOG.md | 1 + src/daemon/service-audit.test.ts | 36 ++++++++++++++++++++++++++++++++ src/daemon/service-audit.ts | 23 +++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fc539ededd..641c65c32a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- **Gateway service audit:** treat POSIX shell `-c` wrappers as opaque for the gateway-subcommand check, avoiding false missing-command warnings for shell-wrapped macOS LaunchAgents without parsing inner commands or ports. Fixes #81751. (#81778) Thanks @liaoandi. - **Outbound channel bootstrap:** suppress repeated failed plugin activation for the same channel, config, and registry generation while retrying after config or registry reloads. (#100377) Thanks @xialonglee. - **OpenAI Realtime client-secret deadlines:** bound voice and transcription secret acquisition to 30 seconds through the guarded fetch boundary while preserving authentication and bounded response parsing. (#102860) Thanks @Alix-007. - **Gateway client watchdog:** keep transport-stall detection active for unbounded and mixed pending requests so dead sockets reject pending requests, reconnect, and never replay rejected requests. (#103407) Thanks @NianJiuZst. diff --git a/src/daemon/service-audit.test.ts b/src/daemon/service-audit.test.ts index 1452b1d6c0b7..01a5db80c9db 100644 --- a/src/daemon/service-audit.test.ts +++ b/src/daemon/service-audit.test.ts @@ -343,6 +343,42 @@ describe("auditGatewayServiceConfig", () => { ).toBe(false); }); + it("treats zsh -lc LaunchAgent commands as opaque for the gateway token audit", async () => { + const audit = await auditGatewayServiceConfig({ + env: { HOME: "/tmp" }, + platform: "darwin", + expectedPort: 18889, + command: { + programArguments: [ + "/bin/zsh", + "-lc", + "exec /usr/bin/node /opt/openclaw/dist/index.js gateway --port 18890", + ], + environment: {}, + }, + }); + + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(false); + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayPortMismatch)).toBe(false); + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayPathMissing)).toBe(true); + }); + + it.each([ + ["non-shell command", ["/usr/local/bin/helper", "-lc", "exec node gateway"]], + ["shell without an inline-command flag", ["/bin/zsh", "-l", "exec node gateway"]], + ])("keeps exact gateway token audit for %s", async (_name, programArguments) => { + const audit = await auditGatewayServiceConfig({ + env: { HOME: "/tmp" }, + platform: "darwin", + command: { + programArguments, + environment: {}, + }, + }); + + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(true); + }); + it("flags gateway service port drift from the expected config port", async () => { const audit = await auditGatewayServiceConfig({ env: { HOME: "/tmp" }, diff --git a/src/daemon/service-audit.ts b/src/daemon/service-audit.ts index a8a8c5f23207..222fb61a4af2 100644 --- a/src/daemon/service-audit.ts +++ b/src/daemon/service-audit.ts @@ -10,6 +10,8 @@ import { sortUniqueStrings, } from "@openclaw/normalization-core/string-normalization"; import { normalizeEnvVarKey } from "../infra/host-env-security.js"; +import { resolveInlineCommandMatch } from "../infra/shell-inline-command.js"; +import { POSIX_SHELL_WRAPPERS } from "../infra/shell-wrapper-resolution.js"; import { parseTcpPort } from "../infra/tcp-port.js"; import { VERSION } from "../version.js"; import { resolveLaunchAgentPlistPath } from "./launchd.js"; @@ -87,6 +89,22 @@ function hasGatewaySubcommand(programArguments?: string[]): boolean { return Boolean(programArguments?.some((arg) => arg === "gateway")); } +const POSIX_SERVICE_INLINE_COMMAND_FLAGS = new Set(["-c"]); +const POSIX_SERVICE_SHELL_WRAPPERS: ReadonlySet = POSIX_SHELL_WRAPPERS; + +function isOpaquePosixShellInlineCommand(programArguments: string[]): boolean { + const executable = programArguments[0]?.trim(); + const shellName = executable ? path.posix.basename(executable).toLowerCase() : ""; + if (!POSIX_SERVICE_SHELL_WRAPPERS.has(shellName)) { + return false; + } + return ( + resolveInlineCommandMatch(programArguments, POSIX_SERVICE_INLINE_COMMAND_FLAGS, { + allowCombinedC: true, + }).command !== null + ); +} + function parseSystemdUnit(content: string): { after: Set; wants: Set; @@ -249,7 +267,10 @@ function auditGatewayCommand(programArguments: string[] | undefined, issues: Ser if (!programArguments || programArguments.length === 0) { return; } - if (!hasGatewaySubcommand(programArguments)) { + if ( + !hasGatewaySubcommand(programArguments) && + !isOpaquePosixShellInlineCommand(programArguments) + ) { issues.push({ code: SERVICE_AUDIT_CODES.gatewayCommandMissing, message: "Service command does not include the gateway subcommand",