Version in base suite: 1.7.4+~1.7.1-1 Base version: node-shell-quote_1.7.4+~1.7.1-1 Target version: node-shell-quote_1.7.4+~1.7.1-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/node-shell-quote/node-shell-quote_1.7.4+~1.7.1-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/node-shell-quote/node-shell-quote_1.7.4+~1.7.1-1+deb13u1.dsc changelog | 7 + patches/CVE-2026-9277.patch | 155 ++++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 163 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmptsqzxm7a/node-shell-quote_1.7.4+~1.7.1-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmptsqzxm7a/node-shell-quote_1.7.4+~1.7.1-1+deb13u1.dsc: no acceptable signature found diff -Nru node-shell-quote-1.7.4+~1.7.1/debian/changelog node-shell-quote-1.7.4+~1.7.1/debian/changelog --- node-shell-quote-1.7.4+~1.7.1/debian/changelog 2022-11-14 10:19:44.000000000 +0000 +++ node-shell-quote-1.7.4+~1.7.1/debian/changelog 2026-05-23 09:56:08.000000000 +0000 @@ -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 Sat, 23 May 2026 11:56:08 +0200 + node-shell-quote (1.7.4+~1.7.1-1) unstable; urgency=medium * Team upload diff -Nru node-shell-quote-1.7.4+~1.7.1/debian/patches/CVE-2026-9277.patch node-shell-quote-1.7.4+~1.7.1/debian/patches/CVE-2026-9277.patch --- node-shell-quote-1.7.4+~1.7.1/debian/patches/CVE-2026-9277.patch 1970-01-01 00:00:00.000000000 +0000 +++ node-shell-quote-1.7.4+~1.7.1/debian/patches/CVE-2026-9277.patch 2026-05-23 09:56:08.000000000 +0000 @@ -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 +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 +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 -Nru node-shell-quote-1.7.4+~1.7.1/debian/patches/series node-shell-quote-1.7.4+~1.7.1/debian/patches/series --- node-shell-quote-1.7.4+~1.7.1/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ node-shell-quote-1.7.4+~1.7.1/debian/patches/series 2026-05-23 09:49:31.000000000 +0000 @@ -0,0 +1 @@ +CVE-2026-9277.patch