From 718d418b32199f94c91fcb27a48f01b3f8b07b87 Mon Sep 17 00:00:00 2001 From: Mark L <73659136+liuxiaopai-ai@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:13:41 +0800 Subject: [PATCH] fix(daemon): harden launchd plist with umask 077 (#31919) * fix(daemon): add launchd umask hardening * fix: finalize launchd umask changelog + thanks (#31919) (thanks @liuxiaopai-ai) --------- Co-authored-by: Peter Steinberger --- CHANGELOG.md | 1 + src/daemon/launchd-plist.ts | 3 ++- src/daemon/launchd.test.ts | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 616b5d7e315..c82d27c5e8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ Docs: https://docs.openclaw.ai - Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat. - Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3. - Gateway/macOS supervised restart: actively `launchctl kickstart -k` during intentional supervised restarts to bypass LaunchAgent `ThrottleInterval` delays, and fall back to in-process restart when kickstart fails. Landed from contributor PR #29078 by @cathrynlavery. Thanks @cathrynlavery. +- Gateway/macOS LaunchAgent hardening: write `Umask=077` in generated gateway LaunchAgent plists so npm upgrades preserve owner-only default file permissions for gateway-created state files. (#31919) Fixes #31905. Thanks @liuxiaopai-ai. - Daemon/macOS TLS certs: default LaunchAgent service env `NODE_EXTRA_CA_CERTS` to `/etc/ssl/cert.pem` (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi. - Discord/Components wildcard handlers: use distinct internal registration sentinel IDs and parse those sentinels as wildcard keys so select/user/role/channel/mentionable/modal interactions are not dropped by raw customId dedupe paths. Landed from contributor PR #29459 by @Sid-Qin. Thanks @Sid-Qin. - Feishu/Reaction notifications: add `channels.feishu.reactionNotifications` (`off | own | all`, default `own`) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529) Thanks @cowboy129. diff --git a/src/daemon/launchd-plist.ts b/src/daemon/launchd-plist.ts index 37448cdcebf..8cca6d6d8e0 100644 --- a/src/daemon/launchd-plist.ts +++ b/src/daemon/launchd-plist.ts @@ -4,6 +4,7 @@ import fs from "node:fs/promises"; // intentional gateway restarts. Keep it low so CLI restarts and forced // reinstalls do not stall for a full minute. export const LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS = 1; +export const LAUNCH_AGENT_UMASK_DECIMAL = 0o077; const plistEscape = (value: string): string => value @@ -111,5 +112,5 @@ export function buildLaunchAgentPlist({ ? `\n Comment\n ${plistEscape(comment.trim())}` : ""; const envXml = renderEnvDict(environment); - return `\n\n\n \n Label\n ${plistEscape(label)}\n ${commentXml}\n RunAtLoad\n \n KeepAlive\n \n ThrottleInterval\n ${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}\n ProgramArguments\n ${argsXml}\n \n ${workingDirXml}\n StandardOutPath\n ${plistEscape(stdoutPath)}\n StandardErrorPath\n ${plistEscape(stderrPath)}${envXml}\n \n\n`; + return `\n\n\n \n Label\n ${plistEscape(label)}\n ${commentXml}\n RunAtLoad\n \n KeepAlive\n \n ThrottleInterval\n ${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}\n Umask\n ${LAUNCH_AGENT_UMASK_DECIMAL}\n ProgramArguments\n ${argsXml}\n \n ${workingDirXml}\n StandardOutPath\n ${plistEscape(stdoutPath)}\n StandardErrorPath\n ${plistEscape(stderrPath)}${envXml}\n \n\n`; } diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index 6cf31dc5ce5..0266038d0b9 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -1,6 +1,9 @@ import { PassThrough } from "node:stream"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS } from "./launchd-plist.js"; +import { + LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS, + LAUNCH_AGENT_UMASK_DECIMAL, +} from "./launchd-plist.js"; import { installLaunchAgent, isLaunchAgentListed, @@ -201,6 +204,8 @@ describe("launchd install", () => { expect(plist).not.toContain("SuccessfulExit"); expect(plist).toContain("ThrottleInterval"); expect(plist).toContain(`${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}`); + expect(plist).toContain("Umask"); + expect(plist).toContain(`${LAUNCH_AGENT_UMASK_DECIMAL}`); }); it("restarts LaunchAgent with bootout-bootstrap-kickstart order", async () => {