mirror of
https://github.com/anthropic-experimental/sandbox-runtime.git
synced 2026-05-06 13:40:59 +08:00
Add allowMachLookup config for additional macOS XPC services (#204)
This commit is contained in:
@@ -28,6 +28,7 @@ export interface MacOSSandboxParams {
|
||||
allowUnixSockets?: string[]
|
||||
allowAllUnixSockets?: boolean
|
||||
allowLocalBinding?: boolean
|
||||
allowMachLookup?: string[]
|
||||
readConfig: FsReadRestrictionConfig | undefined
|
||||
writeConfig: FsWriteRestrictionConfig | undefined
|
||||
ignoreViolations?: IgnoreViolationsConfig | undefined
|
||||
@@ -401,6 +402,7 @@ function generateSandboxProfile({
|
||||
allowUnixSockets,
|
||||
allowAllUnixSockets,
|
||||
allowLocalBinding,
|
||||
allowMachLookup,
|
||||
allowPty,
|
||||
allowGitConfig = false,
|
||||
enableWeakerNetworkIsolation = false,
|
||||
@@ -414,6 +416,7 @@ function generateSandboxProfile({
|
||||
allowUnixSockets?: string[]
|
||||
allowAllUnixSockets?: boolean
|
||||
allowLocalBinding?: boolean
|
||||
allowMachLookup?: string[]
|
||||
allowPty?: boolean
|
||||
allowGitConfig?: boolean
|
||||
enableWeakerNetworkIsolation?: boolean
|
||||
@@ -460,6 +463,16 @@ function generateSandboxProfile({
|
||||
'(allow mach-lookup (global-name "com.apple.trustd.agent"))',
|
||||
]
|
||||
: []),
|
||||
...(allowMachLookup && allowMachLookup.length > 0
|
||||
? [
|
||||
'; User-specified XPC/Mach services',
|
||||
...allowMachLookup.map(name =>
|
||||
name.endsWith('*')
|
||||
? `(allow mach-lookup (global-name-prefix ${escapePath(name.slice(0, -1))}))`
|
||||
: `(allow mach-lookup (global-name ${escapePath(name)}))`,
|
||||
),
|
||||
]
|
||||
: []),
|
||||
'',
|
||||
'; POSIX IPC - shared memory',
|
||||
'(allow ipc-posix-shm)',
|
||||
@@ -699,6 +712,7 @@ export function wrapCommandWithSandboxMacOS(
|
||||
allowUnixSockets,
|
||||
allowAllUnixSockets,
|
||||
allowLocalBinding,
|
||||
allowMachLookup,
|
||||
readConfig,
|
||||
writeConfig,
|
||||
allowPty,
|
||||
@@ -733,6 +747,7 @@ export function wrapCommandWithSandboxMacOS(
|
||||
allowUnixSockets,
|
||||
allowAllUnixSockets,
|
||||
allowLocalBinding,
|
||||
allowMachLookup,
|
||||
allowPty,
|
||||
allowGitConfig,
|
||||
enableWeakerNetworkIsolation,
|
||||
|
||||
@@ -122,6 +122,23 @@ export const NetworkConfigSchema = z.object({
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to allow binding to local ports (default: false)'),
|
||||
allowMachLookup: z
|
||||
.array(
|
||||
z.string().refine(
|
||||
val => {
|
||||
const prefix = val.endsWith('*') ? val.slice(0, -1) : val
|
||||
return !prefix.includes('*')
|
||||
},
|
||||
{
|
||||
message:
|
||||
'Wildcards are only allowed as a single trailing "*" (e.g., "com.example.*" or "*" for all services).',
|
||||
},
|
||||
),
|
||||
)
|
||||
.optional()
|
||||
.describe(
|
||||
'macOS only: Additional XPC/Mach service names to allow looking up. Supports trailing-wildcard prefix matching (e.g., "2BUA8C4S2C.com.1password.*"). Needed for tools like 1Password CLI, Playwright, or the iOS Simulator that communicate via XPC.',
|
||||
),
|
||||
httpProxyPort: z
|
||||
.number()
|
||||
.int()
|
||||
|
||||
@@ -504,6 +504,10 @@ function getAllowLocalBinding(): boolean | undefined {
|
||||
return config?.network?.allowLocalBinding
|
||||
}
|
||||
|
||||
function getAllowMachLookup(): string[] | undefined {
|
||||
return config?.network?.allowMachLookup
|
||||
}
|
||||
|
||||
function getIgnoreViolations(): Record<string, string[]> | undefined {
|
||||
return config?.ignoreViolations
|
||||
}
|
||||
@@ -671,6 +675,7 @@ async function wrapWithSandbox(
|
||||
allowUnixSockets: getAllowUnixSockets(),
|
||||
allowAllUnixSockets: getAllowAllUnixSockets(),
|
||||
allowLocalBinding: getAllowLocalBinding(),
|
||||
allowMachLookup: getAllowMachLookup(),
|
||||
ignoreViolations: getIgnoreViolations(),
|
||||
allowPty,
|
||||
allowGitConfig: getAllowGitConfig(),
|
||||
@@ -1006,6 +1011,7 @@ export interface ISandboxManager {
|
||||
getNetworkRestrictionConfig(): NetworkRestrictionConfig
|
||||
getAllowUnixSockets(): string[] | undefined
|
||||
getAllowLocalBinding(): boolean | undefined
|
||||
getAllowMachLookup(): string[] | undefined
|
||||
getIgnoreViolations(): Record<string, string[]> | undefined
|
||||
getEnableWeakerNestedSandbox(): boolean | undefined
|
||||
getProxyPort(): number | undefined
|
||||
@@ -1046,6 +1052,7 @@ export const SandboxManager: ISandboxManager = {
|
||||
getNetworkRestrictionConfig,
|
||||
getAllowUnixSockets,
|
||||
getAllowLocalBinding,
|
||||
getAllowMachLookup,
|
||||
getIgnoreViolations,
|
||||
getEnableWeakerNestedSandbox,
|
||||
getProxyPort,
|
||||
|
||||
@@ -224,6 +224,41 @@ describe('Config Validation', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('should accept valid allowMachLookup entries', () => {
|
||||
const config = {
|
||||
network: {
|
||||
allowedDomains: [],
|
||||
deniedDomains: [],
|
||||
allowMachLookup: [
|
||||
'2BUA8C4S2C.com.1password.*',
|
||||
'com.apple.CoreSimulator.CoreSimulatorService',
|
||||
'*',
|
||||
],
|
||||
},
|
||||
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
|
||||
}
|
||||
|
||||
const result = SandboxRuntimeConfigSchema.safeParse(config)
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
test.each(['com.*.foo', 'com.example.**'])(
|
||||
'should reject allowMachLookup entry with non-trailing wildcard: %s',
|
||||
entry => {
|
||||
const config = {
|
||||
network: {
|
||||
allowedDomains: [],
|
||||
deniedDomains: [],
|
||||
allowMachLookup: [entry],
|
||||
},
|
||||
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
|
||||
}
|
||||
|
||||
const result = SandboxRuntimeConfigSchema.safeParse(config)
|
||||
expect(result.success).toBe(false)
|
||||
},
|
||||
)
|
||||
|
||||
test('should use default ripgrep command when not specified', () => {
|
||||
const config = {
|
||||
network: {
|
||||
|
||||
@@ -866,3 +866,43 @@ describe.if(isMacOS)('macOS Seatbelt Process Enumeration', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe.if(isMacOS)('macOS Seatbelt allowMachLookup', () => {
|
||||
it('should emit global-name and global-name-prefix rules for configured services', () => {
|
||||
const wrappedCommand = wrapCommandWithSandboxMacOS({
|
||||
command: 'true',
|
||||
needsNetworkRestriction: true,
|
||||
allowMachLookup: [
|
||||
'com.apple.CoreSimulator.CoreSimulatorService',
|
||||
'2BUA8C4S2C.com.1password.*',
|
||||
],
|
||||
readConfig: undefined,
|
||||
writeConfig: undefined,
|
||||
})
|
||||
|
||||
expect(wrappedCommand).toContain(
|
||||
'(allow mach-lookup (global-name \\"com.apple.CoreSimulator.CoreSimulatorService\\"))',
|
||||
)
|
||||
expect(wrappedCommand).toContain(
|
||||
'(allow mach-lookup (global-name-prefix \\"2BUA8C4S2C.com.1password.\\"))',
|
||||
)
|
||||
})
|
||||
|
||||
it('should emit a syntactically valid profile with allowMachLookup set', () => {
|
||||
const wrappedCommand = wrapCommandWithSandboxMacOS({
|
||||
command: 'true',
|
||||
needsNetworkRestriction: true,
|
||||
allowMachLookup: ['com.example.service', 'com.example.prefix.*', '*'],
|
||||
readConfig: undefined,
|
||||
writeConfig: undefined,
|
||||
})
|
||||
|
||||
const result = spawnSync(wrappedCommand, {
|
||||
shell: true,
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
expect(result.status).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user