122 lines
3.5 KiB
JavaScript
122 lines
3.5 KiB
JavaScript
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
|
|
const childProcess = require('child_process')
|
|
const originalSpawn = childProcess.spawn
|
|
|
|
async function withPlatform (platform, run) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(process, 'platform')
|
|
Object.defineProperty(process, 'platform', {
|
|
configurable: true,
|
|
value: platform
|
|
})
|
|
|
|
try {
|
|
await run()
|
|
} finally {
|
|
Object.defineProperty(process, 'platform', descriptor)
|
|
}
|
|
}
|
|
|
|
test.afterEach(() => {
|
|
childProcess.spawn = originalSpawn
|
|
delete require.cache[require.resolve('../../src/app/lib/open-file-with-editor')]
|
|
})
|
|
|
|
test('parseEditorCommand preserves quoted executable and args', () => {
|
|
const { parseEditorCommand } = require('../../src/app/lib/open-file-with-editor')
|
|
const result = parseEditorCommand('"C:\\Program Files\\Notepad++\\notepad++.exe" -multiInst -nosession')
|
|
|
|
assert.equal(result.command, 'C:\\Program Files\\Notepad++\\notepad++.exe')
|
|
assert.deepEqual(result.args, ['-multiInst', '-nosession'])
|
|
})
|
|
|
|
test('openFileWithEditor passes malicious filenames as a literal Windows argument', async () => {
|
|
let spawnCall
|
|
childProcess.spawn = (command, args, options) => {
|
|
spawnCall = { command, args, options }
|
|
|
|
return {
|
|
stderr: {
|
|
on: () => {}
|
|
},
|
|
on: (event, handler) => {
|
|
if (event === 'close') {
|
|
process.nextTick(() => handler(0))
|
|
}
|
|
},
|
|
unref: () => {}
|
|
}
|
|
}
|
|
|
|
await withPlatform('win32', async () => {
|
|
const { openFileWithEditor } = require('../../src/app/lib/open-file-with-editor')
|
|
await openFileWithEditor('C:\\Temp\\poc";Start-Process calc;#.txt', 'notepad.exe')
|
|
})
|
|
|
|
assert.equal(spawnCall.command, 'powershell.exe')
|
|
assert.deepEqual(spawnCall.args.slice(0, 2), ['-NoLogo', '-Command'])
|
|
assert.match(spawnCall.args[2], /& \$editor @editorArgs/)
|
|
assert.equal(
|
|
Buffer.from(spawnCall.options.env.ELECTERM_EDITOR_COMMAND_B64, 'base64').toString('utf8'),
|
|
'notepad.exe'
|
|
)
|
|
assert.deepEqual(
|
|
JSON.parse(Buffer.from(spawnCall.options.env.ELECTERM_EDITOR_ARGS_B64, 'base64').toString('utf8')),
|
|
['C:\\Temp\\poc";Start-Process calc;#.txt']
|
|
)
|
|
assert.equal(spawnCall.options.windowsHide, true)
|
|
})
|
|
|
|
test('openFileWithEditor passes malicious filenames as a literal shell argument on Unix', async () => {
|
|
let spawnCall
|
|
childProcess.spawn = (command, args, options) => {
|
|
spawnCall = { command, args, options }
|
|
|
|
return {
|
|
stderr: {
|
|
on: () => {}
|
|
},
|
|
on: (event, handler) => {
|
|
if (event === 'close') {
|
|
process.nextTick(() => handler(0))
|
|
}
|
|
},
|
|
unref: () => {}
|
|
}
|
|
}
|
|
|
|
const originalShell = process.env.SHELL
|
|
process.env.SHELL = '/bin/zsh'
|
|
|
|
try {
|
|
await withPlatform('darwin', async () => {
|
|
const { openFileWithEditor } = require('../../src/app/lib/open-file-with-editor')
|
|
await openFileWithEditor('/tmp/poc";touch /tmp/pwn;#.txt', 'code --wait')
|
|
})
|
|
} finally {
|
|
process.env.SHELL = originalShell
|
|
}
|
|
|
|
assert.equal(spawnCall.command, '/bin/zsh')
|
|
assert.deepEqual(spawnCall.args, [
|
|
'-l',
|
|
'-i',
|
|
'-c',
|
|
'exec "$0" "$@"',
|
|
'code',
|
|
'--wait',
|
|
'/tmp/poc";touch /tmp/pwn;#.txt'
|
|
])
|
|
assert.equal(spawnCall.options.detached, false)
|
|
})
|
|
|
|
test('parseEditorCommand rejects unmatched quotes', () => {
|
|
const { parseEditorCommand } = require('../../src/app/lib/open-file-with-editor')
|
|
|
|
assert.throws(
|
|
() => parseEditorCommand('"C:\\Program Files\\Notepad++\\notepad++.exe'),
|
|
/unmatched quote/
|
|
)
|
|
})
|