diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f0b3ad188..33fe7c58fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Docs: https://docs.openclaw.ai - Feishu/Run channel fallback: prefer `Provider` over `Surface` when inferring queued run `messageProvider` fallback (when `OriginatingChannel` is missing), preventing Feishu turns from being mislabeled as `webchat` in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai. - Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai. - Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin. +- macOS/LaunchAgent security defaults: write `Umask=63` (octal `077`) into generated gateway launchd plists so post-update service reinstalls keep owner-only file permissions by default instead of falling back to system `022`. (#32022) Fixes #31905. Thanks @liuxiaopai-ai. - Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example `env sh -c ...`) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting. - Sandbox/Bootstrap context boundary hardening: reject symlink/hardlink alias bootstrap seed files that resolve outside the source workspace and switch post-compaction `AGENTS.md` context reads to boundary-verified file opens, preventing host file content from being injected via workspace aliasing. Thanks @tdjackey for reporting. - Browser/Security output boundary hardening: replace check-then-rename output commits with root-bound fd-verified writes, unify install/skills canonical path-boundary checks, and add regression coverage for symlink-rebind race paths across browser output and shared fs-safe write flows. Thanks @tdjackey for reporting. diff --git a/src/daemon/launchd-plist.ts b/src/daemon/launchd-plist.ts index 8cca6d6d8e0..fa2a780a5c8 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; +// launchd stores plist integer values in decimal; 0o077 renders as 63 (owner-only files). export const LAUNCH_AGENT_UMASK_DECIMAL = 0o077; const plistEscape = (value: string): string => diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index 0266038d0b9..ca94f8b5602 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -189,7 +189,7 @@ describe("launchd install", () => { expect(plist).toContain(`${tmpDir}`); }); - it("writes KeepAlive=true policy", async () => { + it("writes KeepAlive=true policy with restrictive umask", async () => { const env = createDefaultLaunchdEnv(); await installLaunchAgent({ env, @@ -202,10 +202,10 @@ describe("launchd install", () => { expect(plist).toContain("KeepAlive"); expect(plist).toContain(""); 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}`); + expect(plist).toContain("ThrottleInterval"); + expect(plist).toContain(`${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}`); }); it("restarts LaunchAgent with bootout-bootstrap-kickstart order", async () => {