Hi,here is a patch for Trixie. Same can be applied to bookworm, just to change dist/version
Best regards, xavier
diff --git a/debian/changelog b/debian/changelog index d3e8cfa..05e6b17 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +node-shell-quote (1.7.4+~1.7.1-1+deb13u1) trixie-security; urgency=medium + + * Team upload + * Validate object-token shapes (Closes: #1137372, CVE-2026-9277) + + -- Xavier Guimard <[email protected]> Sat, 23 May 2026 11:56:08 +0200 + node-shell-quote (1.7.4+~1.7.1-1) unstable; urgency=medium * Team upload diff --git a/debian/patches/CVE-2026-9277.patch b/debian/patches/CVE-2026-9277.patch new file mode 100644 index 0000000..86f765d --- /dev/null +++ b/debian/patches/CVE-2026-9277.patch @@ -0,0 +1,155 @@ +Description: [PATCH] [Fix] `quote`: validate object-token shapes + The per-character `.op` escape (`/(.)/g`) did not match line terminators + (`\n`, `\r`, U+2028, U+2029), + so a literal newline in `.op` passed through unescaped and acted as a shell + command separator. + Replace the regex with strict shape validation: + - `{ op }`: `.op` must match the parser's control-operator allowlist + - `{ op: 'glob', pattern }`: `.pattern` must be a string without line + terminators; glob metas pass through, shell-special chars are escaped + (Previously the field was discarded and the literal `\g\l\o\b` was emitted) + - `{ comment }`: `.comment` must be a string without line terminators + (Previously `quote` crashed on `{ comment }` tokens that `parse` emits) + - Any other object shape: `TypeError` +Author: Jordan Harband <[email protected]> +Origin: upstream, https://github.com/ljharb/shell-quote/commit/4378a6e6 +Bug: https://github.com/ljharb/shell-quote/security/advisories/GHSA-w7jw-789q-3m8p +Bug-Debian: https://bugs.debian.org/1137372 +Forwarded: not-needed +Applied-Upstream: 1.8.4, commit:4378a6e6 +Reviewed-By: Yadd <[email protected]> +Last-Update: 2026-05-23 + +--- a/README.md ++++ b/README.md +@@ -107,6 +107,13 @@ + Return a quoted string for the array `args` suitable for using in shell + commands. + ++Each entry of `args` may be a string, or one of the object shapes that ++`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`. ++ + ## parse(cmd, env={}) + + Return an array of arguments from the quoted string `cmd`. +--- a/index.js ++++ b/index.js +@@ -1,9 +1,51 @@ + 'use strict'; + ++var OPS = [ ++ '||', ++ '&&', ++ ';;', ++ '|&', ++ '<(', ++ '<<<', ++ '>>', ++ '>&', ++ '<&', ++ '&', ++ ';', ++ '(', ++ ')', ++ '|', ++ '<', ++ '>' ++]; ++var LINE_TERMINATORS = /[\n\r\u2028\u2029]/; ++var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g; ++ + exports.quote = function (xs) { + return xs.map(function (s) { + if (s && typeof s === 'object') { +- return s.op.replace(/(.)/g, '\\$1'); ++ if (s.op === 'glob') { ++ if (typeof s.pattern !== 'string') { ++ throw new TypeError('glob token requires a string `pattern`'); ++ } ++ if (LINE_TERMINATORS.test(s.pattern)) { ++ throw new TypeError('glob `pattern` must not contain line terminators'); ++ } ++ return s.pattern.replace(GLOB_SHELL_SPECIAL, '\\$&'); ++ } ++ if (typeof s.op === 'string') { ++ if (OPS.indexOf(s.op) < 0) { ++ throw new TypeError('invalid `op` value: ' + JSON.stringify(s.op)); ++ } ++ return s.op.replace(/[\s\S]/g, '\\$&'); ++ } ++ if (typeof s.comment === 'string') { ++ if (LINE_TERMINATORS.test(s.comment)) { ++ throw new TypeError('`comment` must not contain line terminators'); ++ } ++ return '#' + s.comment; ++ } ++ throw new TypeError('unrecognized object token shape'); + } else if ((/["\s]/).test(s) && !(/'/).test(s)) { + return "'" + s.replace(/(['\\])/g, '\\$1') + "'"; + } else if ((/["'\s]/).test(s)) { +--- a/test/quote.js ++++ b/test/quote.js +@@ -48,3 +48,59 @@ + t.equal(quote([x]), '\\`\\:\\\\a\\\\b'); + t.end(); + }); ++ ++test('quote ops: allowlist', function (t) { ++ var ops = ['||', '&&', ';;', '|&', '<(', '<<<', '>>', '>&', '<&', '&', ';', '(', ')', '|', '<', '>']; ++ for (var i = 0; i < ops.length; i++) { ++ var op = ops[i]; ++ var expected = ''; ++ for (var j = 0; j < op.length; j++) { expected += '\\' + op.charAt(j); } ++ t.equal(quote([{ op: op }]), expected, 'op ' + op); ++ } ++ t.end(); ++}); ++ ++test('quote ops: rejects line terminators (GHSA-w7jw-789q-3m8p)', function (t) { ++ t['throws'](function () { quote([{ op: ';\nid' }]); }, TypeError, 'newline in op'); ++ t['throws'](function () { quote([{ op: ';\rid' }]); }, TypeError, 'carriage return in op'); ++ t['throws'](function () { quote([{ op: ';\u2028id' }]); }, TypeError, 'U+2028 in op'); ++ t['throws'](function () { quote([{ op: ';\u2029id' }]); }, TypeError, 'U+2029 in op'); ++ t.end(); ++}); ++ ++test('quote ops: rejects non-allowlisted values', function (t) { ++ t['throws'](function () { quote([{ op: '' }]); }, TypeError, 'empty op'); ++ t['throws'](function () { quote([{ op: 'foo' }]); }, TypeError, 'arbitrary string'); ++ t['throws'](function () { quote([{ op: '|||' }]); }, TypeError, 'near-miss'); ++ t['throws'](function () { quote([{ op: 42 }]); }, TypeError, 'non-string op'); ++ t.end(); ++}); ++ ++test('quote glob pattern', function (t) { ++ t.equal(quote([{ op: 'glob', pattern: 'test/*.test.js' }]), 'test/*.test.js'); ++ t.equal(quote([{ op: 'glob', pattern: '?ab' }]), '?ab'); ++ t.equal(quote([{ op: 'glob', pattern: '[ab]c' }]), '[ab]c'); ++ t.equal(quote([{ op: 'glob', pattern: '{a,b}' }]), '{a,b}'); ++ t.equal(quote([{ op: 'glob', pattern: 'my dir/*.txt' }]), 'my\\ dir/*.txt'); ++ t.equal(quote([{ op: 'glob', pattern: 'a$b' }]), 'a\\$b'); ++ t['throws'](function () { quote([{ op: 'glob' }]); }, TypeError, 'missing pattern'); ++ t['throws'](function () { quote([{ op: 'glob', pattern: 'a\nb' }]); }, TypeError, 'newline in pattern'); ++ t['throws'](function () { quote([{ op: 'glob', pattern: 'a\u2028b' }]); }, TypeError, 'U+2028 in pattern'); ++ t.end(); ++}); ++ ++test('quote comment', function (t) { ++ t.equal(quote(['echo', 'hi', { comment: ' a comment' }]), 'echo hi # a comment'); ++ t.equal(quote([{ comment: '' }]), '#'); ++ t['throws'](function () { quote([{ comment: 'a\nb' }]); }, TypeError, 'newline in comment'); ++ t['throws'](function () { quote([{ comment: 'a\rb' }]); }, TypeError, 'CR in comment'); ++ t['throws'](function () { quote([{ comment: 'a\u2028b' }]); }, TypeError, 'U+2028 in comment'); ++ 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'); ++ t['throws'](function () { quote([{ op: null }]); }, TypeError, 'null op'); ++ t.end(); ++}); diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..fba295f --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +CVE-2026-9277.patch

