Back to Blog
critical SEVERITY5 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. This fix upgrades shell-quote from version 1.8.3 to 1.9.0 and adds a dependency override to ensure the patched version is used throughout the dependency tree.

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

Answer Summary

CVE-2026-9277 is a critical command injection vulnerability in the Node.js shell-quote package (CWE-78: OS Command Injection) caused by improper escaping of line terminators like `\n` and `\r`. Attackers could inject malicious shell commands by embedding newlines in user input that gets passed to shell-quote's parsing or quoting functions. The fix requires upgrading shell-quote to version 1.9.0 or later, which properly escapes these line terminators before shell execution.

Vulnerability at a Glance

cweCWE-78
fixUpgrade shell-quote to 1.9.0 and add dependency override
riskArbitrary code execution on the server
languageJavaScript/Node.js
root causeshell-quote failed to escape line terminators (\n, \r) in shell command strings
vulnerabilityCommand Injection via Unescaped Line Terminators

Introduction

The shell-quote npm package is a widely-used utility for parsing and quoting shell commands in Node.js applications. It's commonly found in build tools, CLI utilities, and any application that needs to safely construct shell commands from user input. However, a critical flaw in version 1.8.3 and earlier created a dangerous attack vector: line terminators like \n and \r were not being properly escaped, allowing attackers to break out of intended command boundaries and execute arbitrary code.

In this interactive JSONL editor for Claude Code conversation files, shell-quote was present in the dependency tree through the package-lock.json. While the vulnerability wasn't confirmed as directly reachable in application code, its presence in the dependency graph represented a significant risk that warranted immediate remediation.

The Vulnerability Explained

What Makes Line Terminators Dangerous?

When constructing shell commands, certain characters have special meaning. Line terminators (\n, \r, \r\n) tell the shell that one command has ended and another is beginning. If an attacker can inject these characters into a command string, they can effectively "escape" from the intended command and run their own.

Consider how shell-quote might be used:

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

// User provides a filename
const userInput = 'report.txt';
const quoted = shellQuote.quote(['cat', userInput]);
// Expected: "cat 'report.txt'"

With the vulnerable version (1.8.3), an attacker could provide:

const maliciousInput = 'report.txt\nrm -rf /';
const quoted = shellQuote.quote(['cat', maliciousInput]);
// Vulnerable output might become: cat 'report.txt
// rm -rf /'

The newline character breaks the command into two separate commands. The shell would first execute cat 'report.txt (which would fail), and then execute rm -rf / — a catastrophic command that deletes everything on the system.

Attack Scenario for This Application

This JSONL editor handles Claude Code conversation files. Imagine a scenario where:

  1. A user imports a conversation file with a maliciously crafted filename
  2. The application uses shell-quote to construct a command for file operations
  3. The filename contains \nwhoami > /tmp/pwned.txt\n
  4. The vulnerable shell-quote doesn't escape the newlines
  5. The attacker's command executes with the application's privileges

Even if the direct code path wasn't confirmed reachable, the presence of this vulnerability in the dependency tree means any future code changes could inadvertently create an exploitable path.

The Fix

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

Change 1: Update package-lock.json

The package-lock.json was updated to reference the patched version:

Before:

"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==",

After:

"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==",
  "license": "MIT",

Change 2: Add Dependency Override in package.json

Critically, an overrides section was added to package.json:

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

This override is essential because shell-quote might be a transitive dependency (a dependency of a dependency). Without the override, npm might still install the vulnerable version to satisfy another package's requirements. The override forces npm to use version 1.9.0 everywhere in the dependency tree.

How Version 1.9.0 Fixes the Issue

The patched version of shell-quote now properly escapes line terminators before they reach the shell. When processing input containing \n or \r, the library now:

  1. Detects these special characters
  2. Escapes them so they're treated as literal characters, not command separators
  3. Ensures the shell interprets them as part of the string, not as control characters

Prevention & Best Practices

1. Keep Dependencies Updated

Use automated tools to monitor for vulnerable dependencies:

# Check for vulnerabilities
npm audit

# Automatically fix what's possible
npm audit fix

2. Use Dependency Overrides Strategically

When a vulnerability exists in a transitive dependency, use npm's overrides (or yarn's resolutions) to force the patched version:

{
  "overrides": {
    "vulnerable-package": "^patched.version"
  }
}

3. Prefer Safe APIs Over Shell Execution

When possible, avoid shell execution entirely:

// Dangerous: uses shell
const { exec } = require('child_process');
exec(`cat ${filename}`);

// Safer: no shell involved
const { execFile } = require('child_process');
execFile('cat', [filename]);

// Safest: use Node.js APIs directly
const fs = require('fs');
fs.readFile(filename, 'utf8', callback);

4. Implement Defense in Depth

Even with patched dependencies:
- Validate and sanitize all user input
- Use allowlists for acceptable characters in filenames
- Run applications with minimal privileges
- Monitor for suspicious command execution

Key Takeaways

  • Line terminators (\n, \r) are command separators in shells — failing to escape them enables command injection attacks
  • Transitive dependencies need attention too — the overrides field in package.json ensures patched versions are used throughout the dependency tree
  • shell-quote 1.8.3 and earlier are vulnerable — upgrade to 1.9.0 or later immediately
  • Static analysis tools like Trivy can catch known CVEs — integrate them into your CI/CD pipeline
  • Even "not confirmed reachable" vulnerabilities should be fixed — code changes over time, and today's unreachable code path might become reachable tomorrow

How Orbis AppSec Detected This

  • Source: The shell-quote package in the dependency tree, which could receive user-influenced input through any code path that constructs shell commands
  • Sink: The shell-quote library's quote() and parse() functions that interact with shell interpreters
  • Missing control: Proper escaping of line terminators (\n, \r) before shell execution
  • CWE: CWE-78 (Improper Neutralization of Special Elements used in an OS Command)
  • Fix: Upgraded shell-quote from 1.8.3 to 1.9.0 and added a dependency override to enforce the patched version 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 how a seemingly small oversight — failing to escape line terminators — can lead to critical arbitrary code execution vulnerabilities. The shell-quote package is used by thousands of Node.js projects, making this a high-impact vulnerability that required immediate attention.

The fix was straightforward: upgrade to version 1.9.0 and add a dependency override to ensure consistency across the dependency tree. However, this incident reinforces the importance of proactive dependency management, automated vulnerability scanning, and defense-in-depth strategies.

Remember: your application is only as secure as its weakest dependency. Keep your dependencies updated, monitor for CVEs, and always validate user input before it reaches any shell execution context.

References

Frequently Asked Questions

What is command injection?

Command injection is a vulnerability where an attacker can execute arbitrary operating system commands on a server by injecting malicious input into shell commands that aren't properly sanitized.

How do you prevent command injection in Node.js?

Avoid shell execution when possible, use parameterized APIs like `child_process.execFile()` instead of `exec()`, validate and sanitize all user input, and keep dependencies like shell-quote updated to patched versions.

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 patched libraries like shell-quote 1.9.0+) and prefer APIs that don't invoke a shell interpreter.

Can static analysis detect command injection?

Yes, static analysis tools like Trivy, Semgrep, and Snyk can detect known vulnerable dependency versions and dangerous shell execution patterns, though they may not catch all custom command injection scenarios.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

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.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

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 Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot