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

Reply via email to