mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-29 06:25:16 -07:00
feat(core): populate sandbox forbidden paths with project ignore file contents (#24038)
This commit is contained in:
@@ -14,6 +14,9 @@ import {
|
||||
findSecretFiles,
|
||||
isSecretFile,
|
||||
tryRealpath,
|
||||
resolveSandboxPaths,
|
||||
getPathIdentity,
|
||||
type SandboxRequest,
|
||||
} from './sandboxManager.js';
|
||||
import { createSandboxManager } from './sandboxManagerFactory.js';
|
||||
import { LinuxSandboxManager } from '../sandbox/linux/LinuxSandboxManager.js';
|
||||
@@ -121,8 +124,10 @@ describe('SandboxManager', () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
describe('sanitizePaths', () => {
|
||||
it('should return undefined if no paths are provided', () => {
|
||||
expect(sanitizePaths(undefined)).toBeUndefined();
|
||||
it('should return an empty array if no paths are provided', () => {
|
||||
expect(sanitizePaths(undefined)).toEqual([]);
|
||||
expect(sanitizePaths(null)).toEqual([]);
|
||||
expect(sanitizePaths([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should deduplicate paths and return them', () => {
|
||||
@@ -133,6 +138,20 @@ describe('SandboxManager', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should deduplicate case-insensitively on Windows and macOS', () => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue('win32');
|
||||
const paths = ['/workspace/foo', '/WORKSPACE/FOO'];
|
||||
expect(sanitizePaths(paths)).toEqual(['/workspace/foo']);
|
||||
|
||||
vi.spyOn(os, 'platform').mockReturnValue('darwin');
|
||||
const macPaths = ['/tmp/foo', '/tmp/FOO'];
|
||||
expect(sanitizePaths(macPaths)).toEqual(['/tmp/foo']);
|
||||
|
||||
vi.spyOn(os, 'platform').mockReturnValue('linux');
|
||||
const linuxPaths = ['/tmp/foo', '/tmp/FOO'];
|
||||
expect(sanitizePaths(linuxPaths)).toEqual(['/tmp/foo', '/tmp/FOO']);
|
||||
});
|
||||
|
||||
it('should throw an error if a path is not absolute', () => {
|
||||
const paths = ['/workspace/foo', 'relative/path'];
|
||||
expect(() => sanitizePaths(paths)).toThrow(
|
||||
@@ -141,6 +160,110 @@ describe('SandboxManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPathIdentity', () => {
|
||||
it('should normalize slashes and strip trailing slashes', () => {
|
||||
expect(getPathIdentity('/foo/bar//baz/')).toBe(
|
||||
path.normalize('/foo/bar/baz'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle case sensitivity correctly per platform', () => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue('win32');
|
||||
expect(getPathIdentity('/Workspace/Foo')).toBe('/workspace/foo');
|
||||
|
||||
vi.spyOn(os, 'platform').mockReturnValue('darwin');
|
||||
expect(getPathIdentity('/Tmp/Foo')).toBe('/tmp/foo');
|
||||
|
||||
vi.spyOn(os, 'platform').mockReturnValue('linux');
|
||||
expect(getPathIdentity('/Tmp/Foo')).toBe('/Tmp/Foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveSandboxPaths', () => {
|
||||
it('should resolve allowed and forbidden paths', async () => {
|
||||
const options = {
|
||||
workspace: '/workspace',
|
||||
forbiddenPaths: async () => ['/workspace/forbidden'],
|
||||
};
|
||||
const req = {
|
||||
command: 'ls',
|
||||
args: [],
|
||||
cwd: '/workspace',
|
||||
env: {},
|
||||
policy: {
|
||||
allowedPaths: ['/workspace/allowed'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await resolveSandboxPaths(options, req as SandboxRequest);
|
||||
|
||||
expect(result.allowed).toEqual(['/workspace/allowed']);
|
||||
expect(result.forbidden).toEqual(['/workspace/forbidden']);
|
||||
});
|
||||
|
||||
it('should filter out workspace from allowed paths', async () => {
|
||||
const options = {
|
||||
workspace: '/workspace',
|
||||
};
|
||||
const req = {
|
||||
command: 'ls',
|
||||
args: [],
|
||||
cwd: '/workspace',
|
||||
env: {},
|
||||
policy: {
|
||||
allowedPaths: ['/workspace', '/workspace/', '/other/path'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await resolveSandboxPaths(options, req as SandboxRequest);
|
||||
|
||||
expect(result.allowed).toEqual(['/other/path']);
|
||||
});
|
||||
|
||||
it('should prioritize forbidden paths over allowed paths', async () => {
|
||||
const options = {
|
||||
workspace: '/workspace',
|
||||
forbiddenPaths: async () => ['/workspace/secret'],
|
||||
};
|
||||
const req = {
|
||||
command: 'ls',
|
||||
args: [],
|
||||
cwd: '/workspace',
|
||||
env: {},
|
||||
policy: {
|
||||
allowedPaths: ['/workspace/secret', '/workspace/normal'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await resolveSandboxPaths(options, req as SandboxRequest);
|
||||
|
||||
expect(result.allowed).toEqual(['/workspace/normal']);
|
||||
expect(result.forbidden).toEqual(['/workspace/secret']);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive conflicts on supported platforms', async () => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue('darwin');
|
||||
const options = {
|
||||
workspace: '/workspace',
|
||||
forbiddenPaths: async () => ['/workspace/SECRET'],
|
||||
};
|
||||
const req = {
|
||||
command: 'ls',
|
||||
args: [],
|
||||
cwd: '/workspace',
|
||||
env: {},
|
||||
policy: {
|
||||
allowedPaths: ['/workspace/secret'],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await resolveSandboxPaths(options, req as SandboxRequest);
|
||||
|
||||
expect(result.allowed).toEqual([]);
|
||||
expect(result.forbidden).toEqual(['/workspace/SECRET']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryRealpath', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
Reference in New Issue
Block a user