@google/gemini-cli 0.28.0-preview.5 → 0.28.0-preview.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/google-gemini-cli-0.28.0-preview.6.tgz +0 -0
- package/dist/package.json +3 -2
- package/dist/src/config/extension-manager.js +1 -1
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extensions/extensionUpdates.test.js +98 -132
- package/dist/src/config/extensions/extensionUpdates.test.js.map +1 -1
- package/dist/src/config/trustedFolders.d.ts +1 -1
- package/dist/src/config/trustedFolders.js +80 -15
- package/dist/src/config/trustedFolders.js.map +1 -1
- package/dist/src/config/trustedFolders.test.js +211 -558
- package/dist/src/config/trustedFolders.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ui/components/ConsentPrompt.test.js +4 -4
- package/dist/src/ui/components/ConsentPrompt.test.js.map +1 -1
- package/dist/src/ui/components/LogoutConfirmationDialog.test.js +8 -4
- package/dist/src/ui/components/LogoutConfirmationDialog.test.js.map +1 -1
- package/dist/src/ui/components/MultiFolderTrustDialog.js +1 -1
- package/dist/src/ui/components/MultiFolderTrustDialog.js.map +1 -1
- package/dist/src/ui/components/PermissionsModifyTrustDialog.js +9 -8
- package/dist/src/ui/components/PermissionsModifyTrustDialog.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.d.ts +1 -1
- package/dist/src/ui/hooks/useFolderTrust.js +2 -2
- package/dist/src/ui/hooks/useFolderTrust.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +8 -8
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.d.ts +2 -2
- package/dist/src/ui/hooks/usePermissionsModifyTrust.js +5 -5
- package/dist/src/ui/hooks/usePermissionsModifyTrust.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +31 -31
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/dist/google-gemini-cli-0.28.0-preview.4.tgz +0 -0
|
@@ -3,93 +3,140 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
import { FatalConfigError, ideContextStore, AuthType, } from '@google/gemini-cli-core';
|
|
8
|
-
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
7
|
import * as fs from 'node:fs';
|
|
10
|
-
import stripJsonComments from 'strip-json-comments';
|
|
11
8
|
import * as path from 'node:path';
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import { FatalConfigError, ideContextStore, coreEvents, } from '@google/gemini-cli-core';
|
|
11
|
+
import { loadTrustedFolders, TrustLevel, isWorkspaceTrusted, resetTrustedFoldersForTesting, } from './trustedFolders.js';
|
|
12
|
+
import { loadEnvironment } from './settings.js';
|
|
14
13
|
import { createMockSettings } from '../test-utils/settings.js';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const actualOs = await importOriginal();
|
|
18
|
-
return {
|
|
19
|
-
...actualOs,
|
|
20
|
-
homedir: vi.fn(() => '/mock/home/user'),
|
|
21
|
-
platform: vi.fn(() => 'linux'),
|
|
22
|
-
};
|
|
23
|
-
});
|
|
14
|
+
// We explicitly do NOT mock 'fs' or 'proper-lockfile' here to ensure
|
|
15
|
+
// we are testing the actual behavior on the real file system.
|
|
24
16
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|
25
17
|
const actual = await importOriginal();
|
|
26
18
|
return {
|
|
27
19
|
...actual,
|
|
28
20
|
homedir: () => '/mock/home/user',
|
|
21
|
+
coreEvents: {
|
|
22
|
+
emitFeedback: vi.fn(),
|
|
23
|
+
},
|
|
29
24
|
};
|
|
30
25
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
...actualFs,
|
|
35
|
-
existsSync: vi.fn(),
|
|
36
|
-
readFileSync: vi.fn(),
|
|
37
|
-
writeFileSync: vi.fn(),
|
|
38
|
-
mkdirSync: vi.fn(),
|
|
39
|
-
realpathSync: vi.fn().mockImplementation((p) => p),
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
vi.mock('strip-json-comments', () => ({
|
|
43
|
-
default: vi.fn((content) => content),
|
|
44
|
-
}));
|
|
45
|
-
describe('Trusted Folders Loading', () => {
|
|
46
|
-
let mockStripJsonComments;
|
|
47
|
-
let mockFsWriteFileSync;
|
|
26
|
+
describe('Trusted Folders', () => {
|
|
27
|
+
let tempDir;
|
|
28
|
+
let trustedFoldersPath;
|
|
48
29
|
beforeEach(() => {
|
|
30
|
+
// Create a temporary directory for each test
|
|
31
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-'));
|
|
32
|
+
trustedFoldersPath = path.join(tempDir, 'trustedFolders.json');
|
|
33
|
+
// Set the environment variable to point to the temp file
|
|
34
|
+
vi.stubEnv('GEMINI_CLI_TRUSTED_FOLDERS_PATH', trustedFoldersPath);
|
|
35
|
+
// Reset the internal state
|
|
49
36
|
resetTrustedFoldersForTesting();
|
|
50
|
-
vi.
|
|
51
|
-
mockStripJsonComments = vi.mocked(stripJsonComments);
|
|
52
|
-
mockFsWriteFileSync = vi.mocked(fs.writeFileSync);
|
|
53
|
-
vi.mocked(osActual.homedir).mockReturnValue('/mock/home/user');
|
|
54
|
-
mockStripJsonComments.mockImplementation((jsonString) => jsonString);
|
|
55
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
56
|
-
vi.mocked(fs.readFileSync).mockReturnValue('{}');
|
|
57
|
-
vi.mocked(fs.realpathSync).mockImplementation((p) => p.toString());
|
|
37
|
+
vi.clearAllMocks();
|
|
58
38
|
});
|
|
59
39
|
afterEach(() => {
|
|
60
|
-
|
|
40
|
+
// Clean up the temporary directory
|
|
41
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
42
|
+
vi.unstubAllEnvs();
|
|
61
43
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
describe('Locking & Concurrency', () => {
|
|
45
|
+
it('setValue should handle concurrent calls correctly using real lockfile', async () => {
|
|
46
|
+
// Initialize the file
|
|
47
|
+
fs.writeFileSync(trustedFoldersPath, '{}', 'utf-8');
|
|
48
|
+
const loadedFolders = loadTrustedFolders();
|
|
49
|
+
// Start two concurrent calls
|
|
50
|
+
// These will race to acquire the lock on the real file system
|
|
51
|
+
const p1 = loadedFolders.setValue('/path1', TrustLevel.TRUST_FOLDER);
|
|
52
|
+
const p2 = loadedFolders.setValue('/path2', TrustLevel.TRUST_FOLDER);
|
|
53
|
+
await Promise.all([p1, p2]);
|
|
54
|
+
// Verify final state in the file
|
|
55
|
+
const content = fs.readFileSync(trustedFoldersPath, 'utf-8');
|
|
56
|
+
const config = JSON.parse(content);
|
|
57
|
+
expect(config).toEqual({
|
|
58
|
+
'/path1': TrustLevel.TRUST_FOLDER,
|
|
59
|
+
'/path2': TrustLevel.TRUST_FOLDER,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('Loading & Parsing', () => {
|
|
64
|
+
it('should load empty rules if no files exist', () => {
|
|
65
|
+
const { rules, errors } = loadTrustedFolders();
|
|
66
|
+
expect(rules).toEqual([]);
|
|
67
|
+
expect(errors).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it('should load rules from the configuration file', () => {
|
|
70
|
+
const config = {
|
|
71
|
+
'/user/folder': TrustLevel.TRUST_FOLDER,
|
|
72
|
+
};
|
|
73
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
74
|
+
const { rules, errors } = loadTrustedFolders();
|
|
75
|
+
expect(rules).toEqual([
|
|
76
|
+
{ path: '/user/folder', trustLevel: TrustLevel.TRUST_FOLDER },
|
|
77
|
+
]);
|
|
78
|
+
expect(errors).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
it('should handle JSON parsing errors gracefully', () => {
|
|
81
|
+
fs.writeFileSync(trustedFoldersPath, 'invalid json', 'utf-8');
|
|
82
|
+
const { rules, errors } = loadTrustedFolders();
|
|
83
|
+
expect(rules).toEqual([]);
|
|
84
|
+
expect(errors.length).toBe(1);
|
|
85
|
+
expect(errors[0].path).toBe(trustedFoldersPath);
|
|
86
|
+
expect(errors[0].message).toContain('Unexpected token');
|
|
87
|
+
});
|
|
88
|
+
it('should handle non-object JSON gracefully', () => {
|
|
89
|
+
fs.writeFileSync(trustedFoldersPath, 'null', 'utf-8');
|
|
90
|
+
const { rules, errors } = loadTrustedFolders();
|
|
91
|
+
expect(rules).toEqual([]);
|
|
92
|
+
expect(errors.length).toBe(1);
|
|
93
|
+
expect(errors[0].message).toContain('not a valid JSON object');
|
|
94
|
+
});
|
|
95
|
+
it('should handle invalid trust levels gracefully', () => {
|
|
96
|
+
const config = {
|
|
97
|
+
'/path': 'INVALID_LEVEL',
|
|
98
|
+
};
|
|
99
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
100
|
+
const { rules, errors } = loadTrustedFolders();
|
|
101
|
+
expect(rules).toEqual([]);
|
|
102
|
+
expect(errors.length).toBe(1);
|
|
103
|
+
expect(errors[0].message).toContain('Invalid trust level "INVALID_LEVEL"');
|
|
104
|
+
});
|
|
105
|
+
it('should support JSON with comments', () => {
|
|
106
|
+
const content = `
|
|
107
|
+
{
|
|
108
|
+
// This is a comment
|
|
109
|
+
"/path": "TRUST_FOLDER"
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
fs.writeFileSync(trustedFoldersPath, content, 'utf-8');
|
|
113
|
+
const { rules, errors } = loadTrustedFolders();
|
|
114
|
+
expect(rules).toEqual([
|
|
115
|
+
{ path: '/path', trustLevel: TrustLevel.TRUST_FOLDER },
|
|
116
|
+
]);
|
|
117
|
+
expect(errors).toEqual([]);
|
|
118
|
+
});
|
|
66
119
|
});
|
|
67
120
|
describe('isPathTrusted', () => {
|
|
68
|
-
function setup(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (p.toString() === getTrustedFoldersPath())
|
|
72
|
-
return JSON.stringify(config);
|
|
73
|
-
return '{}';
|
|
74
|
-
});
|
|
75
|
-
const folders = loadTrustedFolders();
|
|
76
|
-
return { folders };
|
|
121
|
+
function setup(config) {
|
|
122
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
123
|
+
return loadTrustedFolders();
|
|
77
124
|
}
|
|
78
125
|
it('provides a method to determine if a path is trusted', () => {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'/secret/publickeys': TrustLevel.TRUST_FOLDER,
|
|
86
|
-
},
|
|
126
|
+
const folders = setup({
|
|
127
|
+
'./myfolder': TrustLevel.TRUST_FOLDER,
|
|
128
|
+
'/trustedparent/trustme': TrustLevel.TRUST_PARENT,
|
|
129
|
+
'/user/folder': TrustLevel.TRUST_FOLDER,
|
|
130
|
+
'/secret': TrustLevel.DO_NOT_TRUST,
|
|
131
|
+
'/secret/publickeys': TrustLevel.TRUST_FOLDER,
|
|
87
132
|
});
|
|
133
|
+
// We need to resolve relative paths for comparison since the implementation uses realpath
|
|
134
|
+
const resolvedMyFolder = path.resolve('./myfolder');
|
|
88
135
|
expect(folders.isPathTrusted('/secret')).toBe(false);
|
|
89
136
|
expect(folders.isPathTrusted('/user/folder')).toBe(true);
|
|
90
137
|
expect(folders.isPathTrusted('/secret/publickeys/public.pem')).toBe(true);
|
|
91
138
|
expect(folders.isPathTrusted('/user/folder/harhar')).toBe(true);
|
|
92
|
-
expect(folders.isPathTrusted('
|
|
139
|
+
expect(folders.isPathTrusted(path.join(resolvedMyFolder, 'somefile.jpg'))).toBe(true);
|
|
93
140
|
expect(folders.isPathTrusted('/trustedparent/someotherfolder')).toBe(true);
|
|
94
141
|
expect(folders.isPathTrusted('/trustedparent/trustme')).toBe(true);
|
|
95
142
|
// No explicit rule covers this file
|
|
@@ -98,335 +145,51 @@ describe('Trusted Folders Loading', () => {
|
|
|
98
145
|
expect(folders.isPathTrusted('/user/someotherfolder')).toBe(undefined);
|
|
99
146
|
});
|
|
100
147
|
it('prioritizes the longest matching path (precedence)', () => {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'/parent/trustme/butnotthis': TrustLevel.DO_NOT_TRUST,
|
|
108
|
-
},
|
|
148
|
+
const folders = setup({
|
|
149
|
+
'/a': TrustLevel.TRUST_FOLDER,
|
|
150
|
+
'/a/b': TrustLevel.DO_NOT_TRUST,
|
|
151
|
+
'/a/b/c': TrustLevel.TRUST_FOLDER,
|
|
152
|
+
'/parent/trustme': TrustLevel.TRUST_PARENT,
|
|
153
|
+
'/parent/trustme/butnotthis': TrustLevel.DO_NOT_TRUST,
|
|
109
154
|
});
|
|
110
|
-
// /a/b/c/d matches /a (len 2), /a/b (len 4), /a/b/c (len 6).
|
|
111
|
-
// /a/b/c wins (TRUST_FOLDER).
|
|
112
155
|
expect(folders.isPathTrusted('/a/b/c/d')).toBe(true);
|
|
113
|
-
// /a/b/x matches /a (len 2), /a/b (len 4).
|
|
114
|
-
// /a/b wins (DO_NOT_TRUST).
|
|
115
156
|
expect(folders.isPathTrusted('/a/b/x')).toBe(false);
|
|
116
|
-
// /a/x matches /a (len 2).
|
|
117
|
-
// /a wins (TRUST_FOLDER).
|
|
118
157
|
expect(folders.isPathTrusted('/a/x')).toBe(true);
|
|
119
|
-
// Overlap with TRUST_PARENT
|
|
120
|
-
// /parent/trustme/butnotthis/file matches:
|
|
121
|
-
// - /parent/trustme (len 15, TRUST_PARENT -> effective /parent)
|
|
122
|
-
// - /parent/trustme/butnotthis (len 26, DO_NOT_TRUST)
|
|
123
|
-
// /parent/trustme/butnotthis wins.
|
|
124
158
|
expect(folders.isPathTrusted('/parent/trustme/butnotthis/file')).toBe(false);
|
|
125
|
-
// /parent/other matches /parent/trustme (len 15, effective /parent)
|
|
126
159
|
expect(folders.isPathTrusted('/parent/other')).toBe(true);
|
|
127
160
|
});
|
|
128
161
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return '{}';
|
|
162
|
+
describe('setValue', () => {
|
|
163
|
+
it('should update the user config and save it atomically', async () => {
|
|
164
|
+
fs.writeFileSync(trustedFoldersPath, '{}', 'utf-8');
|
|
165
|
+
const loadedFolders = loadTrustedFolders();
|
|
166
|
+
await loadedFolders.setValue('/new/path', TrustLevel.TRUST_FOLDER);
|
|
167
|
+
expect(loadedFolders.user.config['/new/path']).toBe(TrustLevel.TRUST_FOLDER);
|
|
168
|
+
const content = fs.readFileSync(trustedFoldersPath, 'utf-8');
|
|
169
|
+
const config = JSON.parse(content);
|
|
170
|
+
expect(config['/new/path']).toBe(TrustLevel.TRUST_FOLDER);
|
|
139
171
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
it('should handle JSON parsing errors gracefully', () => {
|
|
147
|
-
const userPath = getTrustedFoldersPath();
|
|
148
|
-
vi.mocked(fs.existsSync).mockImplementation((p) => p.toString() === userPath);
|
|
149
|
-
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
|
150
|
-
if (p.toString() === userPath)
|
|
151
|
-
return 'invalid json';
|
|
152
|
-
return '{}';
|
|
172
|
+
it('should throw FatalConfigError if there were load errors', async () => {
|
|
173
|
+
fs.writeFileSync(trustedFoldersPath, 'invalid json', 'utf-8');
|
|
174
|
+
const loadedFolders = loadTrustedFolders();
|
|
175
|
+
expect(loadedFolders.errors.length).toBe(1);
|
|
176
|
+
await expect(loadedFolders.setValue('/some/path', TrustLevel.TRUST_FOLDER)).rejects.toThrow(FatalConfigError);
|
|
153
177
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
'/
|
|
166
|
-
};
|
|
167
|
-
vi.mocked(fs.readFileSync).mockImplementation((p) => {
|
|
168
|
-
if (p.toString() === customPath)
|
|
169
|
-
return JSON.stringify(userContent);
|
|
170
|
-
return '{}';
|
|
171
|
-
});
|
|
172
|
-
const { rules, errors } = loadTrustedFolders();
|
|
173
|
-
expect(rules).toEqual([
|
|
174
|
-
{
|
|
175
|
-
path: '/user/folder/from/env',
|
|
176
|
-
trustLevel: TrustLevel.TRUST_FOLDER,
|
|
177
|
-
},
|
|
178
|
-
]);
|
|
179
|
-
expect(errors).toEqual([]);
|
|
180
|
-
delete process.env['GEMINI_CLI_TRUSTED_FOLDERS_PATH'];
|
|
181
|
-
});
|
|
182
|
-
it('setValue should update the user config and save it', () => {
|
|
183
|
-
const loadedFolders = loadTrustedFolders();
|
|
184
|
-
loadedFolders.setValue('/new/path', TrustLevel.TRUST_FOLDER);
|
|
185
|
-
expect(loadedFolders.user.config['/new/path']).toBe(TrustLevel.TRUST_FOLDER);
|
|
186
|
-
expect(mockFsWriteFileSync).toHaveBeenCalledWith(getTrustedFoldersPath(), JSON.stringify({ '/new/path': TrustLevel.TRUST_FOLDER }, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
describe('isWorkspaceTrusted', () => {
|
|
190
|
-
let mockCwd;
|
|
191
|
-
const mockRules = {};
|
|
192
|
-
const mockSettings = {
|
|
193
|
-
security: {
|
|
194
|
-
folderTrust: {
|
|
195
|
-
enabled: true,
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
beforeEach(() => {
|
|
200
|
-
resetTrustedFoldersForTesting();
|
|
201
|
-
vi.spyOn(process, 'cwd').mockImplementation(() => mockCwd);
|
|
202
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
203
|
-
if (p.toString() === getTrustedFoldersPath()) {
|
|
204
|
-
return JSON.stringify(mockRules);
|
|
205
|
-
}
|
|
206
|
-
return '{}';
|
|
178
|
+
it('should report corrupted config via coreEvents.emitFeedback and still succeed', async () => {
|
|
179
|
+
// Initialize with valid JSON
|
|
180
|
+
fs.writeFileSync(trustedFoldersPath, '{}', 'utf-8');
|
|
181
|
+
const loadedFolders = loadTrustedFolders();
|
|
182
|
+
// Corrupt the file after initial load
|
|
183
|
+
fs.writeFileSync(trustedFoldersPath, 'invalid json', 'utf-8');
|
|
184
|
+
await loadedFolders.setValue('/new/path', TrustLevel.TRUST_FOLDER);
|
|
185
|
+
expect(coreEvents.emitFeedback).toHaveBeenCalledWith('error', expect.stringContaining('may be corrupted'), expect.any(Error));
|
|
186
|
+
// Should have overwritten the corrupted file with new valid config
|
|
187
|
+
const content = fs.readFileSync(trustedFoldersPath, 'utf-8');
|
|
188
|
+
const config = JSON.parse(content);
|
|
189
|
+
expect(config).toEqual({ '/new/path': TrustLevel.TRUST_FOLDER });
|
|
207
190
|
});
|
|
208
|
-
vi.spyOn(fs, 'existsSync').mockImplementation((p) => p.toString() === getTrustedFoldersPath());
|
|
209
191
|
});
|
|
210
|
-
|
|
211
|
-
vi.restoreAllMocks();
|
|
212
|
-
// Clear the object
|
|
213
|
-
Object.keys(mockRules).forEach((key) => delete mockRules[key]);
|
|
214
|
-
});
|
|
215
|
-
it('should throw a fatal error if the config is malformed', () => {
|
|
216
|
-
mockCwd = '/home/user/projectA';
|
|
217
|
-
// This mock needs to be specific to this test to override the one in beforeEach
|
|
218
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
219
|
-
if (p.toString() === getTrustedFoldersPath()) {
|
|
220
|
-
return '{"foo": "bar",}'; // Malformed JSON with trailing comma
|
|
221
|
-
}
|
|
222
|
-
return '{}';
|
|
223
|
-
});
|
|
224
|
-
expect(() => isWorkspaceTrusted(mockSettings)).toThrow(FatalConfigError);
|
|
225
|
-
expect(() => isWorkspaceTrusted(mockSettings)).toThrow(/Please fix the configuration file/);
|
|
226
|
-
});
|
|
227
|
-
it('should throw a fatal error if the config is not a JSON object', () => {
|
|
228
|
-
mockCwd = '/home/user/projectA';
|
|
229
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
230
|
-
if (p.toString() === getTrustedFoldersPath()) {
|
|
231
|
-
return 'null';
|
|
232
|
-
}
|
|
233
|
-
return '{}';
|
|
234
|
-
});
|
|
235
|
-
expect(() => isWorkspaceTrusted(mockSettings)).toThrow(FatalConfigError);
|
|
236
|
-
expect(() => isWorkspaceTrusted(mockSettings)).toThrow(/not a valid JSON object/);
|
|
237
|
-
});
|
|
238
|
-
it('should return true for a directly trusted folder', () => {
|
|
239
|
-
mockCwd = '/home/user/projectA';
|
|
240
|
-
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
|
241
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
242
|
-
isTrusted: true,
|
|
243
|
-
source: 'file',
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
it('should return true for a child of a trusted folder', () => {
|
|
247
|
-
mockCwd = '/home/user/projectA/src';
|
|
248
|
-
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
|
249
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
250
|
-
isTrusted: true,
|
|
251
|
-
source: 'file',
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
it('should return true for a child of a trusted parent folder', () => {
|
|
255
|
-
mockCwd = '/home/user/projectB';
|
|
256
|
-
mockRules['/home/user/projectB/somefile.txt'] = TrustLevel.TRUST_PARENT;
|
|
257
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
258
|
-
isTrusted: true,
|
|
259
|
-
source: 'file',
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
it('should return false for a directly untrusted folder', () => {
|
|
263
|
-
mockCwd = '/home/user/untrusted';
|
|
264
|
-
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
|
265
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
266
|
-
isTrusted: false,
|
|
267
|
-
source: 'file',
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
it('should return false for a child of an untrusted folder', () => {
|
|
271
|
-
mockCwd = '/home/user/untrusted/src';
|
|
272
|
-
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
|
273
|
-
expect(isWorkspaceTrusted(mockSettings).isTrusted).toBe(false);
|
|
274
|
-
});
|
|
275
|
-
it('should return undefined when no rules match', () => {
|
|
276
|
-
mockCwd = '/home/user/other';
|
|
277
|
-
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
|
278
|
-
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
|
279
|
-
expect(isWorkspaceTrusted(mockSettings).isTrusted).toBeUndefined();
|
|
280
|
-
});
|
|
281
|
-
it('should prioritize specific distrust over parent trust', () => {
|
|
282
|
-
mockCwd = '/home/user/projectA/untrusted';
|
|
283
|
-
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
|
284
|
-
mockRules['/home/user/projectA/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
|
285
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
286
|
-
isTrusted: false,
|
|
287
|
-
source: 'file',
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
it('should use workspaceDir instead of process.cwd() when provided', () => {
|
|
291
|
-
mockCwd = '/home/user/untrusted';
|
|
292
|
-
const workspaceDir = '/home/user/projectA';
|
|
293
|
-
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
|
294
|
-
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
|
295
|
-
// process.cwd() is untrusted, but workspaceDir is trusted
|
|
296
|
-
expect(isWorkspaceTrusted(mockSettings, workspaceDir)).toEqual({
|
|
297
|
-
isTrusted: true,
|
|
298
|
-
source: 'file',
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
it('should handle path normalization', () => {
|
|
302
|
-
mockCwd = '/home/user/projectA';
|
|
303
|
-
mockRules[`/home/user/../user/${path.basename('/home/user/projectA')}`] =
|
|
304
|
-
TrustLevel.TRUST_FOLDER;
|
|
305
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
306
|
-
isTrusted: true,
|
|
307
|
-
source: 'file',
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
describe('isWorkspaceTrusted with IDE override', () => {
|
|
312
|
-
const mockCwd = '/home/user/projectA';
|
|
313
|
-
beforeEach(() => {
|
|
314
|
-
resetTrustedFoldersForTesting();
|
|
315
|
-
vi.spyOn(process, 'cwd').mockImplementation(() => mockCwd);
|
|
316
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p.toString());
|
|
317
|
-
vi.spyOn(fs, 'existsSync').mockImplementation((p) => p.toString().endsWith('trustedFolders.json') ? false : true);
|
|
318
|
-
});
|
|
319
|
-
afterEach(() => {
|
|
320
|
-
vi.clearAllMocks();
|
|
321
|
-
ideContextStore.clear();
|
|
322
|
-
resetTrustedFoldersForTesting();
|
|
323
|
-
});
|
|
324
|
-
const mockSettings = {
|
|
325
|
-
security: {
|
|
326
|
-
folderTrust: {
|
|
327
|
-
enabled: true,
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
it('should return true when ideTrust is true, ignoring config', () => {
|
|
332
|
-
ideContextStore.set({ workspaceState: { isTrusted: true } });
|
|
333
|
-
// Even if config says don't trust, ideTrust should win.
|
|
334
|
-
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ [process.cwd()]: TrustLevel.DO_NOT_TRUST }));
|
|
335
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
336
|
-
isTrusted: true,
|
|
337
|
-
source: 'ide',
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
it('should return false when ideTrust is false, ignoring config', () => {
|
|
341
|
-
ideContextStore.set({ workspaceState: { isTrusted: false } });
|
|
342
|
-
// Even if config says trust, ideTrust should win.
|
|
343
|
-
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ [process.cwd()]: TrustLevel.TRUST_FOLDER }));
|
|
344
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
345
|
-
isTrusted: false,
|
|
346
|
-
source: 'ide',
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
it('should fall back to config when ideTrust is undefined', () => {
|
|
350
|
-
vi.spyOn(fs, 'existsSync').mockImplementation((p) => p === getTrustedFoldersPath() || p === mockCwd ? true : false);
|
|
351
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
352
|
-
if (p === getTrustedFoldersPath()) {
|
|
353
|
-
return JSON.stringify({ [mockCwd]: TrustLevel.TRUST_FOLDER });
|
|
354
|
-
}
|
|
355
|
-
return '{}';
|
|
356
|
-
});
|
|
357
|
-
expect(isWorkspaceTrusted(mockSettings)).toEqual({
|
|
358
|
-
isTrusted: true,
|
|
359
|
-
source: 'file',
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
it('should always return true if folderTrust setting is disabled', () => {
|
|
363
|
-
const settings = {
|
|
364
|
-
security: {
|
|
365
|
-
folderTrust: {
|
|
366
|
-
enabled: false,
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
ideContextStore.set({ workspaceState: { isTrusted: false } });
|
|
371
|
-
expect(isWorkspaceTrusted(settings)).toEqual({
|
|
372
|
-
isTrusted: true,
|
|
373
|
-
source: undefined,
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
describe('Trusted Folders Caching', () => {
|
|
378
|
-
beforeEach(() => {
|
|
379
|
-
resetTrustedFoldersForTesting();
|
|
380
|
-
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
381
|
-
vi.spyOn(fs, 'readFileSync').mockReturnValue('{}');
|
|
382
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p.toString());
|
|
383
|
-
});
|
|
384
|
-
afterEach(() => {
|
|
385
|
-
vi.restoreAllMocks();
|
|
386
|
-
});
|
|
387
|
-
it('should cache the loaded folders object', () => {
|
|
388
|
-
const readSpy = vi.spyOn(fs, 'readFileSync');
|
|
389
|
-
// First call should read the file
|
|
390
|
-
loadTrustedFolders();
|
|
391
|
-
expect(readSpy).toHaveBeenCalledTimes(1);
|
|
392
|
-
// Second call should use the cache
|
|
393
|
-
loadTrustedFolders();
|
|
394
|
-
expect(readSpy).toHaveBeenCalledTimes(1);
|
|
395
|
-
// Resetting should clear the cache
|
|
396
|
-
resetTrustedFoldersForTesting();
|
|
397
|
-
// Third call should read the file again
|
|
398
|
-
loadTrustedFolders();
|
|
399
|
-
expect(readSpy).toHaveBeenCalledTimes(2);
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
describe('invalid trust levels', () => {
|
|
403
|
-
const mockCwd = '/user/folder';
|
|
404
|
-
const mockRules = {};
|
|
405
|
-
beforeEach(() => {
|
|
406
|
-
resetTrustedFoldersForTesting();
|
|
407
|
-
vi.spyOn(process, 'cwd').mockImplementation(() => mockCwd);
|
|
408
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p.toString());
|
|
409
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
410
|
-
if (p.toString() === getTrustedFoldersPath()) {
|
|
411
|
-
return JSON.stringify(mockRules);
|
|
412
|
-
}
|
|
413
|
-
return '{}';
|
|
414
|
-
});
|
|
415
|
-
vi.spyOn(fs, 'existsSync').mockImplementation((p) => p.toString() === getTrustedFoldersPath() || p.toString() === mockCwd);
|
|
416
|
-
});
|
|
417
|
-
afterEach(() => {
|
|
418
|
-
vi.restoreAllMocks();
|
|
419
|
-
// Clear the object
|
|
420
|
-
Object.keys(mockRules).forEach((key) => delete mockRules[key]);
|
|
421
|
-
});
|
|
422
|
-
it('should create a comprehensive error message for invalid trust level', () => {
|
|
423
|
-
mockRules[mockCwd] = 'INVALID_TRUST_LEVEL';
|
|
424
|
-
const { errors } = loadTrustedFolders();
|
|
425
|
-
const possibleValues = Object.values(TrustLevel).join(', ');
|
|
426
|
-
expect(errors.length).toBe(1);
|
|
427
|
-
expect(errors[0].message).toBe(`Invalid trust level "INVALID_TRUST_LEVEL" for path "${mockCwd}". Possible values are: ${possibleValues}.`);
|
|
428
|
-
});
|
|
429
|
-
it('should throw a fatal error for invalid trust level', () => {
|
|
192
|
+
describe('isWorkspaceTrusted Integration', () => {
|
|
430
193
|
const mockSettings = {
|
|
431
194
|
security: {
|
|
432
195
|
folderTrust: {
|
|
@@ -434,193 +197,83 @@ describe('invalid trust levels', () => {
|
|
|
434
197
|
},
|
|
435
198
|
},
|
|
436
199
|
};
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
beforeEach(() => {
|
|
445
|
-
vi.stubEnv('GEMINI_API_KEY', '');
|
|
446
|
-
resetTrustedFoldersForTesting();
|
|
447
|
-
vi.spyOn(process, 'cwd').mockImplementation(() => mockCwd);
|
|
448
|
-
vi.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
449
|
-
if (p === getTrustedFoldersPath()) {
|
|
450
|
-
return JSON.stringify(mockRules);
|
|
451
|
-
}
|
|
452
|
-
if (p === path.resolve(mockCwd, '.env')) {
|
|
453
|
-
return 'GEMINI_API_KEY=shhh-secret';
|
|
454
|
-
}
|
|
455
|
-
return '{}';
|
|
200
|
+
it('should return true for a directly trusted folder', () => {
|
|
201
|
+
const config = { '/projectA': TrustLevel.TRUST_FOLDER };
|
|
202
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
203
|
+
expect(isWorkspaceTrusted(mockSettings, '/projectA')).toEqual({
|
|
204
|
+
isTrusted: true,
|
|
205
|
+
source: 'file',
|
|
206
|
+
});
|
|
456
207
|
});
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// 1. Mock untrusted workspace
|
|
465
|
-
mockCwd = '/home/user/untrusted';
|
|
466
|
-
mockRules[mockCwd] = TrustLevel.DO_NOT_TRUST;
|
|
467
|
-
// 2. Load environment (should return early)
|
|
468
|
-
const settings = createMockSettings({
|
|
469
|
-
security: { folderTrust: { enabled: true } },
|
|
208
|
+
it('should return false for a directly untrusted folder', () => {
|
|
209
|
+
const config = { '/untrusted': TrustLevel.DO_NOT_TRUST };
|
|
210
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
211
|
+
expect(isWorkspaceTrusted(mockSettings, '/untrusted')).toEqual({
|
|
212
|
+
isTrusted: false,
|
|
213
|
+
source: 'file',
|
|
214
|
+
});
|
|
470
215
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// 4. Verify validateAuthMethod fails
|
|
475
|
-
const result = validateAuthMethod(AuthType.USE_GEMINI);
|
|
476
|
-
expect(result).toContain('you must specify the GEMINI_API_KEY environment variable');
|
|
477
|
-
});
|
|
478
|
-
it('should identify if sandbox flag is available in Settings', () => {
|
|
479
|
-
const schema = getSettingsSchema();
|
|
480
|
-
expect(schema.tools.properties).toBeDefined();
|
|
481
|
-
expect('sandbox' in schema.tools.properties).toBe(true);
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
describe('Trusted Folders realpath caching', () => {
|
|
485
|
-
beforeEach(() => {
|
|
486
|
-
resetTrustedFoldersForTesting();
|
|
487
|
-
vi.resetAllMocks();
|
|
488
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p.toString());
|
|
489
|
-
});
|
|
490
|
-
afterEach(() => {
|
|
491
|
-
vi.restoreAllMocks();
|
|
492
|
-
});
|
|
493
|
-
it('should only call fs.realpathSync once for the same path', () => {
|
|
494
|
-
const mockPath = '/some/path';
|
|
495
|
-
const mockRealPath = '/real/path';
|
|
496
|
-
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
497
|
-
const realpathSpy = vi
|
|
498
|
-
.spyOn(fs, 'realpathSync')
|
|
499
|
-
.mockReturnValue(mockRealPath);
|
|
500
|
-
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
501
|
-
[mockPath]: TrustLevel.TRUST_FOLDER,
|
|
502
|
-
'/another/path': TrustLevel.TRUST_FOLDER,
|
|
503
|
-
}));
|
|
504
|
-
const folders = loadTrustedFolders();
|
|
505
|
-
// Call isPathTrusted multiple times with the same path
|
|
506
|
-
folders.isPathTrusted(mockPath);
|
|
507
|
-
folders.isPathTrusted(mockPath);
|
|
508
|
-
folders.isPathTrusted(mockPath);
|
|
509
|
-
// fs.realpathSync should only be called once for mockPath (at the start of isPathTrusted)
|
|
510
|
-
// And once for each rule in the config (if they are different)
|
|
511
|
-
// Let's check calls for mockPath
|
|
512
|
-
const mockPathCalls = realpathSpy.mock.calls.filter((call) => call[0] === mockPath);
|
|
513
|
-
expect(mockPathCalls.length).toBe(1);
|
|
514
|
-
});
|
|
515
|
-
it('should cache results for rule paths in the loop', () => {
|
|
516
|
-
const rulePath = '/rule/path';
|
|
517
|
-
const locationPath = '/location/path';
|
|
518
|
-
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
519
|
-
const realpathSpy = vi
|
|
520
|
-
.spyOn(fs, 'realpathSync')
|
|
521
|
-
.mockImplementation((p) => p.toString()); // identity for simplicity
|
|
522
|
-
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
|
|
523
|
-
[rulePath]: TrustLevel.TRUST_FOLDER,
|
|
524
|
-
}));
|
|
525
|
-
const folders = loadTrustedFolders();
|
|
526
|
-
// First call
|
|
527
|
-
folders.isPathTrusted(locationPath);
|
|
528
|
-
const firstCallCount = realpathSpy.mock.calls.length;
|
|
529
|
-
expect(firstCallCount).toBe(2); // locationPath and rulePath
|
|
530
|
-
// Second call with same location and same config
|
|
531
|
-
folders.isPathTrusted(locationPath);
|
|
532
|
-
const secondCallCount = realpathSpy.mock.calls.length;
|
|
533
|
-
// Should still be 2 because both were cached
|
|
534
|
-
expect(secondCallCount).toBe(2);
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
describe('isWorkspaceTrusted with Symlinks', () => {
|
|
538
|
-
const mockSettings = {
|
|
539
|
-
security: {
|
|
540
|
-
folderTrust: {
|
|
541
|
-
enabled: true,
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
|
-
};
|
|
545
|
-
beforeEach(() => {
|
|
546
|
-
resetTrustedFoldersForTesting();
|
|
547
|
-
vi.resetAllMocks();
|
|
548
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p.toString());
|
|
549
|
-
});
|
|
550
|
-
afterEach(() => {
|
|
551
|
-
vi.restoreAllMocks();
|
|
552
|
-
});
|
|
553
|
-
it('should trust a folder even if CWD is a symlink and rule is realpath', () => {
|
|
554
|
-
const symlinkPath = '/var/folders/project';
|
|
555
|
-
const realPath = '/private/var/folders/project';
|
|
556
|
-
vi.spyOn(process, 'cwd').mockReturnValue(symlinkPath);
|
|
557
|
-
// Mock fs.existsSync to return true for trust config and both paths
|
|
558
|
-
vi.spyOn(fs, 'existsSync').mockImplementation((p) => {
|
|
559
|
-
const pathStr = p.toString();
|
|
560
|
-
if (pathStr === getTrustedFoldersPath())
|
|
561
|
-
return true;
|
|
562
|
-
if (pathStr === symlinkPath)
|
|
563
|
-
return true;
|
|
564
|
-
if (pathStr === realPath)
|
|
565
|
-
return true;
|
|
566
|
-
return false;
|
|
216
|
+
it('should return undefined when no rules match', () => {
|
|
217
|
+
fs.writeFileSync(trustedFoldersPath, '{}', 'utf-8');
|
|
218
|
+
expect(isWorkspaceTrusted(mockSettings, '/other').isTrusted).toBeUndefined();
|
|
567
219
|
});
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
220
|
+
it('should prioritize IDE override over file config', () => {
|
|
221
|
+
const config = { '/projectA': TrustLevel.DO_NOT_TRUST };
|
|
222
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
223
|
+
ideContextStore.set({ workspaceState: { isTrusted: true } });
|
|
224
|
+
try {
|
|
225
|
+
expect(isWorkspaceTrusted(mockSettings, '/projectA')).toEqual({
|
|
226
|
+
isTrusted: true,
|
|
227
|
+
source: 'ide',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
ideContextStore.clear();
|
|
232
|
+
}
|
|
576
233
|
});
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
234
|
+
it('should always return true if folderTrust setting is disabled', () => {
|
|
235
|
+
const disabledSettings = {
|
|
236
|
+
security: { folderTrust: { enabled: false } },
|
|
237
|
+
};
|
|
238
|
+
expect(isWorkspaceTrusted(disabledSettings, '/any')).toEqual({
|
|
239
|
+
isTrusted: true,
|
|
240
|
+
source: undefined,
|
|
241
|
+
});
|
|
585
242
|
});
|
|
586
|
-
// Should be trusted because both resolve to the same realpath
|
|
587
|
-
expect(isWorkspaceTrusted(mockSettings).isTrusted).toBe(true);
|
|
588
243
|
});
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
return true;
|
|
602
|
-
return false;
|
|
603
|
-
});
|
|
604
|
-
// Mock realpathSync
|
|
605
|
-
vi.spyOn(fs, 'realpathSync').mockImplementation((p) => {
|
|
606
|
-
const pathStr = p.toString();
|
|
607
|
-
if (pathStr === symlinkPath)
|
|
608
|
-
return realPath;
|
|
609
|
-
if (pathStr === realPath)
|
|
610
|
-
return realPath;
|
|
611
|
-
return pathStr;
|
|
244
|
+
describe('Symlinks Support', () => {
|
|
245
|
+
it('should trust a folder if the rule matches the realpath', () => {
|
|
246
|
+
// Create a real directory and a symlink
|
|
247
|
+
const realDir = path.join(tempDir, 'real');
|
|
248
|
+
const symlinkDir = path.join(tempDir, 'symlink');
|
|
249
|
+
fs.mkdirSync(realDir);
|
|
250
|
+
fs.symlinkSync(realDir, symlinkDir);
|
|
251
|
+
// Rule uses realpath
|
|
252
|
+
const config = { [realDir]: TrustLevel.TRUST_FOLDER };
|
|
253
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
254
|
+
// Check against symlink path
|
|
255
|
+
expect(isWorkspaceTrusted(mockSettings, symlinkDir).isTrusted).toBe(true);
|
|
612
256
|
});
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
[symlinkPath]: TrustLevel.TRUST_FOLDER,
|
|
257
|
+
const mockSettings = {
|
|
258
|
+
security: { folderTrust: { enabled: true } },
|
|
616
259
|
};
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
260
|
+
});
|
|
261
|
+
describe('Verification: Auth and Trust Interaction', () => {
|
|
262
|
+
it('should verify loadEnvironment returns early when untrusted', () => {
|
|
263
|
+
const untrustedDir = path.join(tempDir, 'untrusted');
|
|
264
|
+
fs.mkdirSync(untrustedDir);
|
|
265
|
+
const config = { [untrustedDir]: TrustLevel.DO_NOT_TRUST };
|
|
266
|
+
fs.writeFileSync(trustedFoldersPath, JSON.stringify(config), 'utf-8');
|
|
267
|
+
const envPath = path.join(untrustedDir, '.env');
|
|
268
|
+
fs.writeFileSync(envPath, 'GEMINI_API_KEY=secret', 'utf-8');
|
|
269
|
+
vi.stubEnv('GEMINI_API_KEY', '');
|
|
270
|
+
const settings = createMockSettings({
|
|
271
|
+
security: { folderTrust: { enabled: true } },
|
|
272
|
+
});
|
|
273
|
+
loadEnvironment(settings.merged, untrustedDir);
|
|
274
|
+
expect(process.env['GEMINI_API_KEY']).toBe('');
|
|
275
|
+
vi.unstubAllEnvs();
|
|
621
276
|
});
|
|
622
|
-
// Should be trusted because both resolve to the same realpath
|
|
623
|
-
expect(isWorkspaceTrusted(mockSettings).isTrusted).toBe(true);
|
|
624
277
|
});
|
|
625
278
|
});
|
|
626
279
|
//# sourceMappingURL=trustedFolders.test.js.map
|