VYPR
Critical severity9.6GHSA Advisory· Published May 8, 2026· Updated May 13, 2026

CVE-2026-43944

CVE-2026-43944

Description

electerm is an open-sourced terminal/ssh/sftp/telnet/serialport/RDP/VNC/Spice/ftp client. From versions 3.0.6 to before 3.8.15, electerm is vulnerable to arbitrary local code execution via deep links, CLI --opts, or crafted shortcuts. Exploit requires clicking a crafted electerm://... link or opening a crafted shortcut/command that launches electerm with attacker-controlled opts. This issue has been patched in version 3.8.15.

Affected products

1

Patches

3
0599e67069b0

Security: do not allow unsafe props when open deep link

https://github.com/electerm/electermZHAO XudongMay 10, 2026via ghsa
6 files changed · +39 19
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "electerm",
    -  "version": "3.9.0",
    +  "version": "3.9.5",
       "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.9.0",
    +  "version": "3.9.5",
       "lockfileVersion": 3,
       "requires": true,
       "packages": {
         "": {
           "name": "electerm",
    -      "version": "3.9.0",
    +      "version": "3.9.5",
           "hasInstallScript": true,
           "license": "MIT",
           "dependencies": {
    
  • src/app/common/bookmark-zod-schemas.js+13 13 modified
    @@ -38,7 +38,7 @@ const commonNetworkBookmarkProps = {
       username: z.string().optional().describe('Username'),
       password: z.string().optional().describe('Password'),
       description: z.string().optional().describe('Bookmark description'),
    -  runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected'),
    +  // runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected'),
       startDirectoryRemote: z.string().optional().describe('Remote starting directory'),
       startDirectoryLocal: z.string().optional().describe('Local starting directory'),
       profile: z.string().optional().describe('Profile id'),
    @@ -67,9 +67,9 @@ const sshBookmarkSchema = {
       displayRaw: z.boolean().optional().describe('Display raw output, default is false'),
       encode: z.string().optional().describe('Charset, default is utf8'),
       envLang: z.string().optional().describe('ENV LANG, default is en_US.UTF-8'),
    -  setEnv: z.string().optional().describe('Environment variables, format: KEY1=VALUE1 KEY2=VALUE2'),
    +  // setEnv: z.string().optional().describe('Environment variables, format: KEY1=VALUE1 KEY2=VALUE2'),
       color: z.string().optional().describe('Tag color, like #000000'),
    -  interactiveValues: z.string().optional().describe('Strings separated by newline'),
    +  // interactiveValues: z.string().optional().describe('Strings separated by newline'),
       sshTunnels: z.array(sshTunnelSchema).optional().describe('SSH tunnel definitions'),
       connectionHoppings: z.array(connectionHoppingSchema).optional().describe('Connection hopping definitions')
     }
    @@ -95,21 +95,21 @@ const serialBookmarkSchema = {
       xon: z.boolean().optional().describe('XON flow control'),
       xoff: z.boolean().optional().describe('XOFF flow control'),
       xany: z.boolean().optional().describe('XANY flow control'),
    -  description: z.string().optional().describe('Bookmark description'),
    -  runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected')
    +  description: z.string().optional().describe('Bookmark description')
    +  // runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected')
     }
     
     const localBookmarkSchema = {
       title: z.string().describe('Bookmark title'),
       description: z.string().optional().describe('Bookmark description'),
    -  startDirectoryLocal: z.string().optional().describe('Local starting directory'),
    -  runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected'),
    -  execWindows: z.string().optional().describe('Windows exec path (overrides global setting)'),
    -  execMac: z.string().optional().describe('Mac exec path (overrides global setting)'),
    -  execLinux: z.string().optional().describe('Linux exec path (overrides global setting)'),
    -  execWindowsArgs: z.array(z.string()).optional().describe('Windows exec arguments'),
    -  execMacArgs: z.array(z.string()).optional().describe('Mac exec arguments'),
    -  execLinuxArgs: z.array(z.string()).optional().describe('Linux exec arguments')
    +  startDirectoryLocal: z.string().optional().describe('Local starting directory')
    +  // runScripts: z.array(runScriptSchema).optional().describe('Run scripts after connected'),
    +  // execWindows: z.string().optional().describe('Windows exec path (overrides global setting)'),
    +  // execMac: z.string().optional().describe('Mac exec path (overrides global setting)'),
    +  // execLinux: z.string().optional().describe('Linux exec path (overrides global setting)'),
    +  // execWindowsArgs: z.array(z.string()).optional().describe('Windows exec arguments'),
    +  // execMacArgs: z.array(z.string()).optional().describe('Mac exec arguments'),
    +  // execLinuxArgs: z.array(z.string()).optional().describe('Linux exec arguments')
     }
     
     module.exports = {
    
  • src/client/components/main/main.jsx+1 1 modified
    @@ -51,7 +51,7 @@ export default auto(function Index (props) {
         ipcOnEvent('open-about', store.openAbout)
         ipcOnEvent('new-ssh', store.onNewSsh)
         ipcOnEvent('add-tab-from-command-line', store.addTabFromCommandLine)
    -    ipcOnEvent('open-tab', (e, parsed) => store.addTab(parsed))
    +    ipcOnEvent('open-tab', (e, parsed) => store.ipcOpenTab(parsed))
         ipcOnEvent('openSettings', store.openSetting)
         ipcOnEvent('selectall', store.selectall)
         ipcOnEvent('focused', store.focus)
    
  • src/client/store/load-data.js+2 2 modified
    @@ -100,7 +100,7 @@ export async function addTabFromCommandLine (store, opts) {
         (conf.username && conf.host) ||
         conf.fromCmdLine
       ) {
    -    store.addTab(conf)
    +    store.ipcOpenTab(conf)
       } else if (
         options.initFolder &&
         !(store.config.onStartSessions || []).length &&
    @@ -229,7 +229,7 @@ export default (Store) => {
       Store.prototype.checkPendingDeepLink = async function () {
         const pending = await window.pre.runGlobalAsync('getPendingDeepLink')
         if (pending) {
    -      window.store.addTab(pending)
    +      window.store.ipcOpenTab(pending)
         }
       }
       Store.prototype.parseQuickConnect = function (url) {
    
  • src/client/store/tab.js+20 0 modified
    @@ -362,6 +362,26 @@ export default Store => {
         store.updateHistory(newTab)
       }
     
    +  // Dangerous props that should not be accepted from IPC
    +  const dangerousTabProps = [
    +    'execLinux',
    +    'execMac',
    +    'execWindows',
    +    'execWindowsArgs',
    +    'execMacArgs',
    +    'execLinuxArgs',
    +    'setEnv',
    +    'runScripts',
    +    'interactiveValues'
    +  ]
    +
    +  Store.prototype.ipcOpenTab = function (parsed) {
    +    const safeTab = Object.fromEntries(
    +      Object.entries(parsed).filter(([key]) => !dangerousTabProps.includes(key))
    +    )
    +    return window.store.addTab(safeTab)
    +  }
    +
       Store.prototype.clickNextTab = debounce(function () {
         window.store.clickBioTab(1)
       }, 100)
    
8a6a17951e96

Security: Deep link support prop check

https://github.com/electerm/electermZHAO XudongMay 4, 2026via nvd-ref
2 files changed · +18 2
  • src/app/common/parse-quick-connect.js+9 1 modified
    @@ -21,6 +21,12 @@
     
     const SUPPORTED_PROTOCOLS = ['ssh', 'telnet', 'vnc', 'rdp', 'spice', 'serial', 'ftp', 'http', 'https', 'electerm']
     
    +/**
    + * Deny list for opts keys - these are parsed from the URL itself
    + * and should not be overridable via the opts JSON parameter for safety
    + */
    +const OPTS_DENY_LIST = ['type', 'host']
    +
     /**
      * Default ports for each protocol
      */
    @@ -393,6 +399,7 @@ function parseQuickConnect (str) {
         if (optsStr) {
           try {
             const extraOpts = JSON.parse(optsStr)
    +        OPTS_DENY_LIST.forEach(key => delete extraOpts[key])
             Object.assign(opts, extraOpts)
           } catch (err) {
             console.error('Failed to parse opts:', err)
    @@ -439,5 +446,6 @@ module.exports = {
       getDefaultPort,
       getSupportedProtocols,
       SUPPORTED_PROTOCOLS,
    -  DEFAULT_PORTS
    +  DEFAULT_PORTS,
    +  OPTS_DENY_LIST
     }
    
  • src/client/common/parse-quick-connect.js+9 1 modified
    @@ -21,6 +21,12 @@
     
     const SUPPORTED_PROTOCOLS = ['ssh', 'telnet', 'vnc', 'rdp', 'spice', 'serial', 'ftp', 'http', 'https', 'electerm']
     
    +/**
    + * Deny list for opts keys - these are parsed from the URL itself
    + * and should not be overridable via the opts JSON parameter for safety
    + */
    +const OPTS_DENY_LIST = ['type', 'host']
    +
     /**
      * Default ports for each protocol
      */
    @@ -393,6 +399,7 @@ function parseQuickConnect (str) {
         if (optsStr) {
           try {
             const extraOpts = JSON.parse(optsStr)
    +        OPTS_DENY_LIST.forEach(key => delete extraOpts[key])
             Object.assign(opts, extraOpts)
           } catch (err) {
             console.error('Failed to parse opts:', err)
    @@ -439,5 +446,6 @@ export {
       getDefaultPort,
       getSupportedProtocols,
       SUPPORTED_PROTOCOLS,
    -  DEFAULT_PORTS
    +  DEFAULT_PORTS,
    +  OPTS_DENY_LIST
     }
    
a79e06f4a1f0

Security: Do not allow exec path with ".."

https://github.com/electerm/electermZHAO XudongMay 3, 2026via nvd-ref
2 files changed · +7 3
  • src/app/server/session-local.js+6 2 modified
    @@ -24,13 +24,17 @@ class TerminalLocal extends TerminalBase {
         } = this.initOptions
         this.isLocal = true
         const { platform } = process
    -    const exec = platform.startsWith('win')
    +    const isWin = platform.startsWith('win')
    +    const exec = isWin
           ? pathResolve(
             process.env.windir,
             execWindows
           )
           : platform === 'darwin' ? execMac : execLinux
    -    const arg = platform.startsWith('win')
    +    if ((exec || '').includes('..')) {
    +      return Promise.reject(new Error('execWindows should not contain ".."'))
    +    }
    +    const arg = isWin
           ? execWindowsArgs
           : platform === 'darwin' ? execMacArgs : execLinuxArgs
         const cwd = process.env[platform === 'win32' ? 'USERPROFILE' : 'HOME']
    
  • src/client/components/ai/ai-config.jsx+1 1 modified
    @@ -6,7 +6,7 @@ import {
       Alert,
       Space
     } from 'antd'
    -import { useEffect, useState } from 'react'
    +import { useEffect } from 'react'
     import Link from '../common/external-link'
     import AiCache from './ai-cache'
     import {
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

7

News mentions

0

No linked articles in our index yet.