diff --git a/src/sandbox/macos-sandbox-utils.ts b/src/sandbox/macos-sandbox-utils.ts index 9f816fd4..3174eec5 100644 --- a/src/sandbox/macos-sandbox-utils.ts +++ b/src/sandbox/macos-sandbox-utils.ts @@ -35,6 +35,7 @@ export interface MacOSSandboxParams { allowGitConfig?: boolean enableWeakerNetworkIsolation?: boolean allowClipboard?: boolean + allowNotifications?: boolean binShell?: string } @@ -332,6 +333,7 @@ function generateSandboxProfile({ allowGitConfig = false, enableWeakerNetworkIsolation = false, allowClipboard = false, + allowNotifications = false, logTag, }: { readConfig: FsReadRestrictionConfig | undefined @@ -346,6 +348,7 @@ function generateSandboxProfile({ allowGitConfig?: boolean enableWeakerNetworkIsolation?: boolean allowClipboard?: boolean + allowNotifications?: boolean logTag: string }): string { const profile: string[] = [ @@ -367,6 +370,8 @@ function generateSandboxProfile({ '', '; Mach IPC - specific services only (no wildcard)', '(allow mach-lookup', + ' (global-name "com.apple.audio.AudioComponentRegistrar")', + ' (global-name "com.apple.audio.audiohald")', ' (global-name "com.apple.audio.systemsoundserver")', ' (global-name "com.apple.distributed_notifications@Uv3")', ' (global-name "com.apple.FontObjectsServer")', @@ -499,6 +504,21 @@ function generateSandboxProfile({ ) } + // Notification access - SCOPED TO TERMINAL-NOTIFIER ONLY using signing-identifier + if (allowNotifications) { + profile.push( + '; Notification access - scoped to terminal-notifier via signing-identifier', + '; Minimal set: only core notification pipeline services', + '(with-filter (signing-identifier "terminal-notifier")', + ' (allow mach-lookup', + ' (global-name "com.apple.usernoted.client")', + ' (global-name "com.apple.windowserver.active")', + ' )', + ')', + '', + ) + } + profile.push( '; File I/O on device files', '(allow file-ioctl (literal "/dev/null"))', @@ -655,6 +675,7 @@ export function wrapCommandWithSandboxMacOS( allowGitConfig = false, enableWeakerNetworkIsolation = false, allowClipboard = false, + allowNotifications = false, binShell, } = params @@ -688,6 +709,7 @@ export function wrapCommandWithSandboxMacOS( allowGitConfig, enableWeakerNetworkIsolation, allowClipboard, + allowNotifications, logTag, }) diff --git a/src/sandbox/sandbox-config.ts b/src/sandbox/sandbox-config.ts index eaed9db4..35399555 100644 --- a/src/sandbox/sandbox-config.ts +++ b/src/sandbox/sandbox-config.ts @@ -221,6 +221,12 @@ export const SandboxRuntimeConfigSchema = z.object({ .describe( 'Allow clipboard (pasteboard) access (macOS only). Required for pasting images into sandboxed processes.', ), + allowNotifications: z + .boolean() + .optional() + .describe( + 'Allow notification access (macOS only). Required for tools like terminal-notifier that need window server, TCC, and dock access.', + ), seccomp: SeccompConfigSchema.optional().describe( 'Custom seccomp binary paths (Linux only).', ), diff --git a/src/sandbox/sandbox-manager.ts b/src/sandbox/sandbox-manager.ts index 641c85dd..9efdc85f 100644 --- a/src/sandbox/sandbox-manager.ts +++ b/src/sandbox/sandbox-manager.ts @@ -471,6 +471,10 @@ function getAllowClipboard(): boolean { return config?.allowClipboard ?? false } +function getAllowNotifications(): boolean { + return config?.allowNotifications ?? false +} + function getSeccompConfig(): | { bpfPath?: string; applyPath?: string } | undefined { @@ -591,6 +595,7 @@ async function wrapWithSandbox( allowGitConfig: getAllowGitConfig(), enableWeakerNetworkIsolation: getEnableWeakerNetworkIsolation(), allowClipboard: getAllowClipboard(), + allowNotifications: getAllowNotifications(), binShell, })