CVE-2026-43943
Description
electerm is an open-sourced terminal/ssh/sftp/telnet/serialport/RDP/VNC/Spice/ftp client. Prior to version 3.7.9, a code execution (RCE) vulnerability exists in electerm's SFTP open with system editor or "Edit with custom editor" feature. When a user opts to edit a file using open with system editor or open with a custom editor, the filename is passed directly into a command line without sanitization. A malicious actor controlling the SSH server or user OS can exploit this by crafting a filename containing shell metacharacters. If a victim subsequently attempts to edit this file, the injected commands are executed on their machine with the user's privileges. This could allow the attacker to run arbitrary code, install malware, or move laterally within the network. This issue has been patched in version 3.7.9.
Affected products
2Patches
124ce7103e264Improve open file function
4 files changed · +144 −14
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "electerm", - "version": "3.7.6", + "version": "3.7.8", "description": "Terminal/ssh/telnet/serialport/sftp client(linux, mac, win)", "main": "app.js", "bin": "npm/electerm",
package-lock.json+2 −2 modified@@ -1,12 +1,12 @@ { "name": "electerm", - "version": "3.7.6", + "version": "3.7.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "electerm", - "version": "3.7.6", + "version": "3.7.8", "hasInstallScript": true, "license": "MIT", "dependencies": {
src/app/lib/fs.js+54 −11 modified@@ -5,9 +5,8 @@ const path = require('path') const { isWin, isMac, tempDir } = require('../common/runtime-constants') const uid = require('../common/uid') const { promisify } = require('util') -const execAsync = promisify( - require('child_process').exec -) +const { exec, spawn } = require('child_process') +const execAsync = promisify(exec) const { getSizeCount, getSizeCountWin } = require('../common/get-folder-size-and-file-count.js') const ROOT_PATH = '/' @@ -49,6 +48,47 @@ const runWinCmd = (cmd) => { return execAsync(`powershell.exe -Command "${cmd}"`) } +function spawnDetachedCommand (command, args, options = {}) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + detached: false, + stdio: ['ignore', 'ignore', 'pipe'], + ...options + }) + let stderr = '' + + child.stderr.on('data', data => { + stderr += data.toString() + }) + child.on('error', reject) + + let settled = false + const settle = (err) => { + if (settled) { + return + } + settled = true + clearTimeout(timer) + child.unref() + if (err) { + reject(err) + } else { + resolve() + } + } + + child.on('close', code => { + if (code !== 0) { + settle(new Error(stderr.trim() || `Command exited with code ${code}`)) + } else { + settle(null) + } + }) + + const timer = setTimeout(() => settle(null), 5000) + }) +} + function getFolderSizeWin (folderPath) { return runWinCmd( `Get-ChildItem -Path "${folderPath}" -Recurse | Where-Object { ! $_.PSIsContainer } | Measure-Object -Property Length -Sum` @@ -109,16 +149,19 @@ const touch = (localFilePath) => { * @param {string} localFolderPath absolute path */ const openFile = (localFilePath) => { - let cmd if (isWin) { - cmd = `Invoke-Item '${localFilePath}'` - return runWinCmd(cmd) + return spawnDetachedCommand('powershell.exe', [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Invoke-Item -LiteralPath $args[0]', + '--', + localFilePath + ], { + windowsHide: true + }) } - cmd = (isMac - ? 'open' - : 'xdg-open') + - ` "${localFilePath}"` - return run(cmd) + return spawnDetachedCommand(isMac ? 'open' : 'xdg-open', [localFilePath]) } /**
test/unit/open-file.spec.js+87 −0 added@@ -0,0 +1,87 @@ +const test = require('node:test') +const assert = require('node:assert/strict') + +const childProcess = require('child_process') +const os = require('os') + +const originalSpawn = childProcess.spawn +const originalPlatform = os.platform +const originalArch = os.arch +const originalNodeEnv = process.env.NODE_ENV + +function loadFsForPlatform (platform) { + os.platform = () => platform + os.arch = () => 'x64' + process.env.NODE_ENV = 'development' + delete require.cache[require.resolve('../../src/app/lib/fs')] + delete require.cache[require.resolve('../../src/app/common/runtime-constants')] + return require('../../src/app/lib/fs').fsExport +} + +test.afterEach(() => { + childProcess.spawn = originalSpawn + os.platform = originalPlatform + os.arch = originalArch + process.env.NODE_ENV = originalNodeEnv + delete require.cache[require.resolve('../../src/app/lib/fs')] + delete require.cache[require.resolve('../../src/app/common/runtime-constants')] +}) + +test('openFile passes malicious Windows filenames as a literal PowerShell 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: () => {} + } + } + + const fsExport = loadFsForPlatform('win32') + await fsExport.openFile("C:\\Temp\\poc';Start-Process calc;#.txt") + + assert.equal(spawnCall.command, 'powershell.exe') + assert.deepEqual(spawnCall.args, [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Invoke-Item -LiteralPath $args[0]', + '--', + "C:\\Temp\\poc';Start-Process calc;#.txt" + ]) + assert.equal(spawnCall.options.windowsHide, true) +}) + +test('openFile passes malicious Unix filenames directly to open command', 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 fsExport = loadFsForPlatform('darwin') + await fsExport.openFile('/tmp/poc";touch /tmp/pwn;#.txt') + + assert.equal(spawnCall.command, 'open') + assert.deepEqual(spawnCall.args, ['/tmp/poc";touch /tmp/pwn;#.txt']) + assert.equal(spawnCall.options.detached, false) +})
Vulnerability mechanics
AI mechanics synthesis has not run for this CVE yet.
References
5- github.com/electerm/electerm/commit/24ce7103e264cffe6eb5476c0506a2379e6f8333nvdPatch
- github.com/electerm/electerm/security/advisories/GHSA-q4p8-8j9m-8hxjnvdPatchVendor Advisory
- github.com/advisories/GHSA-q4p8-8j9m-8hxjghsaADVISORY
- github.com/electerm/electerm/releases/tag/v3.7.9nvdRelease Notes
- nvd.nist.gov/vuln/detail/CVE-2026-43943ghsa
News mentions
0No linked articles in our index yet.