Files
sandbox-runtime/test/config-validation.test.ts
shawnm-anthropic e15986b0f3 feat(terminating-tls): Add opt-in configuration for providing CA cert and key (#247)
* Add opt-in configuration for providing CA cert and key

* Wire tlsTerminate CA loader into SandboxManager.initialize()

When network.tlsTerminate is set, initialize() loads and validates the CA
(throws on unreadable/non-PEM). reset() clears the cache. No behavior
change when tlsTerminate is unset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add tests for tlsTerminate config and loadMitmCA

- test/fixtures/tls-terminate/: committed test-only RSA-2048 self-signed CA
  (CN=srt-test-ca DO NOT TRUST, valid to 2126). README documents the
  generating openssl command.
- test/sandbox/mitm-ca.test.ts: load/cache/reset plus all throw paths
  (missing file, non-PEM, swapped cert/key) against the fixture CA.
- test/config-validation.test.ts: schema cases for network.tlsTerminate
  (optional, both paths required, non-empty).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 16:29:11 -07:00

369 lines
9.5 KiB
TypeScript

import { describe, test, expect } from 'bun:test'
import { SandboxRuntimeConfigSchema } from '../src/sandbox/sandbox-config.js'
describe('Config Validation', () => {
test('should validate a valid minimal config', () => {
const config = {
network: {
allowedDomains: [],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
})
test('should validate a config with valid domains', () => {
const config = {
network: {
allowedDomains: ['example.com', '*.github.com', 'localhost'],
deniedDomains: ['evil.com'],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
})
test('should reject invalid domain patterns', () => {
const config = {
network: {
allowedDomains: ['not-a-domain'],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(false)
})
test('should reject domain with protocol', () => {
const config = {
network: {
allowedDomains: ['https://example.com'],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(false)
})
test('should reject empty filesystem paths', () => {
const config = {
network: {
allowedDomains: [],
deniedDomains: [],
},
filesystem: {
denyRead: [''],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(false)
})
test('should validate config with optional fields', () => {
const config = {
network: {
allowedDomains: ['example.com'],
deniedDomains: [],
allowUnixSockets: ['/var/run/docker.sock'],
allowAllUnixSockets: false,
allowLocalBinding: true,
},
filesystem: {
denyRead: ['/etc/shadow'],
allowWrite: ['/tmp'],
denyWrite: ['/etc'],
},
ignoreViolations: {
'*': ['/usr/bin'],
'git push': ['/usr/bin/nc'],
},
enableWeakerNestedSandbox: true,
enableWeakerNetworkIsolation: false,
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
})
test('should reject missing required fields', () => {
const config = {
network: {
allowedDomains: [],
},
filesystem: {
denyRead: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(false)
})
test('should validate wildcard domains correctly', () => {
const validWildcards = ['*.example.com', '*.github.io', '*.co.uk']
const invalidWildcards = [
'*example.com', // Missing dot after asterisk
'*.com', // No subdomain
'*.', // Invalid format
]
for (const domain of validWildcards) {
const config = {
network: { allowedDomains: [domain], deniedDomains: [] },
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
}
for (const domain of invalidWildcards) {
const config = {
network: { allowedDomains: [domain], deniedDomains: [] },
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(false)
}
})
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: {
allowedDomains: [],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
ripgrep: {
command: '/usr/local/bin/rg',
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.ripgrep?.command).toBe('/usr/local/bin/rg')
}
})
test('should validate config with custom ripgrep command and args', () => {
const config = {
network: {
allowedDomains: [],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
ripgrep: {
command: 'claude',
args: ['--ripgrep'],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.ripgrep?.command).toBe('claude')
expect(result.data.ripgrep?.args).toEqual(['--ripgrep'])
}
})
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: {
allowedDomains: [],
deniedDomains: [],
},
filesystem: {
denyRead: [],
allowWrite: [],
denyWrite: [],
},
}
const result = SandboxRuntimeConfigSchema.safeParse(config)
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.ripgrep).toBeUndefined()
}
})
describe('bwrapPath / socatPath', () => {
const base = {
network: { allowedDomains: [], deniedDomains: [] },
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
}
test('accepts absolute paths', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
bwrapPath: '/usr/local/bin/bwrap',
socatPath: '/opt/tools/socat',
})
expect(result.success).toBe(true)
})
test('rejects relative bwrapPath', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
bwrapPath: 'bwrap',
})
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.issues[0]?.message).toContain('must be absolute')
}
})
test('rejects relative socatPath', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
socatPath: './bin/socat',
})
expect(result.success).toBe(false)
})
test('rejects empty bwrapPath', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
bwrapPath: '',
})
expect(result.success).toBe(false)
})
})
describe('network.tlsTerminate', () => {
const base = {
network: { allowedDomains: [], deniedDomains: [] },
filesystem: { denyRead: [], allowWrite: [], denyWrite: [] },
}
test('is optional — config without it validates', () => {
expect(SandboxRuntimeConfigSchema.safeParse(base).success).toBe(true)
})
test('accepts caCertPath + caKeyPath', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
network: {
...base.network,
tlsTerminate: { caCertPath: '/etc/ca.crt', caKeyPath: '/etc/ca.key' },
},
})
expect(result.success).toBe(true)
})
test('rejects when caKeyPath is missing', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
network: {
...base.network,
tlsTerminate: { caCertPath: '/etc/ca.crt' },
},
})
expect(result.success).toBe(false)
})
test('rejects empty caCertPath', () => {
const result = SandboxRuntimeConfigSchema.safeParse({
...base,
network: {
...base.network,
tlsTerminate: { caCertPath: '', caKeyPath: '/etc/ca.key' },
},
})
expect(result.success).toBe(false)
})
})
})