mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix: filter launchd handoff environment
This commit is contained in:
@@ -92,6 +92,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Configure/Ollama: show the configured Ollama model allowlist after Cloud only or Cloud + Local setup and skip slow per-model cloud metadata fetches. (#73995) Thanks @obviyus.
|
||||
- Channels/WhatsApp: detect explicit group `@mentions` again when the bot's own E.164 is in `allowFrom`, so shared-number setups no longer skip group pings that directly mention the bot. Fixes #49317. (#73453) Thanks @juan-flores077.
|
||||
- WhatsApp/reliability: publish real transport-liveness into WhatsApp channel status and force earlier reconnects on silent transport stalls, so quiet healthy sessions stay connected while wedged sockets recover before the later remote 408 path. (#72656) Thanks @Sathvik-1007.
|
||||
- Core/channels: tighten selected runtime, media, and plugin edge-case handling while preserving existing behavior. Thanks @jesse-merhi.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
|
||||
@@ -72,6 +72,35 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
|
||||
expect(args[1]).not.toContain('basename "$service_target"');
|
||||
});
|
||||
|
||||
it("sanitizes restart helper environment overrides before spawning", () => {
|
||||
spawnMock.mockReturnValue({ pid: 4242, unref: unrefMock });
|
||||
|
||||
scheduleDetachedLaunchdRestartHandoff({
|
||||
env: {
|
||||
HOME: "/Users/test",
|
||||
OPENCLAW_PROFILE: "default",
|
||||
PATH: "/tmp/evil-bin",
|
||||
DYLD_INSERT_LIBRARIES: "/tmp/evil.dylib",
|
||||
NPM_CONFIG_GLOBALCONFIG: "/tmp/evil-npmrc",
|
||||
},
|
||||
mode: "kickstart",
|
||||
});
|
||||
|
||||
const [, args, options] = spawnMock.mock.calls[0] as [
|
||||
string,
|
||||
string[],
|
||||
{ env: Record<string, string | undefined> },
|
||||
];
|
||||
expect(args[1]).toContain("exec >>'/Users/test/.openclaw/logs/gateway-restart.log' 2>&1");
|
||||
expect(args[1]).not.toContain("/tmp/evil-bin");
|
||||
expect(args[1]).not.toContain("/tmp/evil.dylib");
|
||||
expect(args[1]).not.toContain("/tmp/evil-npmrc");
|
||||
expect(options.env.OPENCLAW_PROFILE).toBe("default");
|
||||
expect(options.env.PATH).not.toBe("/tmp/evil-bin");
|
||||
expect(options.env.DYLD_INSERT_LIBRARIES).toBeUndefined();
|
||||
expect(options.env.NPM_CONFIG_GLOBALCONFIG).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects invalid launchd labels before spawning the helper", () => {
|
||||
expect(() => {
|
||||
scheduleDetachedLaunchdRestartHandoff({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { sanitizeHostExecEnv } from "../infra/host-env-security.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { sanitizeForLog } from "../terminal/ansi.js";
|
||||
import { resolveGatewayLaunchAgentLabel } from "./constants.js";
|
||||
@@ -25,6 +26,13 @@ export type LaunchdRestartTarget = {
|
||||
const START_AFTER_EXIT_PRINT_RETRY_COUNT = 15;
|
||||
const START_AFTER_EXIT_PRINT_RETRY_DELAY_SECONDS = 0.2;
|
||||
|
||||
type LaunchdRestartLogEnv = {
|
||||
HOME?: string;
|
||||
USERPROFILE?: string;
|
||||
OPENCLAW_STATE_DIR?: string;
|
||||
OPENCLAW_PROFILE?: string;
|
||||
};
|
||||
|
||||
function assertValidLaunchAgentLabel(label: string): string {
|
||||
const trimmed = label.trim();
|
||||
if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) {
|
||||
@@ -40,6 +48,27 @@ function resolveGuiDomain(): string {
|
||||
return `gui/${process.getuid()}`;
|
||||
}
|
||||
|
||||
function collectStringEnvOverrides(
|
||||
env?: Record<string, string | undefined>,
|
||||
): Record<string, string> | undefined {
|
||||
const overrides = Object.fromEntries(
|
||||
Object.entries(env ?? {}).filter(
|
||||
(entry): entry is [string, string] => typeof entry[1] === "string",
|
||||
),
|
||||
);
|
||||
return Object.keys(overrides).length > 0 ? overrides : undefined;
|
||||
}
|
||||
|
||||
function collectRestartLogEnv(env?: Record<string, string | undefined>): LaunchdRestartLogEnv {
|
||||
const source = { ...process.env, ...env };
|
||||
return {
|
||||
HOME: source.HOME,
|
||||
USERPROFILE: source.USERPROFILE,
|
||||
OPENCLAW_STATE_DIR: source.OPENCLAW_STATE_DIR,
|
||||
OPENCLAW_PROFILE: source.OPENCLAW_PROFILE,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveLaunchAgentLabel(env?: Record<string, string | undefined>): string {
|
||||
const envLabel = normalizeOptionalString(env?.OPENCLAW_LAUNCHD_LABEL);
|
||||
if (envLabel) {
|
||||
@@ -80,11 +109,11 @@ export function isCurrentProcessLaunchdServiceLabel(
|
||||
|
||||
function buildLaunchdRestartScript(
|
||||
mode: LaunchdRestartHandoffMode,
|
||||
env: Record<string, string | undefined>,
|
||||
restartLogEnv: LaunchdRestartLogEnv,
|
||||
): string {
|
||||
const waitForCallerPid = `wait_pid="$4"
|
||||
label="$5"
|
||||
${renderPosixRestartLogSetup(env)}
|
||||
${renderPosixRestartLogSetup(restartLogEnv)}
|
||||
printf '[%s] openclaw restart attempt source=launchd-handoff mode=${mode} target=%s waitPid=%s\\n' "$(date -u +%FT%TZ)" "$service_target" "$wait_pid" >&2
|
||||
if [ -n "$wait_pid" ] && [ "$wait_pid" -gt 1 ] 2>/dev/null; then
|
||||
while kill -0 "$wait_pid" >/dev/null 2>&1; do
|
||||
@@ -169,13 +198,17 @@ export function scheduleDetachedLaunchdRestartHandoff(params: {
|
||||
typeof params.waitForPid === "number" && Number.isFinite(params.waitForPid)
|
||||
? Math.floor(params.waitForPid)
|
||||
: 0;
|
||||
const restartEnv = { ...process.env, ...params.env };
|
||||
const restartLogEnv = collectRestartLogEnv(params.env);
|
||||
const restartEnv = sanitizeHostExecEnv({
|
||||
baseEnv: process.env,
|
||||
overrides: collectStringEnvOverrides(params.env),
|
||||
});
|
||||
try {
|
||||
const child = spawn(
|
||||
"/bin/sh",
|
||||
[
|
||||
"-c",
|
||||
buildLaunchdRestartScript(params.mode, restartEnv),
|
||||
buildLaunchdRestartScript(params.mode, restartLogEnv),
|
||||
"openclaw-launchd-restart-handoff",
|
||||
target.serviceTarget,
|
||||
target.domain,
|
||||
|
||||
Reference in New Issue
Block a user