Skip to content

Commit d33f24c

Browse files
ClawbornClawborn
authored andcommitted
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 <[email protected]>
1 parent 7bbfb9d commit d33f24c

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

src/daemon/service-env.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,24 @@ describe("buildServiceEnvironment", () => {
328328
expect(env.NO_PROXY).toBe("localhost,127.0.0.1");
329329
expect(env.http_proxy).toBe("http://proxy.local:7890");
330330
expect(env.all_proxy).toBe("socks5://proxy.local:1080");
331+
it("defaults NODE_EXTRA_CA_CERTS to system cert bundle on macOS", () => {
332+
const env = buildServiceEnvironment({
333+
env: { HOME: "/home/user" },
334+
port: 18789,
335+
});
336+
if (process.platform === "darwin") {
337+
expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/cert.pem");
338+
} else {
339+
expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined();
340+
}
341+
});
342+
343+
it("respects user-provided NODE_EXTRA_CA_CERTS over the default", () => {
344+
const env = buildServiceEnvironment({
345+
env: { HOME: "/home/user", NODE_EXTRA_CA_CERTS: "/custom/certs/ca.pem" },
346+
port: 18789,
347+
});
348+
expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/certs/ca.pem");
331349
});
332350
});
333351

@@ -365,6 +383,24 @@ describe("buildNodeServiceEnvironment", () => {
365383
});
366384
expect(env.TMPDIR).toBe(os.tmpdir());
367385
});
386+
387+
it("defaults NODE_EXTRA_CA_CERTS to system cert bundle on macOS for node services", () => {
388+
const env = buildNodeServiceEnvironment({
389+
env: { HOME: "/home/user" },
390+
});
391+
if (process.platform === "darwin") {
392+
expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/cert.pem");
393+
} else {
394+
expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined();
395+
}
396+
});
397+
398+
it("respects user-provided NODE_EXTRA_CA_CERTS for node services", () => {
399+
const env = buildNodeServiceEnvironment({
400+
env: { HOME: "/home/user", NODE_EXTRA_CA_CERTS: "/custom/certs/ca.pem" },
401+
});
402+
expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/certs/ca.pem");
403+
});
368404
});
369405

370406
describe("resolveGatewayStateDir", () => {

src/daemon/service-env.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,17 @@ export function buildServiceEnvironment(params: {
248248
// Keep a usable temp directory for supervised services even when the host env omits TMPDIR.
249249
const tmpDir = env.TMPDIR?.trim() || os.tmpdir();
250250
const proxyEnv = readServiceProxyEnvironment(env);
251+
// On macOS, launchd services don't inherit the shell environment, so Node's undici/fetch
252+
// cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification
253+
// works correctly when running as a LaunchAgent without extra user configuration.
254+
const nodeCaCerts =
255+
env.NODE_EXTRA_CA_CERTS ?? (process.platform === "darwin" ? "/etc/ssl/cert.pem" : undefined);
251256
return {
252257
HOME: env.HOME,
253258
TMPDIR: tmpDir,
254259
PATH: buildMinimalServicePath({ env }),
255260
...proxyEnv,
261+
NODE_EXTRA_CA_CERTS: nodeCaCerts,
256262
OPENCLAW_PROFILE: profile,
257263
OPENCLAW_STATE_DIR: stateDir,
258264
OPENCLAW_CONFIG_PATH: configPath,
@@ -274,11 +280,17 @@ export function buildNodeServiceEnvironment(params: {
274280
const configPath = env.OPENCLAW_CONFIG_PATH;
275281
const tmpDir = env.TMPDIR?.trim() || os.tmpdir();
276282
const proxyEnv = readServiceProxyEnvironment(env);
283+
// On macOS, launchd services don't inherit the shell environment, so Node's undici/fetch
284+
// cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification
285+
// works correctly when running as a LaunchAgent without extra user configuration.
286+
const nodeCaCerts =
287+
env.NODE_EXTRA_CA_CERTS ?? (process.platform === "darwin" ? "/etc/ssl/cert.pem" : undefined);
277288
return {
278289
HOME: env.HOME,
279290
TMPDIR: tmpDir,
280291
PATH: buildMinimalServicePath({ env }),
281292
...proxyEnv,
293+
NODE_EXTRA_CA_CERTS: nodeCaCerts,
282294
OPENCLAW_STATE_DIR: stateDir,
283295
OPENCLAW_CONFIG_PATH: configPath,
284296
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),

0 commit comments

Comments
 (0)