Skip to content

Commit f7eca8d

Browse files
committed
Restrict the file access to only current workspaces directory
1 parent aefe920 commit f7eca8d

File tree

10 files changed

+112
-14
lines changed

10 files changed

+112
-14
lines changed

packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ export class Connector extends BaseConnector {
325325

326326
if (
327327
!this.onChatAnswerUpdated ||
328-
!['accept-code-diff', 'reject-code-diff', 'confirm-tool-use'].includes(action.id)
328+
!['accept-code-diff', 'reject-code-diff', 'confirm-tool-use', 'reject-tool-use'].includes(action.id)
329329
) {
330330
return
331331
}
@@ -375,6 +375,18 @@ export class Connector extends BaseConnector {
375375
},
376376
]
377377
break
378+
case 'reject-tool-use':
379+
answer.buttons = [
380+
{
381+
keepCardAfterClick: true,
382+
text: 'Rejected',
383+
id: 'rejected-tool-use',
384+
status: 'error',
385+
position: 'outside',
386+
disabled: true,
387+
},
388+
]
389+
break
378390
default:
379391
break
380392
}

packages/core/src/codewhisperer/client/codewhisperer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export interface CodeWhispererConfig {
3131
}
3232

3333
export const defaultServiceConfig: CodeWhispererConfig = {
34-
region: 'us-east-1',
35-
endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/',
34+
region: 'us-west-2',
35+
endpoint: 'https://rts.alpha-us-west-2.codewhisperer.ai.aws.dev/',
3636
}
3737

3838
export function getCodewhispererConfig(): CodeWhispererConfig {

packages/core/src/codewhispererChat/controllers/chat/controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ export class ChatController {
762762
await this.processToolUseMessage(message)
763763
break
764764
case 'reject-code-diff':
765+
case 'reject-tool-use':
765766
await this.closeDiffView()
766767
break
767768
default:

packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { ChatHistoryManager } from '../../../storages/chatHistory'
4545
import { ToolType, ToolUtils } from '../../../tools/toolUtils'
4646
import { ChatStream } from '../../../tools/chatStream'
4747
import path from 'path'
48-
import { CommandValidation } from '../../../tools/executeBash'
48+
import { CommandValidation } from '../../../tools/toolShared'
4949
import { Change } from 'diff'
5050
import { FsWriteParams } from '../../../tools/fsWrite'
5151

@@ -499,6 +499,17 @@ export class Messenger {
499499
if (validation.warning) {
500500
message = validation.warning + message
501501
}
502+
} else if (validation.requiresAcceptance && toolUse?.name === ToolType.ListDirectory) {
503+
buttons.push({
504+
id: 'reject-tool-use',
505+
text: 'Reject',
506+
status: 'info',
507+
})
508+
buttons.push({
509+
id: 'confirm-tool-use',
510+
text: 'Confirm',
511+
status: 'info',
512+
})
502513
} else if (toolUse?.name === ToolType.FsWrite) {
503514
const input = toolUse.input as unknown as FsWriteParams
504515
const fileName = path.basename(input.path)

packages/core/src/codewhispererChat/tools/chatStream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Writable } from 'stream'
77
import { getLogger } from '../../shared/logger/logger'
88
import { Messenger } from '../controllers/chat/messenger/messenger'
99
import { ToolUse } from '@amzn/codewhisperer-streaming'
10-
import { CommandValidation } from './executeBash'
10+
import { CommandValidation } from './toolShared'
1111
import { Change } from 'diff'
1212

1313
/**

packages/core/src/codewhispererChat/tools/executeBash.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Writable } from 'stream'
77
import { getLogger } from '../../shared/logger/logger'
88
import { fs } from '../../shared/fs/fs'
99
import { ChildProcess, ChildProcessOptions } from '../../shared/utilities/processUtils'
10-
import { InvokeOutput, OutputKind, sanitizePath } from './toolShared'
10+
import { CommandValidation, InvokeOutput, OutputKind, sanitizePath } from './toolShared'
1111
import { split } from 'shlex'
1212

1313
export enum CommandCategory {
@@ -126,11 +126,6 @@ export interface ExecuteBashParams {
126126
cwd?: string
127127
}
128128

129-
export interface CommandValidation {
130-
requiresAcceptance: boolean
131-
warning?: string
132-
}
133-
134129
export class ExecuteBash {
135130
private readonly command: string
136131
private readonly workingDirectory?: string

packages/core/src/codewhispererChat/tools/listDirectory.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import * as vscode from 'vscode'
66
import { getLogger } from '../../shared/logger/logger'
77
import { readDirectoryRecursively } from '../../shared/utilities/workspaceUtils'
88
import fs from '../../shared/fs/fs'
9-
import { InvokeOutput, OutputKind, sanitizePath } from './toolShared'
9+
import { CommandValidation, InvokeOutput, OutputKind, sanitizePath } from './toolShared'
10+
import { isInDirectory } from '../../shared/filesystemUtilities'
1011
import { Writable } from 'stream'
1112
import path from 'path'
1213

@@ -61,6 +62,24 @@ export class ListDirectory {
6162
updates.end()
6263
}
6364

65+
public requiresAcceptance(): CommandValidation {
66+
// Check if the path is within any of the workspace folders
67+
const workspaceFolders = vscode.workspace.workspaceFolders
68+
69+
if (!workspaceFolders || workspaceFolders.length === 0) {
70+
return { requiresAcceptance: true }
71+
}
72+
73+
// Check if the path is within any workspace folder
74+
const isInWorkspace = workspaceFolders.some((folder) => isInDirectory(folder.uri.fsPath, this.fsPath))
75+
76+
if (!isInWorkspace) {
77+
return { requiresAcceptance: true }
78+
}
79+
80+
return { requiresAcceptance: false }
81+
}
82+
6483
public async invoke(updates?: Writable): Promise<InvokeOutput> {
6584
try {
6685
const fileUri = vscode.Uri.file(this.fsPath)

packages/core/src/codewhispererChat/tools/toolShared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export interface InvokeOutput {
2121
}
2222
}
2323

24+
export interface CommandValidation {
25+
requiresAcceptance: boolean
26+
warning?: string
27+
}
28+
2429
export function sanitizePath(inputPath: string): string {
2530
let sanitized = inputPath.trim()
2631

packages/core/src/codewhispererChat/tools/toolUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import { Writable } from 'stream'
66
import { FsRead, FsReadParams } from './fsRead'
77
import { FsWrite, FsWriteParams } from './fsWrite'
8-
import { CommandValidation, ExecuteBash, ExecuteBashParams } from './executeBash'
8+
import { ExecuteBash, ExecuteBashParams } from './executeBash'
99
import { ToolResult, ToolResultContentBlock, ToolResultStatus, ToolUse } from '@amzn/codewhisperer-streaming'
1010
import { InvokeOutput } from './toolShared'
1111
import { ListDirectory, ListDirectoryParams } from './listDirectory'
12+
import { CommandValidation } from './toolShared'
1213

1314
export enum ToolType {
1415
FsRead = 'fsRead',
@@ -46,7 +47,7 @@ export class ToolUtils {
4647
case ToolType.ExecuteBash:
4748
return tool.tool.requiresAcceptance()
4849
case ToolType.ListDirectory:
49-
return { requiresAcceptance: false }
50+
return tool.tool.requiresAcceptance()
5051
}
5152
}
5253

packages/core/src/test/codewhispererChat/tools/listDirectory.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ import assert from 'assert'
66
import { ListDirectory } from '../../../codewhispererChat/tools/listDirectory'
77
import { TestFolder } from '../../testUtil'
88
import path from 'path'
9+
import * as vscode from 'vscode'
10+
import * as sinon from 'sinon'
911

1012
describe('ListDirectory Tool', () => {
1113
let testFolder: TestFolder
14+
let workspaceFoldersStub: sinon.SinonStub
1215

1316
before(async () => {
1417
testFolder = await TestFolder.create()
18+
// Initialize the stub
19+
workspaceFoldersStub = sinon.stub(vscode.workspace, 'workspaceFolders')
20+
})
21+
22+
after(() => {
23+
// Restore the stub after tests
24+
workspaceFoldersStub.restore()
1525
})
1626

1727
it('throws if path is empty', async () => {
@@ -86,4 +96,48 @@ describe('ListDirectory Tool', () => {
8696
assert.strictEqual(result.output.kind, 'text')
8797
assert.ok(result.output.content.length > 0)
8898
})
99+
100+
it('set requiresAcceptance=true if path is outside workspace', () => {
101+
// Mock empty workspace folders
102+
workspaceFoldersStub.value([])
103+
104+
const listDirectory = new ListDirectory({ path: '/some/outside/path' })
105+
const validation = listDirectory.requiresAcceptance()
106+
107+
assert.strictEqual(validation.requiresAcceptance, true, 'Should require acceptance for path outside workspace')
108+
})
109+
110+
it('set requiresAcceptance=true if path is not within any workspace folder', () => {
111+
// Mock workspace folders that don't contain the target path
112+
workspaceFoldersStub.value([
113+
{ uri: { fsPath: '/workspace/folder1' } },
114+
{ uri: { fsPath: '/workspace/folder2' } },
115+
])
116+
117+
const listDirectory = new ListDirectory({ path: '/some/outside/path' })
118+
const validation = listDirectory.requiresAcceptance()
119+
120+
assert.strictEqual(
121+
validation.requiresAcceptance,
122+
true,
123+
'Should require acceptance for path outside workspace folders'
124+
)
125+
})
126+
127+
it('set requiresAcceptance=false if path is within a workspace folder', () => {
128+
// Mock workspace folders with one containing the target path
129+
const workspacePath = '/workspace/folder1'
130+
const targetPath = `${workspacePath}/subfolder`
131+
132+
workspaceFoldersStub.value([{ uri: { fsPath: workspacePath } }, { uri: { fsPath: '/workspace/folder2' } }])
133+
134+
const listDirectory = new ListDirectory({ path: targetPath })
135+
const validation = listDirectory.requiresAcceptance()
136+
137+
assert.strictEqual(
138+
validation.requiresAcceptance,
139+
false,
140+
'Should not require acceptance for path within workspace folder'
141+
)
142+
})
89143
})

0 commit comments

Comments
 (0)