Files
electerm--electerm/test/unit/open-file-with-editor.spec.js
2026-07-13 12:46:20 +08:00

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/
)
})