diff --git a/src/sandbox/sandbox-config.ts b/src/sandbox/sandbox-config.ts index baa6052d..df21ba6a 100644 --- a/src/sandbox/sandbox-config.ts +++ b/src/sandbox/sandbox-config.ts @@ -1140,6 +1140,10 @@ export const SandboxRuntimeConfigSchema = z '`safe.directory` without adding them to `filesystem.allowWrite`.', ), }) + // Reject unknown top-level keys so a typo like `denyWriteTypo` fails + // loudly instead of being silently stripped while the operator believes + // the policy is enforced. Same reasoning as the strict sub-schemas above. + .strict() .superRefine((cfg, ctx) => { const creds = cfg.credentials if (!creds) return diff --git a/test/config-validation.test.ts b/test/config-validation.test.ts index a041958b..c6ec63db 100644 --- a/test/config-validation.test.ts +++ b/test/config-validation.test.ts @@ -2,20 +2,20 @@ 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 validConfig = { + network: { + allowedDomains: [], + deniedDomains: [], + }, + filesystem: { + denyRead: [], + allowWrite: [], + denyWrite: [], + }, + } - const result = SandboxRuntimeConfigSchema.safeParse(config) + test('should validate a valid minimal config', () => { + const result = SandboxRuntimeConfigSchema.safeParse(validConfig) expect(result.success).toBe(true) }) @@ -127,6 +127,35 @@ describe('Config Validation', () => { expect(result.success).toBe(false) }) + test('should reject unknown top-level keys', () => { + // A typo like `denyWriteTypo` must fail loudly instead of being + // silently stripped while the operator believes the policy is + // enforced (issue #434). + const config = { + ...validConfig, + denyWriteTypo: ['/Users/me'], + } + + const result = SandboxRuntimeConfigSchema.safeParse(config) + expect(result.success).toBe(false) + if (!result.success) { + const messages = result.error.issues.map(issue => issue.message) + expect(messages.some(message => message.includes('denyWriteTypo'))).toBe( + true, + ) + } + }) + + test('should still reject unknown top-level keys alongside valid config', () => { + const config = { + ...validConfig, + allowEverything: true, + } + + const result = SandboxRuntimeConfigSchema.safeParse(config) + expect(result.success).toBe(false) + }) + test('should validate wildcard domains correctly', () => { const validWildcards = ['*.example.com', '*.github.io', '*.co.uk']