diff --git a/README.md b/README.md index 5f6cd57..0fd4f7d 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,8 @@ srt --settings /path/to/srt-settings.json "git push": ["/usr/bin/nc"], "npm": ["/private/tmp"] }, - "enableWeakerNestedSandbox": false + "enableWeakerNestedSandbox": false, + "enableWeakerNetworkIsolation": false } ``` @@ -341,6 +342,7 @@ Examples: - `ignoreViolations` - Object mapping command patterns to arrays of paths where violations should be ignored - `enableWeakerNestedSandbox` - Enable weaker sandbox mode for Docker environments (boolean, default: false) +- `enableWeakerNetworkIsolation` - Allow access to `com.apple.trustd.agent` in the macOS sandbox (boolean, default: false). This is needed for Go programs (`gh`, `gcloud`, `terraform`, `kubectl`, etc.) to verify TLS certificates when using `httpProxyPort` with a MITM proxy and custom CA. **Security warning:** enabling this opens a potential data exfiltration vector through the trustd service. ### Common Configuration Recipes @@ -637,6 +639,7 @@ Users should be aware of potential risks that come from allowing broad domains l - Privilege Escalation via Unix Sockets: The `allowUnixSockets` configuration can inadvertently grant access to powerful system services that could lead to sandbox bypasses. For example, if it is used to allow access to `/var/run/docker.sock` this would effectively grant access to the host system through exploiting the docker socket. Users are encouraged to carefully consider any unix sockets that they allow through the sandbox. - Filesystem Permission Escalation: Overly broad filesystem write permissions can enable privilege escalation attacks. Allowing writes to directories containing executables in `$PATH`, system configuration directories, or user shell configuration files (`.bashrc`, `.zshrc`) can lead to code execution in different security contexts when other users or system processes access these files. - Linux Sandbox Strength: The Linux implementation provides strong filesystem and network isolation but includes an `enableWeakerNestedSandbox` mode that enables it to work inside of Docker environments without privileged namespaces. This option considerably weakens security and should only be used incases where additional isolation is otherwise enforced. +- Weaker Network Isolation (macOS): The `enableWeakerNetworkIsolation` option re-enables access to `com.apple.trustd.agent`, which is needed for Go programs to verify TLS certificates via the macOS Security framework. This opens a potential data exfiltration vector through the trustd service and should only be enabled when Go TLS verification is required (e.g., when using `httpProxyPort` with a MITM proxy and custom CA). ### Known Limitations and Future Work diff --git a/src/sandbox/macos-sandbox-utils.ts b/src/sandbox/macos-sandbox-utils.ts index b9e7378..1ebf8b0 100644 --- a/src/sandbox/macos-sandbox-utils.ts +++ b/src/sandbox/macos-sandbox-utils.ts @@ -33,6 +33,7 @@ export interface MacOSSandboxParams { ignoreViolations?: IgnoreViolationsConfig | undefined allowPty?: boolean allowGitConfig?: boolean + enableWeakerNetworkIsolation?: boolean binShell?: string } @@ -328,6 +329,7 @@ function generateSandboxProfile({ allowLocalBinding, allowPty, allowGitConfig = false, + enableWeakerNetworkIsolation = false, logTag, }: { readConfig: FsReadRestrictionConfig | undefined @@ -340,6 +342,7 @@ function generateSandboxProfile({ allowLocalBinding?: boolean allowPty?: boolean allowGitConfig?: boolean + enableWeakerNetworkIsolation?: boolean logTag: string }): string { const profile: string[] = [ @@ -377,6 +380,13 @@ function generateSandboxProfile({ ' (global-name "com.apple.coreservices.launchservicesd")', ')', '', + ...(enableWeakerNetworkIsolation + ? [ + '; trustd.agent - needed for Go TLS certificate verification (weaker network isolation)', + '(allow mach-lookup (global-name "com.apple.trustd.agent"))', + ] + : []), + '', '; POSIX IPC - shared memory', '(allow ipc-posix-shm)', '', @@ -615,6 +625,7 @@ export function wrapCommandWithSandboxMacOS( writeConfig, allowPty, allowGitConfig = false, + enableWeakerNetworkIsolation = false, binShell, } = params @@ -646,6 +657,7 @@ export function wrapCommandWithSandboxMacOS( allowLocalBinding, allowPty, allowGitConfig, + enableWeakerNetworkIsolation, logTag, }) diff --git a/src/sandbox/sandbox-config.ts b/src/sandbox/sandbox-config.ts index 97f5612..642c47f 100644 --- a/src/sandbox/sandbox-config.ts +++ b/src/sandbox/sandbox-config.ts @@ -189,6 +189,15 @@ export const SandboxRuntimeConfigSchema = z.object({ .boolean() .optional() .describe('Enable weaker nested sandbox mode (for Docker environments)'), + enableWeakerNetworkIsolation: z + .boolean() + .optional() + .describe( + 'Enable weaker network isolation to allow access to com.apple.trustd.agent (macOS only). ' + + 'This is needed for Go programs (gh, gcloud, terraform, kubectl, etc.) to verify TLS certificates ' + + 'when using httpProxyPort with a MITM proxy and custom CA. Enabling this opens a potential data ' + + 'exfiltration vector through the trustd service. Only enable if you need Go TLS verification.', + ), ripgrep: RipgrepConfigSchema.optional().describe( 'Custom ripgrep configuration (default: { command: "rg" })', ), diff --git a/src/sandbox/sandbox-manager.ts b/src/sandbox/sandbox-manager.ts index 1fe5d97..6589f42 100644 --- a/src/sandbox/sandbox-manager.ts +++ b/src/sandbox/sandbox-manager.ts @@ -451,6 +451,10 @@ function getEnableWeakerNestedSandbox(): boolean | undefined { return config?.enableWeakerNestedSandbox } +function getEnableWeakerNetworkIsolation(): boolean | undefined { + return config?.enableWeakerNetworkIsolation +} + function getRipgrepConfig(): { command: string; args?: string[] } { return config?.ripgrep ?? { command: 'rg' } } @@ -581,6 +585,7 @@ async function wrapWithSandbox( ignoreViolations: getIgnoreViolations(), allowPty, allowGitConfig: getAllowGitConfig(), + enableWeakerNetworkIsolation: getEnableWeakerNetworkIsolation(), binShell, }) diff --git a/test/config-validation.test.ts b/test/config-validation.test.ts index 38a31a5..9a6d3d9 100644 --- a/test/config-validation.test.ts +++ b/test/config-validation.test.ts @@ -106,6 +106,7 @@ describe('Config Validation', () => { 'git push': ['/usr/bin/nc'], }, enableWeakerNestedSandbox: true, + enableWeakerNetworkIsolation: false, } const result = SandboxRuntimeConfigSchema.safeParse(config) @@ -127,16 +128,12 @@ describe('Config Validation', () => { }) test('should validate wildcard domains correctly', () => { - const validWildcards = [ - '*.example.com', - '*.github.io', - '*.co.uk', - ] + const validWildcards = ['*.example.com', '*.github.io', '*.co.uk'] const invalidWildcards = [ - '*example.com', // Missing dot after asterisk - '*.com', // No subdomain - '*.', // Invalid format + '*example.com', // Missing dot after asterisk + '*.com', // No subdomain + '*.', // Invalid format ] for (const domain of validWildcards) { @@ -158,6 +155,27 @@ describe('Config Validation', () => { } }) + test('should validate config with enableWeakerNetworkIsolation', () => { + const config = { + network: { + allowedDomains: ['example.com'], + deniedDomains: [], + }, + filesystem: { + denyRead: [], + allowWrite: [], + denyWrite: [], + }, + enableWeakerNetworkIsolation: true, + } + + const result = SandboxRuntimeConfigSchema.safeParse(config) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.enableWeakerNetworkIsolation).toBe(true) + } + }) + test('should validate config with custom ripgrep command', () => { const config = { network: {