Back to Blog
high SEVERITY7 min read

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

The `shell-quote` package (versions prior to 1.9.0) contained a critical command injection vulnerability where unescaped line terminators in shell arguments could be exploited to inject arbitrary commands. This vulnerability was discovered in the docs-site dependency tree and fixed by upgrading to version 1.9.0, which properly escapes line terminators to prevent attackers from breaking out of quoted arguments and executing malicious shell commands.

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

Answer Summary

CVE-2026-9277 is a command injection vulnerability in the Node.js `shell-quote` package (CWE-78: Improper Neutralization of Special Elements used in an OS Command) caused by inadequate escaping of line terminators in shell arguments. When untrusted input containing newlines or carriage returns was processed, attackers could inject arbitrary shell commands. The fix upgrades `shell-quote` from version 1.8.3 to 1.9.0, which properly escapes line terminators to prevent command injection, ensuring that user-controlled input cannot break out of quoted arguments.

Vulnerability at a Glance

cweCWE-78 (Improper Neutralization of Special Elements used in an OS Command)
fixUpgrade shell-quote to 1.9.0 which implements proper line terminator escaping
riskArbitrary code execution when processing untrusted shell arguments
languageJavaScript/Node.js
root causeshell-quote 1.8.3 failed to escape newline and carriage return characters in shell arguments
vulnerabilityCommand Injection via Unescaped Line Terminators

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

Introduction

In the docs-site repository, a critical command injection vulnerability (CVE-2026-9277) was discovered lurking in the dependency tree through the shell-quote package. The vulnerability wasn't in custom code—it was in a transitive dependency that handles shell argument escaping, a task that seems simple but has profound security implications.

The issue: shell-quote version 1.8.3 failed to properly escape line terminator characters (newlines and carriage returns) in shell arguments. This seemingly minor oversight created a dangerous window for attackers to inject arbitrary shell commands by embedding these special characters in what should have been safely quoted arguments.

For any Node.js application that uses shell-quote to safely construct shell commands from user-controlled input, this vulnerability represented a direct path to remote code execution. The fix was straightforward but critical: upgrade to version 1.9.0, which implements proper escaping of line terminators.

The Vulnerability Explained

What Made This Dangerous?

The shell-quote package is designed to solve a common problem in Node.js: when you need to pass user-controlled strings as arguments to shell commands, you must properly escape special characters. Without proper escaping, an attacker can inject shell metacharacters (like ;, |, &, $()) to execute arbitrary commands.

However, shell-quote 1.8.3 had a blind spot: it didn't account for line terminator characters (newline \n and carriage return \r). Here's why this matters:

In most shell contexts, when a quoted string contains a newline, the shell interprets it as the end of the current command and the beginning of a new one. An attacker could craft input like:

user input: "safe_arg\nmalicious_command"

When processed by shell-quote 1.8.3 and then passed to a shell, it would be treated as:

safe_arg
malicious_command

Instead of a single safe argument, the attacker has successfully injected a second command that executes with the same privileges as the original process.

The Attack Scenario

Imagine a Node.js application in docs-site that uses shell-quote to safely construct a command for documentation generation:

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

// User-provided documentation filename
const userFilename = req.query.filename;

// Attempt to safely quote the filename
const quotedFilename = shellQuote.quote([userFilename]);

// Execute a documentation processing command
const command = `process-docs --file ${quotedFilename}`;
execSync(command);

With shell-quote 1.8.3, if an attacker provides:

filename=report.md\nrm -rf /important/data

The resulting command becomes:

process-docs --file report.md
rm -rf /important/data

Both commands execute. The attacker has achieved arbitrary code execution.

Why Line Terminators Were Missed

The vulnerability existed because earlier versions of shell-quote focused on escaping traditional shell metacharacters (;, |, &, $, backticks, etc.) but didn't consider that line terminators could also break the shell context. This is a classic example of incomplete input sanitization—addressing the most obvious attack vectors while missing edge cases.

The scanner (Trivy) flagged this as present in the dependency tree with the assessment "not confirmed reachable," meaning the vulnerability existed in the dependency graph but the actual code path might not have been exercised. However, the principle of defense in depth dictates that you should patch known vulnerabilities regardless—transitive dependencies can be called in unexpected ways, and future code changes might activate the vulnerable path.

The Fix

What Changed?

The fix involved upgrading shell-quote from version 1.8.3 to 1.9.0 and adding an explicit override in package.json to ensure this version is used throughout the dependency tree.

In docs-site/package-lock.json:

     "node_modules/shell-quote": {
-      "version": "1.8.3",
-      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
-      "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+      "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==",

In docs-site/package.json:

   "engines": {
     "node": ">=20.0"
+  },
+  "overrides": {
+    "shell-quote": "1.9.0"
   }

How This Solves the Problem

Version 1.9.0 of shell-quote implements proper escaping of line terminator characters. When the package encounters \n or \r in user input, it now escapes them appropriately so they're treated as literal characters within the quoted string, not as command separators.

With the patched version, the attack scenario from earlier is neutralized:

// With shell-quote 1.9.0
const userFilename = "report.md\nrm -rf /important/data";
const quotedFilename = shellQuote.quote([userFilename]);
// Result: properly escapes the newline, preventing command injection
// The entire string is treated as a single filename argument

The overrides field in package.json is particularly important. It ensures that even if another dependency in the tree specifies an older version of shell-quote, npm will use 1.9.0 instead. This prevents the vulnerable version from being installed through transitive dependency resolution.

Why Both Files Were Changed

  • package-lock.json: Records the exact version and integrity hash of the installed package, ensuring reproducible builds
  • package.json: Adds an explicit override to guarantee that 1.9.0 is used across the entire dependency tree, even if other packages request different versions

This two-pronged approach prevents dependency confusion and ensures the fix persists across team members' installations and CI/CD pipelines.

Prevention & Best Practices

1. Keep Dependencies Updated

Regularly update security-sensitive packages like shell-quote. Enable automated dependency updates through tools like Dependabot or Renovate, which can automatically create PRs for security patches.

2. Use Parameterized APIs

Whenever possible, avoid constructing shell commands from strings. Use APIs that treat arguments as data, not code:

// ❌ Avoid: Command as string
execSync(`process-docs --file ${userFilename}`);

// ✅ Better: Arguments as array (no shell parsing)
execFileSync('process-docs', ['--file', userFilename]);

3. Validate and Sanitize Input

Even with proper escaping, validate that user input matches expected patterns:

// Validate filename format before processing
if (!/^[a-zA-Z0-9_\-\.]+$/.test(userFilename)) {
  throw new Error('Invalid filename format');
}

4. Use Security Scanners

Integrate tools like Trivy, npm audit, and Snyk into your CI/CD pipeline to automatically detect vulnerable dependencies:

# Run Trivy to scan for known vulnerabilities
trivy fs docs-site/

# Run npm audit
npm audit --audit-level=moderate

5. Understand CWE-78

Familiarize yourself with CWE-78: Improper Neutralization of Special Elements used in an OS Command. Many command injection vulnerabilities follow similar patterns.

6. Review Transitive Dependencies

Use npm ls to understand your full dependency tree:

npm ls shell-quote

Know what versions of critical security packages are installed, even indirectly.

Key Takeaways

  • Line terminators are special: Command injection isn't limited to traditional shell metacharacters like ; and |. Newlines and carriage returns can be equally dangerous when not properly escaped.

  • shell-quote 1.8.3 was incomplete: The package addressed common injection vectors but missed line terminator escaping, demonstrating that security libraries require continuous improvement as attack techniques evolve.

  • Transitive dependencies matter: Even though shell-quote was a transitive dependency in docs-site, the vulnerability posed a real risk. Scanning and patching all layers of the dependency tree is essential.

  • Defense in depth requires override mechanisms: The overrides field in package.json is a critical tool for ensuring security patches propagate through complex dependency trees where multiple packages might specify conflicting versions.

  • Automated detection is reliable for known CVEs: Trivy and similar scanners can reliably flag vulnerable versions of well-known packages, but human judgment is still required to assess actual exploitability and prioritize fixes.

How Orbis AppSec Detected This

Source: Transitive dependency declaration in docs-site/package.json and docs-site/package-lock.json (shell-quote 1.8.3)

Sink: Any code path that uses shell-quote to escape arguments for shell execution (e.g., in command construction utilities or subprocess wrappers)

Missing control: Absence of line terminator escaping in shell-quote 1.8.3's quoting logic; no explicit version override to enforce patched versions across the dependency tree

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

Fix: Upgrade shell-quote from 1.8.3 to 1.9.0 which properly escapes line terminators, and add an explicit npm override to ensure the patched version is used throughout the dependency tree.

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 demonstrates a critical principle in secure coding: security libraries must be comprehensive in their protection. A single missed edge case—in this case, line terminator characters—can completely undermine the security guarantees a library is supposed to provide.

For Node.js developers, this vulnerability reinforces several important practices:

  1. Trust but verify: Even widely-used security libraries like shell-quote can have gaps. Keep them updated and monitor security advisories.

  2. Defense in depth: Don't rely solely on shell-quote for security. Use parameterized APIs, validate input, and avoid shell=true when possible.

  3. Dependency management is security work: Maintaining a secure application means actively managing transitive dependencies, not just direct ones. Tools like npm overrides are your allies.

  4. Automation catches what humans miss: Security scanners like Trivy can reliably identify known vulnerable versions. Integrate them into your CI/CD pipeline and act on their findings promptly.

By understanding how this vulnerability worked and why the fix was necessary, you're better equipped to recognize similar issues in your own code and dependencies—and to build systems that are resilient against command injection attacks.


References

Frequently Asked Questions

What is command injection via line terminators?

It's a vulnerability where newline or carriage return characters in shell arguments aren't properly escaped, allowing attackers to inject new shell commands after breaking out of the current argument context.

How do you prevent command injection in Node.js?

Always use libraries like `shell-quote` that properly escape special characters, avoid `shell: true` in child processes, validate and sanitize all user input, and use parameterized APIs that treat arguments as data, not code.

What CWE is this vulnerability?

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

Is using shell-quote enough to prevent command injection?

Only if you're using a patched version that properly escapes all special characters including line terminators. Version 1.8.3 was not sufficient; 1.9.0 and later are required.

Can static analysis detect command injection via line terminators?

Yes, security scanners like Trivy can detect vulnerable versions of shell-quote in dependency trees. However, detecting whether line terminators are actually exploitable requires understanding the full data flow context.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1279

Related Articles

critical

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 version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

high

How Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A command injection vulnerability was discovered in the audio processing plugin `audioedit.js`, where user-controlled input from downloaded media files was passed directly to shell commands via `exec()`. The fix replaces dangerous shell string interpolation with `execFile()` and argument arrays, eliminating the command injection attack surface entirely.

high

How Command Injection Happens in Node.js child_process and How to Fix It

A high-severity command injection vulnerability was discovered in `core/cli.js` where the `execSync()` function was called with user-controllable input without proper sanitization. This could allow attackers to execute arbitrary system commands. The fix implements defensive hardening by explicitly marking and validating the dangerous code path to prevent exploitation.

high

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

A high-severity command injection vulnerability was discovered in `hooks/scripts/auto-stage.js` where the `stageFile()` function used `execSync()` with string interpolation to execute git commands. By switching from `execSync()` with template strings to `spawnSync()` with argument arrays, the fix eliminates shell interpretation and prevents attackers from injecting malicious commands through crafted file paths.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A high-severity vulnerability (CVE-2026-69192) was discovered in the ip-address library version 10.1.0, where inconsistent IP address parsing could lead to Server-Side Request Forgery (SSRF) and trust-boundary bypass attacks. The vulnerability was fixed by upgrading ip-address from 10.1.0 to 10.3.1 in the gateway-workflow-dispatcher-v2.js component, preventing attackers from bypassing IP validation checks and accessing internal resources.