Skip to content

Commit b0e03aa

Browse files
committed
Harden temporary file generation, hashing, and input validation across modules
- [Pasted text] - image hash should have 16 characters - revert this change [Pasted text] changes: [Changes] - the temporary directory for 'ask' cli invocation should be located in the extension's dedicated dir, not os's temp dir ├── apps │ └── editor │ └── src │ ├── features │ │ └── context-restoration │ │ └── utils │ │ └── global-storage-utils.ts │ ├── utils │ │ └── agentic-cli-invocation │ │ ├── index.ts │ │ └── invoke-agentic-cli.ts │ └── views │ └── prompt │ ├── backend │ │ ├── message-handlers │ │ │ ├── handle-invoke-agentic-cli.ts │ │ │ ├── handle-open-prompt-image.ts │ │ │ ├── handle-open-prompt-pasted-text.ts │ │ │ ├── handle-open-website.ts │ │ │ ├── handle-paste-url.ts │ │ │ ├── handle-preview-changes-symbol.ts │ │ │ ├── handle-preview-commit-symbol.ts │ │ │ ├── handle-preview-prompt.ts │ │ │ ├── handle-preview-saved-context-symbol.ts │ │ │ ├── handle-preview-skill-symbol.ts │ │ │ ├── handle-save-prompt-image.ts │ │ │ ├── handle-save-prompt-pasted-text.ts │ │ │ └── utils │ │ │ └── build-cli-prompt.ts │ │ ├── prompt-view-provider.ts │ │ └── utils │ │ ├── persistent-text-storage.ts │ │ ├── preview-text-in-temp-file.ts │ │ └── symbols │ │ ├── image │ │ │ └── replace-image-symbol.ts │ │ ├── pasted-text │ │ │ └── replace-pasted-text-symbol.ts │ │ └── website │ │ └── replace-website-symbol.ts │ ├── frontend │ │ └── hooks │ │ └── use-prompt │ │ └── use-prompt.ts │ └── types │ └── messages.ts └── packages ├── shared │ └── src │ ├── types │ │ └── prompt-types.ts │ └── utils │ └── simplify-prompt-symbols │ ├── simplify-prompt-symbols.spec.ts │ └── simplify-prompt-symbols.ts └── ui └── src └── components └── editor └── common └── prompts ├── PromptField │ └── hooks │ └── use-handlers │ └── handle-input-click.ts └── shared └── symbols ├── get-highlighted-text.tsx ├── position-mapping.ts ├── reconstruct-raw-value.ts └── use-symbol-deletion.ts
1 parent 7fa7f8d commit b0e03aa

5 files changed

Lines changed: 62 additions & 19 deletions

File tree

‎apps/editor/src/utils/agentic-cli-invocation/invoke-agentic-cli.ts‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,16 @@ export const invoke_agentic_cli = async (params: {
368368
if (params.isolate_in_temp_dir) {
369369
if (params.copy_selected_files) {
370370
temp_dir_path = await fs.promises.mkdtemp(
371-
path.join(os.tmpdir(), 'cwc-cli-')
371+
path.join(os.tmpdir(), 'cwc-cli-edit-')
372372
)
373373
} else {
374-
temp_dir_path = path.join(os.tmpdir(), 'cwc-cli')
374+
// Reuse directory to avoid repeated permission asks
375+
const global_storage_path =
376+
params.extension_context.globalStorageUri.fsPath
377+
if (!fs.existsSync(global_storage_path)) {
378+
await fs.promises.mkdir(global_storage_path, { recursive: true })
379+
}
380+
temp_dir_path = path.join(global_storage_path, 'cwc-cli-ask')
375381
await fs.promises.rm(temp_dir_path, { recursive: true, force: true })
376382
await fs.promises.mkdir(temp_dir_path, { recursive: true })
377383
}
@@ -547,11 +553,14 @@ export const invoke_agentic_cli = async (params: {
547553
return undefined
548554
}
549555

550-
const temp_prompt_path = path.join(
551-
os.tmpdir(),
552-
`cwc-prompt-${Date.now()}-${Math.floor(Math.random() * 1000)}.md`
556+
const temp_prompt_dir = await fs.promises.mkdtemp(
557+
path.join(os.tmpdir(), 'cwc-prompt-')
553558
)
554-
await fs.promises.writeFile(temp_prompt_path, final_prompt, 'utf-8')
559+
const temp_prompt_path = path.join(temp_prompt_dir, 'prompt.md')
560+
await fs.promises.writeFile(temp_prompt_path, final_prompt, {
561+
encoding: 'utf-8',
562+
mode: 0o600
563+
})
555564

556565
if (execution_mode === 'interactive-terminal') {
557566
const base_args = agent_info.get_integrated_terminal_args
@@ -598,6 +607,12 @@ export const invoke_agentic_cli = async (params: {
598607
terminal.sendText(post_command_args)
599608
}
600609

610+
setTimeout(() => {
611+
fs.promises
612+
.rm(temp_prompt_dir, { recursive: true, force: true })
613+
.catch(() => {})
614+
}, 10000)
615+
601616
return { agent_output: '', selected_root: selected_root! }
602617
}
603618

@@ -785,6 +800,10 @@ export const invoke_agentic_cli = async (params: {
785800
)
786801
go_back_to_caller = true
787802
break
803+
} finally {
804+
await fs.promises
805+
.rm(temp_prompt_dir, { recursive: true, force: true })
806+
.catch(() => {})
788807
}
789808

790809
if (cancelled_count === invocation_count) {

‎apps/editor/src/views/prompt/backend/message-handlers/handle-open-prompt-image.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export const handle_open_prompt_image = async (
99
) => {
1010
try {
1111
const hash = message.hash
12+
if (!/^[a-fA-F0-9]{16,64}$/.test(hash)) {
13+
throw new Error('Invalid image hash')
14+
}
1215
const txt_filename = `cwc-image-${hash}.txt`
1316
const txt_path = path.join(os.tmpdir(), txt_filename)
1417

@@ -29,10 +32,13 @@ export const handle_open_prompt_image = async (
2932
const buffer = Buffer.from(content_base64, 'base64')
3033

3134
// Create a png file for viewing
35+
const png_dir = await fs.promises.mkdtemp(
36+
path.join(os.tmpdir(), 'cwc-image-')
37+
)
3238
const png_filename = `cwc-image-${hash}.png`
33-
const png_path = path.join(os.tmpdir(), png_filename)
39+
const png_path = path.join(png_dir, png_filename)
3440

35-
await fs.promises.writeFile(png_path, buffer)
41+
await fs.promises.writeFile(png_path, buffer, { mode: 0o600 })
3642

3743
// Open the image
3844
const uri = vscode.Uri.file(png_path)

‎apps/editor/src/views/prompt/backend/message-handlers/handle-save-prompt-image.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export const handle_save_prompt_image = async (
1212
) => {
1313
try {
1414
const buffer = Buffer.from(message.content_base64, 'base64')
15-
const hash = crypto.createHash('md5').update(buffer).digest('hex')
15+
const hash = crypto
16+
.createHash('sha256')
17+
.update(buffer)
18+
.digest('hex')
19+
.substring(0, 16)
1620
const filename = `cwc-image-${hash}.txt`
1721
const file_path = path.join(os.tmpdir(), filename)
1822
await fs.promises.writeFile(file_path, message.content_base64, 'utf-8')

‎apps/editor/src/views/prompt/backend/utils/persistent-text-storage.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,18 @@ export class PersistentTextStorage {
4040
}
4141

4242
async load(id: string): Promise<string> {
43+
if (!/^[a-fA-F0-9]{16,64}$/.test(id)) {
44+
throw new Error('Invalid storage ID')
45+
}
4346
const filename = `${id}.txt`
4447
const file_path = path.join(this.dir_path, filename)
4548
return await fs.promises.readFile(file_path, 'utf-8')
4649
}
4750

4851
get_file_path(id: string): string {
52+
if (!/^[a-fA-F0-9]{16,64}$/.test(id)) {
53+
throw new Error('Invalid storage ID')
54+
}
4955
const filename = `${id}.txt`
5056
return path.join(this.dir_path, filename)
5157
}

‎apps/editor/src/views/prompt/backend/utils/preview-text-in-temp-file.ts‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as crypto from 'crypto'
21
import * as fs from 'fs'
32
import * as os from 'os'
43
import * as path from 'path'
@@ -10,18 +9,27 @@ export const preview_text_in_temp_file = async (params: {
109
content: string
1110
extension?: string
1211
}): Promise<void> => {
13-
const hash = crypto
14-
.createHash('md5')
15-
.update(`${Date.now()}-${Math.random()}`)
16-
.digest('hex')
1712
const ext = params.extension || '.txt'
18-
const temp_file_path = path.join(
19-
os.tmpdir(),
20-
`${params.prefix}-${hash}${ext}`
21-
)
13+
let temp_dir_path: string
2214

2315
try {
24-
await fs.promises.writeFile(temp_file_path, params.content, 'utf8')
16+
temp_dir_path = await fs.promises.mkdtemp(
17+
path.join(os.tmpdir(), 'cwc-preview-')
18+
)
19+
} catch (error) {
20+
vscode.window.showErrorMessage(
21+
'Failed to create temporary directory for preview.'
22+
)
23+
return
24+
}
25+
26+
const temp_file_path = path.join(temp_dir_path, `${params.prefix}${ext}`)
27+
28+
try {
29+
await fs.promises.writeFile(temp_file_path, params.content, {
30+
encoding: 'utf8',
31+
mode: 0o600
32+
})
2533
} catch (error) {
2634
vscode.window.showErrorMessage(
2735
'Failed to create temporary file for preview.'

0 commit comments

Comments
 (0)