From d33f24c4e9f7330901479061aedc2638c501d418 Mon Sep 17 00:00:00 2001 From: Clawborn Date: Sun, 22 Feb 2026 04:42:20 +0800 Subject: [PATCH] Fix NODE_EXTRA_CA_CERTS missing from LaunchAgent environment on macOS launchd services do not inherit the shell environment, so Node's undici/fetch cannot locate the macOS system CA bundle (/etc/ssl/cert.pem). This causes TLS verification failures for all HTTPS requests (e.g. Telegram, webhooks) when the gateway runs as a LaunchAgent, while the same gateway works fine in a terminal. Add NODE_EXTRA_CA_CERTS defaulting to /etc/ssl/cert.pem on macOS in both buildServiceEnvironment and buildNodeServiceEnvironment. User-supplied NODE_EXTRA_CA_CERTS is always respected and takes precedence. Fixes #22856 Co-authored-by: Clawborn --- src/daemon/service-env.test.ts | 36 ++++++++++++++++++++++++++++++++++ src/daemon/service-env.ts | 12 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index 2cfa4cce1de..6dc20971ff0 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -328,6 +328,24 @@ describe("buildServiceEnvironment", () => { expect(env.NO_PROXY).toBe("localhost,127.0.0.1"); expect(env.http_proxy).toBe("http://proxy.local:7890"); expect(env.all_proxy).toBe("socks5://proxy.local:1080"); + it("defaults NODE_EXTRA_CA_CERTS to system cert bundle on macOS", () => { + const env = buildServiceEnvironment({ + env: { HOME: "/home/user" }, + port: 18789, + }); + if (process.platform === "darwin") { + expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/cert.pem"); + } else { + expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined(); + } + }); + + it("respects user-provided NODE_EXTRA_CA_CERTS over the default", () => { + const env = buildServiceEnvironment({ + env: { HOME: "/home/user", NODE_EXTRA_CA_CERTS: "/custom/certs/ca.pem" }, + port: 18789, + }); + expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/certs/ca.pem"); }); }); @@ -365,6 +383,24 @@ describe("buildNodeServiceEnvironment", () => { }); expect(env.TMPDIR).toBe(os.tmpdir()); }); + + it("defaults NODE_EXTRA_CA_CERTS to system cert bundle on macOS for node services", () => { + const env = buildNodeServiceEnvironment({ + env: { HOME: "/home/user" }, + }); + if (process.platform === "darwin") { + expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/cert.pem"); + } else { + expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined(); + } + }); + + it("respects user-provided NODE_EXTRA_CA_CERTS for node services", () => { + const env = buildNodeServiceEnvironment({ + env: { HOME: "/home/user", NODE_EXTRA_CA_CERTS: "/custom/certs/ca.pem" }, + }); + expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/certs/ca.pem"); + }); }); describe("resolveGatewayStateDir", () => { diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 458ca515c1d..0d15120b683 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -248,11 +248,17 @@ export function buildServiceEnvironment(params: { // Keep a usable temp directory for supervised services even when the host env omits TMPDIR. const tmpDir = env.TMPDIR?.trim() || os.tmpdir(); const proxyEnv = readServiceProxyEnvironment(env); + // On macOS, launchd services don't inherit the shell environment, so Node's undici/fetch + // cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification + // works correctly when running as a LaunchAgent without extra user configuration. + const nodeCaCerts = + env.NODE_EXTRA_CA_CERTS ?? (process.platform === "darwin" ? "/etc/ssl/cert.pem" : undefined); return { HOME: env.HOME, TMPDIR: tmpDir, PATH: buildMinimalServicePath({ env }), ...proxyEnv, + NODE_EXTRA_CA_CERTS: nodeCaCerts, OPENCLAW_PROFILE: profile, OPENCLAW_STATE_DIR: stateDir, OPENCLAW_CONFIG_PATH: configPath, @@ -274,11 +280,17 @@ export function buildNodeServiceEnvironment(params: { const configPath = env.OPENCLAW_CONFIG_PATH; const tmpDir = env.TMPDIR?.trim() || os.tmpdir(); const proxyEnv = readServiceProxyEnvironment(env); + // On macOS, launchd services don't inherit the shell environment, so Node's undici/fetch + // cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification + // works correctly when running as a LaunchAgent without extra user configuration. + const nodeCaCerts = + env.NODE_EXTRA_CA_CERTS ?? (process.platform === "darwin" ? "/etc/ssl/cert.pem" : undefined); return { HOME: env.HOME, TMPDIR: tmpDir, PATH: buildMinimalServicePath({ env }), ...proxyEnv, + NODE_EXTRA_CA_CERTS: nodeCaCerts, OPENCLAW_STATE_DIR: stateDir, OPENCLAW_CONFIG_PATH: configPath, OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),