CVE-2026-9277
ADVISORY - githubSummary
Summary
shell-quote's quote() function did not validate object-token inputs against the operator model used by parse(). The .op field was backslash-escaped character by character using /(.)/g, which in JavaScript does not match line terminators (\n, \r, U+2028, U+2029). A line terminator in .op therefore passed through unescaped into the output; POSIX shells treat a literal \n as a command separator, so any content after it would execute as a second command.
The vulnerable code path is reachable in two ways. Neither requires the parser to misbehave — parse() only emits ops from a fixed control set — but both are documented API surface:
- Direct construction. A caller builds
{ op: '...\n...' }from external input (e.g. a deserialized argument array) and passes it toquote(). envFnreturn.parse(cmd, envFn)is documented to splice the return value ofenvFninto the result array when it is an object. An attacker-influenced data source consulted byenvFncan introduce an object token whose.opreachesquote().
Impact
Shell command injection in callers that pass object tokens with attacker-influenced .op values to quote() and then hand the result to a shell. The preconditions are narrower than ordinary string injection — they require the caller to feed object tokens into quote() — but object tokens are a public, documented part of the API surface, and quote() is intended to be a shell-safety boundary.
PoC
const { parse, quote } = require('shell-quote');
// Direct construction
quote([{ op: ';\nid' }]);
// → "\;\n\\i\\d" ← literal newline; second line executes as a command
// Via parse() with an envFn returning attacker-shaped objects
const tokens = parse('echo $X', () => ({ op: ';\nid' }));
require('child_process').execSync(quote(tokens), { shell: true });
// Executes `id` after `echo \;`.
Confirmed under sh, bash, dash, and zsh.
Patch
Fixed by replacing the per-character escape with strict shape validation in quote(). The object-token branch now:
{ op }—.opmust be a string from the same allowlist the parser emits (||,&&,;;,|&,<(,<<<,>>,>&,<&,&,;,(,),|,<,>). Anything else throwsTypeError. This is the direct fix for the reported issue and removes the entire class of.opinjection.{ op: 'glob', pattern }—.patternmust be a string with no line terminators. Glob metacharacters (*,?,[,],{,},,) pass through; all other shell-special characters are backslash-escaped. (Previously the pattern field was discarded entirely and the literal string\g\l\o\bwas emitted — a latent bug, not security-relevant.){ comment }—.commentmust be a string with no line terminators (line terminators would end the shell comment and resume command parsing — same injection shape).- Any other object shape —
TypeError.
The fix is allowlist-based rather than a targeted regex tweak, so it closes the reported vector and forecloses adjacent ones (U+2028 / U+2029 line separators in .op, line terminators in comments, unknown-shape objects coerced through .replace).
Workarounds
Prior to upgrading, callers that build object tokens from untrusted input should validate .op against the parser's operator set themselves, and never construct { op } from attacker-controlled strings.
Credits
Reported by Akshat Sinha
NIST
CVSS SCORE
9.2criticalGitHub
CVSS SCORE
9.2criticalDebian
-
Ubuntu
-
CVSS SCORE
N/AmediumChainguard
CGA-qhgj-wh56-w944
-
minimos
MINI-9q99-vmjw-hjxw
-
minimos
MINI-c98h-4vqh-8jrm
-
minimos
MINI-hg4r-84vf-w9rp
-
minimos
MINI-m2r9-43xq-wxjm
-
minimos
MINI-v87r-r2cf-c3x3
-