fix(deps): update dependency shell-quote to ^1.10.0 [security] - #736
Open
dependency-control-plane[bot] wants to merge 2 commits into
Open
fix(deps): update dependency shell-quote to ^1.10.0 [security]#736dependency-control-plane[bot] wants to merge 2 commits into
dependency-control-plane[bot] wants to merge 2 commits into
Conversation
Author
Edited/Blocked NotificationRenovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR. You can manually request rebase by checking the rebase/retry box above. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
^1.8.3→^1.10.0shell-quote quote() does not escape newlines in object .op values
CVE-2026-9277 / GHSA-w7jw-789q-3m8p
More information
Details
Summary
shell-quote'squote()function did not validate object-token inputs against the operator model used byparse(). The.opfield 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.optherefore passed through unescaped into the output; POSIX shells treat a literal\nas 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:{ 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
.opvalues toquote()and then hand the result to a shell. The preconditions are narrower than ordinary string injection — they require the caller to feed object tokens intoquote()— but object tokens are a public, documented part of the API surface, andquote()is intended to be a shell-safety boundary.PoC
Confirmed under
sh,bash,dash, andzsh.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).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
.opagainst the parser's operator set themselves, and never construct{ op }from attacker-controlled strings.Credits
Reported by Akshat Sinha
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
shell-quote: Quadratic-complexity Denial of Service in
parse()(CWE-407)CVE-2026-13311 / GHSA-395f-4hp3-45gv
More information
Details
Summary
shell-quote'sparse()finalizes its token list with areducethat usesArray.prototype.concatas the accumulator. Eachprev.concat(arg)copies the entire growingarray, so
parse()runs in O(n²) in the number of tokens. An unauthenticated attacker whocan submit a string to any code path that calls
parse()on it can block the single-threadedNode.js event loop for tens of seconds with a small input — a denial of service. The trigger
needs no shell metacharacters (plain space-separated words suffice), so input filters that
only screen for
;,|,$, or backticks do not help.Root cause
parse.js(lines 200–203), inparseInternal— this path runs on everyparse()call:prev.concat(arg)allocates a new array and copies all ofprevon every iteration, soproducing an N-token result costs
1 + 2 + … + N = O(N²)copies. A secondacc.concat(s)reduce in the
module.exportswrapper (lines 211–224, reached only whenenvis a function)has the same shape. The maintainer's own
// TODO: replace this whole reduce with a concatalready flags the construct.
Proof of Concept
Measured on
[email protected], Node v24:parse()Time grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s;

~256 KB → ~57 s; a few hundred KB more → minutes.
poc.js
Impact
parse()is synchronous on the main thread; while it copies arrays quadratically the entireevent loop is blocked and the process serves no other requests. Any service that calls
parse()on attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs,
build-script / arg-string splitters) can be driven to a sustained DoS with a single small
request. No code execution and no data disclosure — availability only.
End-to-end confirmation: a minimal HTTP server that calls
parse()on the request body, hitwith one
POSTof'x '.repeat(32000)(~63 KB), froze for ~4.5 s. An out-of-process probeclient issuing harmless
GET /pingrequests (normally ~1 ms) observed 27 consecutive pingsstalled by up to 4374 ms during that single request — i.e. every concurrent client was denied
service for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes.
This is the same class as several accepted 2026 advisories for quadratic-parser DoS on
untrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550,
python-multipart CVE-2026-53539). It is distinct from the known
shell-quotecommand-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in
quote(), notparse().Suggested remediation
Replace the O(n²) concat-in-reduce with a linear flatten that pushes into the
accumulator instead of reallocating and copying it on every iteration. Apply the
same shape to the wrapper's
acc.concat(s)reduce. A defensive input-length cap onparse()is a cheap additional stop-gap.Disclosure
Found by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is
present on
main). Reported privately here; no public disclosure until a fix is available.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
ljharb/shell-quote (shell-quote)
v1.10.0Compare Source
Merged
parse: add opt-insplitUnquotedoption for shell field-splitting of unquoted expansions#1Commits
parse: match nested${...}braces so nested parameter expansion is consumed as one substitutionc0842c8parse: pin single-quote literalness and unmatched-quote handlinga0d03e32116fa3quote: pin conservative escaping of=,@,^,,,:,!(#11)1c36f3fquoteoutputs POSIX quoting, notcmd.exe/PowerShell100e96eparse's supported parameter-expansion subsete1c75cdparse: a backslash inside single quotes must not escape the closing quote5d460a32de86f5quote: pin that a backslash with whitespace is not doubled in single quotes (#14)190e236quote: use output verbatim; do not re-quote it (#11)1b36468parse: fix swappedSINGLE_QUOTE/DOUBLE_QUOTEvariable names801af5c59bbf8b@arethetypeswrong/cli,evalmda04d475@arethetypeswrong/ci,eslintd390f9aquote: the tilde test escapes every~, not just a leading one (#9)617d119v1.9.0Compare Source
Commits
dca6e21eslint9aa9e8fparse: finalize tokens in linear time (GHSA-395f-4hp3-45gv)7ff548875e8497@types/esrecurse3fb739dnpm installon Windows to survive npm 2/3 staging-rename flakeabe0163b4bafa2quote: escape leading~to prevent shell tilde-expansion7a76c1aauto-changelog,tape7184b44jackspeakis no longer in the graph9ba368av1.8.4Compare Source
Commits
quote: validate object-token shapes4378a6e@ljharb/eslint-config,auto-changelog,eslint,npmignore22ebec09f3caa33344a04@ljharb/eslint-config699c511Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate CLI.