2026-03-11 14:42:50 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2026-03-16 21:34:48 +00:00
|
|
|
import os from 'node:os';
|
|
|
|
|
import { describe, expect, it, vi } from 'vitest';
|
2026-03-23 11:43:58 -04:00
|
|
|
import { NoopSandboxManager, sanitizePaths } from './sandboxManager.js';
|
2026-03-19 15:25:22 -07:00
|
|
|
import { createSandboxManager } from './sandboxManagerFactory.js';
|
2026-03-16 21:34:48 +00:00
|
|
|
import { LinuxSandboxManager } from '../sandbox/linux/LinuxSandboxManager.js';
|
2026-03-18 16:07:54 -04:00
|
|
|
import { MacOsSandboxManager } from '../sandbox/macos/MacOsSandboxManager.js';
|
2026-03-19 15:25:22 -07:00
|
|
|
import { WindowsSandboxManager } from './windowsSandboxManager.js';
|
2026-03-11 14:42:50 -07:00
|
|
|
|
2026-03-23 11:43:58 -04:00
|
|
|
describe('sanitizePaths', () => {
|
|
|
|
|
it('should return undefined if no paths are provided', () => {
|
|
|
|
|
expect(sanitizePaths(undefined)).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should deduplicate paths and return them', () => {
|
|
|
|
|
const paths = ['/workspace/foo', '/workspace/bar', '/workspace/foo'];
|
|
|
|
|
expect(sanitizePaths(paths)).toEqual(['/workspace/foo', '/workspace/bar']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw an error if a path is not absolute', () => {
|
|
|
|
|
const paths = ['/workspace/foo', 'relative/path'];
|
|
|
|
|
expect(() => sanitizePaths(paths)).toThrow(
|
|
|
|
|
'Sandbox path must be absolute: relative/path',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-11 14:42:50 -07:00
|
|
|
describe('NoopSandboxManager', () => {
|
|
|
|
|
const sandboxManager = new NoopSandboxManager();
|
|
|
|
|
|
|
|
|
|
it('should pass through the command and arguments unchanged', async () => {
|
|
|
|
|
const req = {
|
|
|
|
|
command: 'ls',
|
|
|
|
|
args: ['-la'],
|
|
|
|
|
cwd: '/tmp',
|
|
|
|
|
env: { PATH: '/usr/bin' },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await sandboxManager.prepareCommand(req);
|
|
|
|
|
|
|
|
|
|
expect(result.program).toBe('ls');
|
|
|
|
|
expect(result.args).toEqual(['-la']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should sanitize the environment variables', async () => {
|
|
|
|
|
const req = {
|
|
|
|
|
command: 'echo',
|
|
|
|
|
args: ['hello'],
|
|
|
|
|
cwd: '/tmp',
|
|
|
|
|
env: {
|
|
|
|
|
PATH: '/usr/bin',
|
|
|
|
|
GITHUB_TOKEN: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
|
|
|
MY_SECRET: 'super-secret',
|
|
|
|
|
SAFE_VAR: 'is-safe',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await sandboxManager.prepareCommand(req);
|
|
|
|
|
|
|
|
|
|
expect(result.env['PATH']).toBe('/usr/bin');
|
|
|
|
|
expect(result.env['SAFE_VAR']).toBe('is-safe');
|
|
|
|
|
expect(result.env['GITHUB_TOKEN']).toBeUndefined();
|
|
|
|
|
expect(result.env['MY_SECRET']).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-16 21:34:48 +00:00
|
|
|
it('should NOT allow disabling environment variable redaction if requested in config (vulnerability fix)', async () => {
|
2026-03-11 14:42:50 -07:00
|
|
|
const req = {
|
|
|
|
|
command: 'echo',
|
|
|
|
|
args: ['hello'],
|
|
|
|
|
cwd: '/tmp',
|
|
|
|
|
env: {
|
|
|
|
|
API_KEY: 'sensitive-key',
|
|
|
|
|
},
|
2026-03-23 11:43:58 -04:00
|
|
|
policy: {
|
2026-03-11 14:42:50 -07:00
|
|
|
sanitizationConfig: {
|
|
|
|
|
enableEnvironmentVariableRedaction: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await sandboxManager.prepareCommand(req);
|
|
|
|
|
|
2026-03-16 21:34:48 +00:00
|
|
|
// API_KEY should be redacted because SandboxManager forces redaction and API_KEY matches NEVER_ALLOWED_NAME_PATTERNS
|
|
|
|
|
expect(result.env['API_KEY']).toBeUndefined();
|
2026-03-11 14:42:50 -07:00
|
|
|
});
|
|
|
|
|
|
2026-03-16 21:34:48 +00:00
|
|
|
it('should respect allowedEnvironmentVariables in config but filter sensitive ones', async () => {
|
2026-03-11 14:42:50 -07:00
|
|
|
const req = {
|
|
|
|
|
command: 'echo',
|
|
|
|
|
args: ['hello'],
|
|
|
|
|
cwd: '/tmp',
|
|
|
|
|
env: {
|
2026-03-16 21:34:48 +00:00
|
|
|
MY_SAFE_VAR: 'safe-value',
|
2026-03-11 14:42:50 -07:00
|
|
|
MY_TOKEN: 'secret-token',
|
|
|
|
|
},
|
2026-03-23 11:43:58 -04:00
|
|
|
policy: {
|
2026-03-11 14:42:50 -07:00
|
|
|
sanitizationConfig: {
|
2026-03-16 21:34:48 +00:00
|
|
|
allowedEnvironmentVariables: ['MY_SAFE_VAR', 'MY_TOKEN'],
|
2026-03-11 14:42:50 -07:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await sandboxManager.prepareCommand(req);
|
|
|
|
|
|
2026-03-16 21:34:48 +00:00
|
|
|
expect(result.env['MY_SAFE_VAR']).toBe('safe-value');
|
|
|
|
|
// MY_TOKEN matches /TOKEN/i so it should be redacted despite being allowed in config
|
|
|
|
|
expect(result.env['MY_TOKEN']).toBeUndefined();
|
2026-03-11 14:42:50 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should respect blockedEnvironmentVariables in config', async () => {
|
|
|
|
|
const req = {
|
|
|
|
|
command: 'echo',
|
|
|
|
|
args: ['hello'],
|
|
|
|
|
cwd: '/tmp',
|
|
|
|
|
env: {
|
|
|
|
|
SAFE_VAR: 'safe-value',
|
|
|
|
|
BLOCKED_VAR: 'blocked-value',
|
|
|
|
|
},
|
2026-03-23 11:43:58 -04:00
|
|
|
policy: {
|
2026-03-11 14:42:50 -07:00
|
|
|
sanitizationConfig: {
|
|
|
|
|
blockedEnvironmentVariables: ['BLOCKED_VAR'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await sandboxManager.prepareCommand(req);
|
|
|
|
|
|
|
|
|
|
expect(result.env['SAFE_VAR']).toBe('safe-value');
|
|
|
|
|
expect(result.env['BLOCKED_VAR']).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-03-16 21:34:48 +00:00
|
|
|
|
|
|
|
|
describe('createSandboxManager', () => {
|
|
|
|
|
it('should return NoopSandboxManager if sandboxing is disabled', () => {
|
2026-03-19 15:25:22 -07:00
|
|
|
const manager = createSandboxManager({ enabled: false }, '/workspace');
|
2026-03-16 21:34:48 +00:00
|
|
|
expect(manager).toBeInstanceOf(NoopSandboxManager);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-18 16:07:54 -04:00
|
|
|
it.each([
|
|
|
|
|
{ platform: 'linux', expected: LinuxSandboxManager },
|
|
|
|
|
{ platform: 'darwin', expected: MacOsSandboxManager },
|
2026-03-19 15:25:22 -07:00
|
|
|
{ platform: 'win32', expected: WindowsSandboxManager },
|
2026-03-18 16:07:54 -04:00
|
|
|
] as const)(
|
|
|
|
|
'should return $expected.name if sandboxing is enabled and platform is $platform',
|
|
|
|
|
({ platform, expected }) => {
|
|
|
|
|
const osSpy = vi.spyOn(os, 'platform').mockReturnValue(platform);
|
|
|
|
|
try {
|
2026-03-19 15:25:22 -07:00
|
|
|
const manager = createSandboxManager({ enabled: true }, '/workspace');
|
2026-03-18 16:07:54 -04:00
|
|
|
expect(manager).toBeInstanceOf(expected);
|
|
|
|
|
} finally {
|
|
|
|
|
osSpy.mockRestore();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-03-16 21:34:48 +00:00
|
|
|
});
|