diff --git a/apps/macos/Sources/OpenClaw/ExecApprovalsStore.swift b/apps/macos/Sources/OpenClaw/ExecApprovalsStore.swift index 2e50f009d7ed..100fc41b7800 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovalsStore.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovalsStore.swift @@ -1199,7 +1199,14 @@ extension ExecApprovalsStore { private static func ensureSecureStateDirectory() throws { let url = self.stateDirURL() - try FileManager().createDirectory(at: url, withIntermediateDirectories: true) + // Create with the final 0700 mode directly: a default-mode (0755) + // create followed by the chmod below leaves a transient window where + // the directory is world-listable and concurrent observers see the + // wrong permissions. + try FileManager().createDirectory( + at: url, + withIntermediateDirectories: true, + attributes: [.posixPermissions: self.secureStateDirPermissions]) try ExecApprovalsFileIO.assertSafeDirectory(at: url) try FileManager().setAttributes( [.posixPermissions: self.secureStateDirPermissions], diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketPathGuardTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketPathGuardTests.swift index 73d0b65e9189..47a7c53efec8 100644 --- a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketPathGuardTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketPathGuardTests.swift @@ -159,22 +159,38 @@ struct ExecApprovalsSocketPathGuardTests { } @Test - func `harden canonical parent directory creates it with0700 permissions`() async throws { + func `socket path resolves under the configured state directory`() async throws { let stateDir = FileManager().temporaryDirectory .appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true) defer { try? FileManager().removeItem(at: stateDir) } + // String-level assertion only. The isolation lock serializes env + // mutation but not env consumption: concurrent suites that resolve + // OPENCLAW_STATE_DIR mid-window would create this directory and the + // old filesystem assertions here flaked on their 0755 default + // (#104019). Creation-with-0700 is covered by the explicit-path + // tests in this suite. try await TestIsolation.withEnvValues(["OPENCLAW_STATE_DIR": stateDir.path]) { let socketPath = ExecApprovalsStore.socketPath() - try ExecApprovalsSocketPathGuard.hardenParentDirectory(for: socketPath) - - #expect(FileManager().fileExists(atPath: stateDir.path)) - let attrs = try FileManager().attributesOfItem(atPath: stateDir.path) - let permissions = (attrs[.posixPermissions] as? NSNumber)?.intValue ?? -1 - #expect(permissions & 0o777 == 0o700) + #expect(socketPath == stateDir.appendingPathComponent("exec-approvals.sock").path) } } + @Test + func `harden canonical parent directory creates it with 0700 permissions`() throws { + let root = FileManager().temporaryDirectory + .appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true) + let stateDir = root.appendingPathComponent("state", isDirectory: true) + defer { try? FileManager().removeItem(at: root) } + + try ExecApprovalsSocketPathGuard.hardenParentDirectory( + for: stateDir.appendingPathComponent("exec-approvals.sock").path) + + let attrs = try FileManager().attributesOfItem(atPath: stateDir.path) + let permissions = (attrs[.posixPermissions] as? NSNumber)?.intValue ?? -1 + #expect(permissions & 0o777 == 0o700) + } + @Test func `harden custom socket parent creates nested private directories`() throws { let root = FileManager().temporaryDirectory