Skip to content

Commit aecb9a1

Browse files
liaoandivincentkoc
authored andcommitted
fix(status): avoid false shell-wrapper audit warnings
1 parent 8e2ddd6 commit aecb9a1

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
2828

2929
### Fixes
3030

31+
- **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.
3132
- **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.
3233
- **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.
3334
- **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.

src/daemon/service-audit.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,42 @@ describe("auditGatewayServiceConfig", () => {
343343
).toBe(false);
344344
});
345345

346+
it("treats zsh -lc LaunchAgent commands as opaque for the gateway token audit", async () => {
347+
const audit = await auditGatewayServiceConfig({
348+
env: { HOME: "/tmp" },
349+
platform: "darwin",
350+
expectedPort: 18889,
351+
command: {
352+
programArguments: [
353+
"/bin/zsh",
354+
"-lc",
355+
"exec /usr/bin/node /opt/openclaw/dist/index.js gateway --port 18890",
356+
],
357+
environment: {},
358+
},
359+
});
360+
361+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(false);
362+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayPortMismatch)).toBe(false);
363+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayPathMissing)).toBe(true);
364+
});
365+
366+
it.each([
367+
["non-shell command", ["/usr/local/bin/helper", "-lc", "exec node gateway"]],
368+
["shell without an inline-command flag", ["/bin/zsh", "-l", "exec node gateway"]],
369+
])("keeps exact gateway token audit for %s", async (_name, programArguments) => {
370+
const audit = await auditGatewayServiceConfig({
371+
env: { HOME: "/tmp" },
372+
platform: "darwin",
373+
command: {
374+
programArguments,
375+
environment: {},
376+
},
377+
});
378+
379+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(true);
380+
});
381+
346382
it("flags gateway service port drift from the expected config port", async () => {
347383
const audit = await auditGatewayServiceConfig({
348384
env: { HOME: "/tmp" },

src/daemon/service-audit.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
sortUniqueStrings,
1111
} from "@openclaw/normalization-core/string-normalization";
1212
import { normalizeEnvVarKey } from "../infra/host-env-security.js";
13+
import { resolveInlineCommandMatch } from "../infra/shell-inline-command.js";
14+
import { POSIX_SHELL_WRAPPERS } from "../infra/shell-wrapper-resolution.js";
1315
import { parseTcpPort } from "../infra/tcp-port.js";
1416
import { VERSION } from "../version.js";
1517
import { resolveLaunchAgentPlistPath } from "./launchd.js";
@@ -87,6 +89,22 @@ function hasGatewaySubcommand(programArguments?: string[]): boolean {
8789
return Boolean(programArguments?.some((arg) => arg === "gateway"));
8890
}
8991

92+
const POSIX_SERVICE_INLINE_COMMAND_FLAGS = new Set(["-c"]);
93+
const POSIX_SERVICE_SHELL_WRAPPERS: ReadonlySet<string> = POSIX_SHELL_WRAPPERS;
94+
95+
function isOpaquePosixShellInlineCommand(programArguments: string[]): boolean {
96+
const executable = programArguments[0]?.trim();
97+
const shellName = executable ? path.posix.basename(executable).toLowerCase() : "";
98+
if (!POSIX_SERVICE_SHELL_WRAPPERS.has(shellName)) {
99+
return false;
100+
}
101+
return (
102+
resolveInlineCommandMatch(programArguments, POSIX_SERVICE_INLINE_COMMAND_FLAGS, {
103+
allowCombinedC: true,
104+
}).command !== null
105+
);
106+
}
107+
90108
function parseSystemdUnit(content: string): {
91109
after: Set<string>;
92110
wants: Set<string>;
@@ -249,7 +267,10 @@ function auditGatewayCommand(programArguments: string[] | undefined, issues: Ser
249267
if (!programArguments || programArguments.length === 0) {
250268
return;
251269
}
252-
if (!hasGatewaySubcommand(programArguments)) {
270+
if (
271+
!hasGatewaySubcommand(programArguments) &&
272+
!isOpaquePosixShellInlineCommand(programArguments)
273+
) {
253274
issues.push({
254275
code: SERVICE_AUDIT_CODES.gatewayCommandMissing,
255276
message: "Service command does not include the gateway subcommand",

0 commit comments

Comments
 (0)