feat: Enforce unified folder trust for /directory add (#17359)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Gal Zahavi
2026-01-23 21:31:42 -08:00
committed by GitHub
parent 3c2482a084
commit 0242a3dc56
4 changed files with 123 additions and 56 deletions
@@ -278,6 +278,21 @@ describe('directoryCommand', () => {
expect(getDirectorySuggestions).toHaveBeenCalledWith('s');
expect(results).toEqual(['docs/, src/']);
});
it('should filter out existing directories from suggestions', async () => {
const existingPath = path.resolve(process.cwd(), 'existing');
vi.mocked(mockWorkspaceContext.getDirectories).mockReturnValue([
existingPath,
]);
vi.mocked(getDirectorySuggestions).mockResolvedValue([
'existing/',
'new/',
]);
const results = await completion(mockContext, 'ex');
expect(results).toEqual(['new/']);
});
});
});
@@ -286,10 +301,7 @@ describe('directoryCommand', () => {
beforeEach(() => {
vi.spyOn(trustedFolders, 'isFolderTrustEnabled').mockReturnValue(true);
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
isTrusted: true,
source: 'file',
});
// isWorkspaceTrusted is no longer checked, so we don't need to mock it returning true
mockIsPathTrusted = vi.fn();
const mockLoadedFolders = {
isPathTrusted: mockIsPathTrusted,
@@ -319,20 +331,27 @@ describe('directoryCommand', () => {
]);
});
it('should show an error for an untrusted directory', async () => {
it('should return a custom dialog for an explicitly untrusted directory (upgrade flow)', async () => {
if (!addCommand?.action) throw new Error('No action');
mockIsPathTrusted.mockReturnValue(false);
mockIsPathTrusted.mockReturnValue(false); // DO_NOT_TRUST
const newPath = path.normalize('/home/user/untrusted-project');
await addCommand.action(mockContext, newPath);
const result = await addCommand.action(mockContext, newPath);
expect(mockWorkspaceContext.addDirectories).not.toHaveBeenCalled();
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect(result).toEqual(
expect.objectContaining({
type: MessageType.ERROR,
text: expect.stringContaining('explicitly untrusted'),
type: 'custom_dialog',
component: expect.objectContaining({
type: expect.any(Function), // React component for MultiFolderTrustDialog
}),
}),
);
if (!result) {
throw new Error('Command did not return a result');
}
const component = (result as OpenCustomDialogActionReturn)
.component as React.ReactElement<MultiFolderTrustDialogProps>;
expect(component.props.folders.includes(newPath)).toBeTruthy();
});
it('should return a custom dialog for a directory with undefined trust', async () => {
@@ -357,6 +376,25 @@ describe('directoryCommand', () => {
.component as React.ReactElement<MultiFolderTrustDialogProps>;
expect(component.props.folders.includes(newPath)).toBeTruthy();
});
it('should prompt for directory even if workspace is untrusted', async () => {
if (!addCommand?.action) throw new Error('No action');
// Even if workspace is untrusted, we should still check directory trust
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
isTrusted: false,
source: 'file',
});
mockIsPathTrusted.mockReturnValue(undefined);
const newPath = path.normalize('/home/user/new-project');
const result = await addCommand.action(mockContext, newPath);
expect(result).toEqual(
expect.objectContaining({
type: 'custom_dialog',
}),
);
});
});
it('should correctly expand a Windows-style home directory path', () => {