Back to Blog
high SEVERITY4 min read

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.

O
By Orbis AppSec
Published September 22, 2026Reviewed September 22, 2026

Answer Summary

The `runStreaming()` function in an Electron main process accepted a `cmd` string and `args` array without validation, enabling command injection if caller input were attacker-controlled. An attacker could execute arbitrary shell commands by injecting metacharacters through upstream data flows. The fix adds a whitelist regex `/^[A-Za-z0-9_.: \\/-]+$/` and explicit type checks for `cmd` and `args`, rejecting any call that doesn't pass. No CVE assigned; CWE-1357.

Vulnerability at a Glance

cweCWE-1357 (Reliance on Insufficiently Trustworthy Component)
fixAdded whitelist regex and type guards to reject dangerous command patterns
riskArbitrary code execution if upstream caller passes attacker-controlled input
languageJavaScript (Node.js/Electron)
root cause`runStreaming()` passed `cmd` directly to `child_process` without validation or sanitization
vulnerabilityCommand injection via unvalidated child_process invocation

Introduction

A helper function designed to stream subprocess output in an Electron application accepted arbitrary command strings without validation, creating a latent command injection vector. The runStreaming() utility wraps child_process to handle long-running Python package installations (specifically Paddle wheels and model archives), but its signature—runStreaming(cmd, args, opts)—trusted callers to provide safe inputs.

The problem: nothing in runStreaming() enforced that trust. A caller receiving user-influenced data could pass a malicious cmd like "; rm -rf / #" or an args array containing shell metacharacters. While the function used the array-form child_process API (avoiding shell: true), an attacker controlling both cmd and args could still exploit path traversal or argument injection depending on the target binary.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see commit with SAFE_CMD regex addition
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-1357 (Reliance on Insufficiently Trustworthy Component)

The Vulnerability Explained

The vulnerable code accepted cmd as a string and args as an array without any inspection:

const runStreaming = (cmd, args, opts = {}) => new Promise((resolve, reject) => {
  if (quitting) { reject(new Error('应用正在退出')); return }
  let proc
  // ... spawns process with cmd and args directly

An attacker who influenced cmd could pass a relative path containing directory traversal (../../../malicious) or a binary name with shell metacharacters. More critically, if any caller concatenated user input into cmd before passing it, the lack of validation meant that injection propagated directly to process execution.

The real-world impact depends on data flows upstream of runStreaming(). The function handles package downloads for machine learning workflows—contexts where mirror URLs, proxy configurations, or user-supplied index URLs might reach this code path. An attacker compromising a package mirror or supplying a malicious index-url could potentially influence the command executed.

The Fix

The patch adds defense-in-depth validation that fails closed:

const SAFE_CMD = /^[A-Za-z0-9_.: \\/-]+$/
if (typeof cmd !== 'string' || !SAFE_CMD.test(cmd) || !Array.isArray(args) || args.some((a) => typeof a !== 'string')) {
  reject(new Error('非法的子进程调用参数'))
  return
}

This change introduces three specific protections:

  1. Type enforcement: cmd must be a string, args must be an array, and every element of args must be a string. This blocks prototype pollution attacks and type confusion bugs.

  2. Character whitelist: The SAFE_CMD regex permits only alphanumeric characters, safe path separators, and limited punctuation. It explicitly rejects semicolons (;), pipes (|), backticks (`), dollar signs ($), and other shell metacharacters.

  3. Explicit failure: Invalid inputs reject with a clear error message rather than proceeding with potentially dangerous data.

The fix maintains the existing array-form invocation (no shell), so the regex is strictly defense-in-depth—it closes off an entire class of vulnerabilities that would emerge if a caller ever passed unsanitized input.

Key Takeaways

  • Whitelist over blacklist: The SAFE_CMD regex defines permitted characters rather than trying to enumerate dangerous ones, avoiding the inevitable gaps in blacklist approaches.

  • Validate at trust boundaries: runStreaming() is a trust boundary between application logic and the operating system. Validation belongs here even if all current callers appear safe.

  • Array-form spawning isn't enough: While child_process.spawn(cmd, args) without shell: true prevents direct shell injection, a malicious cmd can still target unexpected binaries or exploit argument injection in the target program.

  • Type safety is security: The typeof checks catch JavaScript's dynamic typing footguns—args could be an object with a numeric length property, or cmd could be a String object with dangerous prototype methods.

  • Defense-in-depth justifies unexploitable fixes: Even without a demonstrated exploit, bounding failure modes prevents vulnerabilities from emerging as code evolves.

How Orbis AppSec Detected This

Source: The cmd parameter of runStreaming(), potentially influenced by upstream configuration values like mirror URLs or user settings.

Sink: child_process invocation within runStreaming(), where cmd and args are passed to process spawning.

Missing control: No validation of cmd contents, no type checking for args elements, and no restriction on shell metacharacters in command paths.

CWE: CWE-1357 (Reliance on Insufficiently Trustworthy Component) — the code relied on callers to provide trustworthy inputs without enforcing that contract.

Fix: Added whitelist regex and explicit type guards to reject any command string containing dangerous characters or any malformed argument array.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The runStreaming() fix demonstrates how defense-in-depth validation transforms a latent vulnerability into an explicit, bounded failure mode. By whitelisting safe command characters and enforcing type contracts, the code now fails securely even against hypothetical attacker-controlled inputs—protecting against supply chain compromises and configuration injection attacks that might emerge in future code changes.

Prevention and further reading

Frequently Asked Questions

Does the `runStreaming()` fix change the function's return type or Promise behavior?

No. The fix adds early rejection with `new Error('非法的子进程调用参数')` for invalid inputs, but successful calls resolve identically. The Promise interface remains unchanged.

Why validate `args` as an array of strings when `child_process` already enforces this?

Defense-in-depth. The check ensures that even if a caller passes a malformed `args` value (e.g., through prototype pollution or type confusion), the function fails explicitly before reaching `child_process`.

Which characters does the `SAFE_CMD` regex explicitly allow in command paths?

Letters, digits, underscore, period, colon, space, forward slash, backslash, and hyphen. Notably, it excludes semicolons, pipes, backticks, dollar signs, and other shell metacharacters that enable command chaining.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #23

Related Articles

critical

docker_rpc.uc Command Injection: Unsanitized RPC Parameters

A critical command injection vulnerability in the Docker RPC handler allowed authenticated attackers to execute arbitrary system commands by injecting shell metacharacters into container ID, port, user ID, or command parameters. The fix validates all user-supplied inputs against strict whitelist patterns before interpolating them into shell commands.

critical

{sample} Placeholder in shlex.split() Lets Filenames Inject Args

A protocol replay-check CLI built its subprocess argument list by calling `str.format()` on a user-supplied `--command` template and then handing the result to `shlex.split()`, so a sample filename containing spaces, quotes, or shell metacharacters could split into extra argv entries — or execute as shell code when the template wrapped the placeholder in `sh -c`. The fix wraps the interpolated path in `shlex.quote()` before formatting, so the path always survives `shlex.split()` as a single toke

high

package_abridge.js Command Injection via Unsanitized CLI Arguments

A high-severity command injection vulnerability in a build script allowed attackers who control CLI arguments to execute arbitrary shell commands by injecting metacharacters into an unvalidated parameter. The fix validates incoming CLI arguments and rejects those containing dangerous shell metacharacters before they reach command execution.

critical

shell-quote 1.8.3: Line Terminator Command Injection (CVE-2026-9277)

CVE-2026-9277 is a critical command injection vulnerability in shell-quote versions before 1.9.0, where unescaped line terminators allow attackers to break out of quoted strings and execute arbitrary shell commands. The fix upgrades the dependency across multiple React Native CLI packages and related libraries through npm overrides.

critical

Voice Assistant Command Injection via os.system() f-String

A critical command injection vulnerability in a voice assistant's audio playback handler allowed attackers to execute arbitrary shell commands by manipulating file paths passed to os.system(). The fix replaces shell invocation with subprocess calls and direct OS APIs, eliminating shell metacharacter interpretation entirely.

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho