Le 05/10/2026 à 11:27, Salvatore Bonaccorso a écrit :
Hi Xavier,
On Sat, Oct 03, 2026 at 07:47:47AM +0200, Xavier wrote:
Le 02/10/2026 à 16:51, Salvatore Bonaccorso a écrit :
Source: node-shell-quote
Version: 1.10.0-1
X-Debbugs-CC: [email protected]
Severity: grave
Tags: security upstream
Hi,
The following vulnerability was published for node-shell-quote.
CVE-2026-102422[0]:
Hi,
here is the debdiff. If you don't consider it as urgent, of course I can
push it to release.debian.org.
We have node-shell-quote ineed in dsa-needed list, and issue
warranting a DSA. But while at it, can you as well include the fix for
the no-dsa marked one, CVE-2026-13311? Or is there a reason we should
rather ignore it?
Regards,
Salvatore
Hi,
done. I also fixed the debdiff, base was not good
diff --git a/debian/changelog b/debian/changelog
index 05e6b17..273622b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+node-shell-quote (1.7.4+~1.7.1-1+deb13u2) trixie; urgency=medium
+
+ * Team upload
+ * Reject line terminators in tokens after a comment
+ (Closes: #1149713, CVE-2026-102422)
+ * Finalize parse tokens in linear time (Closes: #1140921, CVE-2026-13311)
+
+ -- Xavier Guimard <[email protected]> Mon, 05 Oct 2026 14:56:23 +0200
+
node-shell-quote (1.7.4+~1.7.1-1+deb13u1) trixie-security; urgency=medium
* Team upload
diff --git a/debian/patches/CVE-2026-102422.patch
b/debian/patches/CVE-2026-102422.patch
new file mode 100644
index 0000000..c4ef5c8
--- /dev/null
+++ b/debian/patches/CVE-2026-102422.patch
@@ -0,0 +1,83 @@
+Description: reject line terminators in tokens after a comment
+Author: Jordan Harband <[email protected]>
+Origin: upstream, https://github.com/ljharb/shell-quote/commit/6002b2ed
+Bug: https://www.cve.org/CVERecord?id=CVE-2026-102422
+Bug-Debian: https://bugs.debian.org/1149713
+Forwarded: not-needed
+Applied-Upstream: 1.12.0, commit:6002b2ed
+Reviewed-By: Xavier Guimard <[email protected]>
+
+--- a/README.md
++++ b/README.md
+@@ -111,8 +111,9 @@
+ `parse` emits: `{ op }` (where `op` is one of the control operators
+ `||`, `&&`, `;;`, `|&`, `<(`, `<<<`, `>>`, `>&`, `<&`, `&`, `;`, `(`,
+ `)`, `|`, `<`, `>`), `{ op: 'glob', pattern }`, or `{ comment }`. Any
+-other object shape, an unrecognized `op`, or a `pattern`/`comment`
+-containing line terminators throws a `TypeError`.
++other object shape, an unrecognized `op`, a `pattern`/`comment`
++containing line terminators, or a string containing line terminators
++anywhere after a `{ comment }` throws a `TypeError`.
+
+ ## parse(cmd, env={})
+
+--- a/index.js
++++ b/index.js
+@@ -22,7 +22,11 @@
+ var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
+
+ exports.quote = function (xs) {
++ var sawComment = false;
+ return xs.map(function (s) {
++ if (sawComment && typeof s === 'string' &&
LINE_TERMINATORS.test(s)) {
++ throw new TypeError('a token after a `comment` must not
contain line terminators');
++ }
+ if (s && typeof s === 'object') {
+ if (s.op === 'glob') {
+ if (typeof s.pattern !== 'string') {
+@@ -43,6 +47,7 @@
+ if (LINE_TERMINATORS.test(s.comment)) {
+ throw new TypeError('`comment` must not
contain line terminators');
+ }
++ sawComment = true;
+ return '#' + s.comment;
+ }
+ throw new TypeError('unrecognized object token shape');
+--- a/test/quote.js
++++ b/test/quote.js
+@@ -2,6 +2,7 @@
+
+ var test = require('tape');
+ var quote = require('../').quote;
++var parse = require('../').parse;
+
+ test('quote', function (t) {
+ t.equal(quote(['a', 'b', 'c d']), 'a b \'c d\'');
+@@ -98,6 +99,27 @@
+ t.end();
+ });
+
++test('quote comment: rejects line terminators in later tokens', function (t) {
++ t['throws'](function () { quote(['echo', { comment: 'x' }, 'a\nid;#']);
}, TypeError, 'newline after a comment');
++ t['throws'](function () { quote(['echo', { comment: 'x' }, 'a\rb']); },
TypeError, 'CR after a comment');
++ t['throws'](function () { quote(['echo', { comment: 'x' },
'a\u2028b']); }, TypeError, 'U+2028 after a comment');
++ t['throws'](function () { quote(['echo', { comment: 'x' },
'a\u2029b']); }, TypeError, 'U+2029 after a comment');
++ t['throws'](
++ function () { quote(['echo', { comment: 'x' }, 'ok',
'it\'s\nid;#']); },
++ TypeError,
++ 'newline in any later token, not just the next one'
++ );
++ t['throws'](
++ function () { quote(parse('curl
http://x/#frag').concat('a\nid;#')); },
++ TypeError,
++ 'a mid-word `#` from parse, followed by an appended token'
++ );
++
++ t.equal(quote(['echo', 'a\nb', { comment: 'x' }]), 'echo \'a\nb\' #x',
'a line terminator before a comment is fine');
++ t.equal(quote(['echo', { comment: 'x' }, 'y']), 'echo #x y', 'later
tokens without line terminators are unchanged');
++ t.end();
++});
++
+ test('quote rejects unrecognized object shapes', function (t) {
+ t['throws'](function () { quote([{}]); }, TypeError, 'empty object');
+ t['throws'](function () { quote([{ foo: 'bar' }]); }, TypeError,
'unknown key');
diff --git a/debian/patches/CVE-2026-13311.patch
b/debian/patches/CVE-2026-13311.patch
new file mode 100644
index 0000000..37474ea
--- /dev/null
+++ b/debian/patches/CVE-2026-13311.patch
@@ -0,0 +1,80 @@
+Description: `parse`: finalize tokens in linear time
+ The two finalizing reduces used `prev.concat(arg)` as the accumulator,
+ reallocating and copying the whole array each iteration, so `parse()` ran
+ in O(n²) in the token count: a small (~128 KB) input could block the event
+ loop for tens of seconds (CWE-407 DoS).
+ Push into the accumulator instead.
+Author: Jordan Harband <[email protected]>
+Origin: upstream, https://github.com/ljharb/shell-quote/commit/7ff54885
+Bug:
https://github.com/ljharb/shell-quote/security/advisories/GHSA-395f-4hp3-45gv
+Bug-Debian: https://bugs.debian.org/1140921
+Forwarded: not-needed
+Applied-Upstream: 1.9.0, commit:7ff54885
+Reviewed-By: Xavier Guimard <[email protected]>
+Last-Update: 2026-10-05
+
+--- a/index.js
++++ b/index.js
+@@ -223,7 +223,10 @@
+ if (arg === undefined) {
+ return prev;
+ }
+- return prev.concat(arg);
++ [].concat(arg).forEach(function (entry) {
++ prev[prev.length] = entry;
++ });
++ return prev;
+ }, []);
+ }
+
+@@ -234,17 +237,19 @@
+ }
+ return mapped.reduce(function (acc, s) {
+ if (typeof s === 'object') {
+- return acc.concat(s);
++ acc[acc.length] = s;
++ return acc;
+ }
+ var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')',
'g'));
+ if (xs.length === 1) {
+- return acc.concat(xs[0]);
++ acc[acc.length] = xs[0];
++ return acc;
+ }
+- return acc.concat(xs.filter(Boolean).map(function (x) {
+- if (RegExp('^' + TOKEN).test(x)) {
+- return JSON.parse(x.split(TOKEN)[1]);
+- }
+- return x;
+- }));
++ xs.filter(Boolean).forEach(function (x) {
++ acc[acc.length] = RegExp('^' + TOKEN).test(x)
++ ? JSON.parse(x.split(TOKEN)[1])
++ : x;
++ });
++ return acc;
+ }, []);
+ };
+--- a/test/parse.js
++++ b/test/parse.js
+@@ -23,3 +23,20 @@
+
+ t.end();
+ });
++
++test('parse stays linear in token count (GHSA-395f-4hp3-45gv)', function (t) {
++ // the old concat-in-reduce finalizer was O(n^2): this many tokens took
++ // ~minutes, so under the unfixed code this test hangs rather than
passes
++ var n = 2e5;
++ var input = new Array(n + 1).join('x '); // avoid String#repeat for old
engines
++
++ var words = parse(input);
++ t.equal(words.length, n, 'every token is returned');
++ t.equal(words[0], 'x', 'first token is correct');
++ t.equal(words[n - 1], 'x', 'last token is correct');
++
++ var withEnv = parse(input, function () { return 'v'; });
++ t.equal(withEnv.length, n, 'env-function path returns every token');
++
++ t.end();
++});
diff --git a/debian/patches/series b/debian/patches/series
index fba295f..3038468 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,3 @@
CVE-2026-9277.patch
+CVE-2026-102422.patch
+CVE-2026-13311.patch
--
Pkg-javascript-devel mailing list
[email protected]
https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/pkg-javascript-devel