Skip to content

Commit c2d9b2d

Browse files
committed
Replace shell-based git execution with execFile to avoid command injection
- Refactor `apps/editor/src/features/checkpoints/utils/git-utils.ts` to prevent shell injection by replacing `child_process.exec` with `child_process.execFile`, updating `exec_async` to accept a signature of `(command: string, args: string[], options: any)`, and rewriting all git commands in the file to pass arguments safely as an array (specifically changing the untracked files diff to pass `['diff', '--no-index', '--binary', '--', '/dev/null', file]`). - [Pasted text] └── apps └── editor └── src ├── commands │ ├── apply-response-command │ │ ├── apply-response-command.ts │ │ ├── response-processor │ │ │ └── handlers │ │ │ ├── fast-replace-handler.ts │ │ │ ├── search-replace-handler.ts │ │ │ └── truncated-handler.ts │ │ └── utils │ │ └── file-operations │ │ └── create-file-if-needed.ts │ └── history-command │ └── actions │ ├── create-temporary-checkpoint.ts │ └── restore-checkpoint.ts ├── features │ └── checkpoints │ ├── actions │ │ └── create-checkpoint.ts │ └── utils │ ├── git-utils.ts │ └── index.ts └── utils └── path-sanitizer.ts
1 parent 555a0a7 commit c2d9b2d

2 files changed

Lines changed: 59 additions & 33 deletions

File tree

‎apps/editor/src/commands/history-command/actions/restore-checkpoint.ts‎

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { execSync } from 'child_process'
1+
import { execFileSync } from 'child_process'
22
import * as os from 'os'
33
import * as path from 'path'
44
import * as vscode from 'vscode'
@@ -182,11 +182,11 @@ export const restore_checkpoint = async (params: {
182182
current_git_info.commit_hash == git_info.commit_hash
183183
) {
184184
try {
185-
execSync(`git restore .`, {
185+
execFileSync('git', ['restore', '.'], {
186186
cwd: folder.uri.fsPath,
187187
stdio: 'pipe'
188188
})
189-
execSync(`git clean -fd`, {
189+
execFileSync('git', ['clean', '-fd'], {
190190
cwd: folder.uri.fsPath,
191191
stdio: 'pipe'
192192
})
@@ -197,11 +197,11 @@ export const restore_checkpoint = async (params: {
197197
data: error
198198
})
199199
try {
200-
execSync(`git checkout .`, {
200+
execFileSync('git', ['checkout', '.'], {
201201
cwd: folder.uri.fsPath,
202202
stdio: 'pipe'
203203
})
204-
execSync(`git clean -fd`, {
204+
execFileSync('git', ['clean', '-fd'], {
205205
cwd: folder.uri.fsPath,
206206
stdio: 'pipe'
207207
})
@@ -245,17 +245,24 @@ export const restore_checkpoint = async (params: {
245245
Buffer.from(diff, 'utf8')
246246
)
247247
try {
248-
execSync(
249-
`git apply --whitespace=nowarn "${temp_diff_path}"`,
248+
execFileSync(
249+
'git',
250+
['apply', '--whitespace=nowarn', temp_diff_path],
250251
{
251252
cwd: folder.uri.fsPath,
252253
stdio: 'pipe'
253254
}
254255
)
255256
} catch (e) {
256257
try {
257-
execSync(
258-
`git apply --reject --whitespace=nowarn "${temp_diff_path}"`,
258+
execFileSync(
259+
'git',
260+
[
261+
'apply',
262+
'--reject',
263+
'--whitespace=nowarn',
264+
temp_diff_path
265+
],
259266
{
260267
cwd: folder.uri.fsPath,
261268
stdio: 'pipe'
@@ -308,13 +315,13 @@ export const restore_checkpoint = async (params: {
308315
GIT_WORK_TREE: temp_git_clone_path
309316
}
310317

311-
execSync(`git read-tree "${git_info.commit_hash}"`, {
318+
execFileSync('git', ['read-tree', git_info.commit_hash], {
312319
cwd: folder.uri.fsPath,
313320
env: git_env,
314321
stdio: 'pipe'
315322
})
316323

317-
execSync(`git checkout-index -a -f`, {
324+
execFileSync('git', ['checkout-index', '-a', '-f'], {
318325
cwd: folder.uri.fsPath,
319326
env: git_env,
320327
stdio: 'pipe'
@@ -343,17 +350,24 @@ export const restore_checkpoint = async (params: {
343350
Buffer.from(diff, 'utf8')
344351
)
345352
try {
346-
execSync(
347-
`git apply --whitespace=nowarn "${temp_diff_path}"`,
353+
execFileSync(
354+
'git',
355+
['apply', '--whitespace=nowarn', temp_diff_path],
348356
{
349357
cwd: temp_git_clone_path,
350358
stdio: 'pipe'
351359
}
352360
)
353361
} catch (e) {
354362
try {
355-
execSync(
356-
`git apply --reject --whitespace=nowarn "${temp_diff_path}"`,
363+
execFileSync(
364+
'git',
365+
[
366+
'apply',
367+
'--reject',
368+
'--whitespace=nowarn',
369+
temp_diff_path
370+
],
357371
{
358372
cwd: temp_git_clone_path,
359373
stdio: 'pipe'

‎apps/editor/src/features/checkpoints/utils/git-utils.ts‎

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import * as vscode from 'vscode'
2-
import { exec } from 'child_process'
2+
import { execFile } from 'child_process'
33
import { Logger } from '@shared/utils/logger'
44

5-
const exec_async = (command: string, options: any): Promise<string> => {
5+
const exec_async = (
6+
command: string,
7+
args: string[],
8+
options: any
9+
): Promise<string> => {
610
return new Promise((resolve, reject) => {
7-
exec(command, options, (error, stdout, stderr) => {
11+
execFile(command, args, options, (error, stdout, stderr) => {
812
if (error) {
913
;(error as any).stdout = stdout
1014
;(error as any).stderr = stderr
@@ -28,14 +32,14 @@ export const get_git_info = async (
2832
): Promise<GitInfo | null> => {
2933
try {
3034
const branch = (
31-
await exec_async('git rev-parse --abbrev-ref HEAD', {
35+
await exec_async('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
3236
cwd: workspace_folder.uri.fsPath,
3337
encoding: 'utf8'
3438
})
3539
).trim()
3640

3741
const commit_hash = (
38-
await exec_async('git rev-parse HEAD', {
42+
await exec_async('git', ['rev-parse', 'HEAD'], {
3943
cwd: workspace_folder.uri.fsPath,
4044
encoding: 'utf8'
4145
})
@@ -60,11 +64,15 @@ export const get_git_diff = async (
6064
let total_diff = ''
6165

6266
try {
63-
const tracked_diff = await exec_async('git diff --binary HEAD', {
64-
cwd,
65-
encoding: 'utf8',
66-
maxBuffer: 50 * 1024 * 1024
67-
})
67+
const tracked_diff = await exec_async(
68+
'git',
69+
['diff', '--binary', 'HEAD'],
70+
{
71+
cwd,
72+
encoding: 'utf8',
73+
maxBuffer: 50 * 1024 * 1024
74+
}
75+
)
6876
if (tracked_diff) {
6977
total_diff += tracked_diff
7078
}
@@ -77,7 +85,8 @@ export const get_git_diff = async (
7785
}
7886

7987
const untracked_files_output = await exec_async(
80-
'git ls-files -z --others --exclude-standard',
88+
'git',
89+
['ls-files', '-z', '--others', '--exclude-standard'],
8190
{
8291
cwd,
8392
encoding: 'utf8',
@@ -90,12 +99,15 @@ export const get_git_diff = async (
9099

91100
const process_untracked_file = async (file: string) => {
92101
try {
93-
const cmd = `git diff --no-index --binary /dev/null "${file}"`
94-
const diff_chunk = await exec_async(cmd, {
95-
cwd,
96-
encoding: 'utf8',
97-
maxBuffer: 50 * 1024 * 1024
98-
})
102+
const diff_chunk = await exec_async(
103+
'git',
104+
['diff', '--no-index', '--binary', '--', '/dev/null', file],
105+
{
106+
cwd,
107+
encoding: 'utf8',
108+
maxBuffer: 50 * 1024 * 1024
109+
}
110+
)
99111
return diff_chunk
100112
} catch (err) {
101113
if ((err as any).status === 1 && (err as any).stdout) {
@@ -137,7 +149,7 @@ export const is_git_repository = async (
137149
workspace_folder: vscode.WorkspaceFolder
138150
): Promise<boolean> => {
139151
try {
140-
await exec_async('git rev-parse --git-dir', {
152+
await exec_async('git', ['rev-parse', '--git-dir'], {
141153
cwd: workspace_folder.uri.fsPath
142154
})
143155
return true

0 commit comments

Comments
 (0)