Skip to content

Commit 409c67f

Browse files
[AutoPR- Security] Patch nodejs24 for CVE-2026-33672, CVE-2026-33671 [HIGH] (#16641)
Co-authored-by: Akarsh Chaudhary <v-akarshc@microsoft.com>
1 parent 46f515e commit 409c67f

3 files changed

Lines changed: 398 additions & 1 deletion

File tree

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
From f720787b6ff1ad2479c9c9319a7baf844818de7d Mon Sep 17 00:00:00 2001
2+
From: AllSpark <allspark@microsoft.com>
3+
Date: Mon, 13 Apr 2026 14:33:16 +0000
4+
Subject: [PATCH] Backport: picomatch safeguard against risky quantified
5+
extglobs; add DEFAULT_MAX_EXTGLOB_RECURSION and analyze repeated extglobs in
6+
parse to literalize or simplify where necessary
7+
8+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
9+
Upstream-reference: AI Backport of https://github.com/micromatch/picomatch/commit/5eceecd27543b8e056b9307d69e105ea03618a7d.patch
10+
---
11+
.../node_modules/picomatch/lib/constants.js | 3 +
12+
.../node_modules/picomatch/lib/parse.js | 302 ++++++++++++++++++
13+
2 files changed, 305 insertions(+)
14+
15+
diff --git a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
16+
index 3f7ef7e5..4b5ecc6f 100644
17+
--- a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
18+
+++ b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
19+
@@ -3,6 +3,8 @@
20+
const WIN_SLASH = '\\\\/';
21+
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
22+
23+
+const DEFAULT_MAX_EXTGLOB_RECURSION = 0;
24+
+
25+
/**
26+
* Posix glob regex
27+
*/
28+
@@ -86,6 +88,7 @@ const POSIX_REGEX_SOURCE = {
29+
};
30+
31+
module.exports = {
32+
+ DEFAULT_MAX_EXTGLOB_RECURSION,
33+
MAX_LENGTH: 1024 * 64,
34+
POSIX_REGEX_SOURCE,
35+
36+
diff --git a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/parse.js b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/parse.js
37+
index 8fd8ff49..8c087b3f 100644
38+
--- a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/parse.js
39+
+++ b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/parse.js
40+
@@ -45,6 +45,278 @@ const syntaxError = (type, char) => {
41+
return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
42+
};
43+
44+
+const splitTopLevel = input => {
45+
+ const parts = [];
46+
+ let bracket = 0;
47+
+ let paren = 0;
48+
+ let quote = 0;
49+
+ let value = '';
50+
+ let escaped = false;
51+
+
52+
+ for (const ch of input) {
53+
+ if (escaped === true) {
54+
+ value += ch;
55+
+ escaped = false;
56+
+ continue;
57+
+ }
58+
+
59+
+ if (ch === '\\') {
60+
+ value += ch;
61+
+ escaped = true;
62+
+ continue;
63+
+ }
64+
+
65+
+ if (ch === '"') {
66+
+ quote = quote === 1 ? 0 : 1;
67+
+ value += ch;
68+
+ continue;
69+
+ }
70+
+
71+
+ if (quote === 0) {
72+
+ if (ch === '[') {
73+
+ bracket++;
74+
+ } else if (ch === ']' && bracket > 0) {
75+
+ bracket--;
76+
+ } else if (bracket === 0) {
77+
+ if (ch === '(') {
78+
+ paren++;
79+
+ } else if (ch === ')' && paren > 0) {
80+
+ paren--;
81+
+ } else if (ch === '|' && paren === 0) {
82+
+ parts.push(value);
83+
+ value = '';
84+
+ continue;
85+
+ }
86+
+ }
87+
+ }
88+
+
89+
+ value += ch;
90+
+ }
91+
+
92+
+ parts.push(value);
93+
+ return parts;
94+
+};
95+
+
96+
+const isPlainBranch = branch => {
97+
+ let escaped = false;
98+
+
99+
+ for (const ch of branch) {
100+
+ if (escaped === true) {
101+
+ escaped = false;
102+
+ continue;
103+
+ }
104+
+
105+
+ if (ch === '\\') {
106+
+ escaped = true;
107+
+ continue;
108+
+ }
109+
+
110+
+ if (/[?*+@!()[\]{}]/.test(ch)) {
111+
+ return false;
112+
+ }
113+
+ }
114+
+
115+
+ return true;
116+
+};
117+
+
118+
+const normalizeSimpleBranch = branch => {
119+
+ let value = branch.trim();
120+
+ let changed = true;
121+
+
122+
+ while (changed === true) {
123+
+ changed = false;
124+
+
125+
+ if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
126+
+ value = value.slice(2, -1);
127+
+ changed = true;
128+
+ }
129+
+ }
130+
+
131+
+ if (!isPlainBranch(value)) {
132+
+ return;
133+
+ }
134+
+
135+
+ return value.replace(/\\(.)/g, '$1');
136+
+};
137+
+
138+
+const hasRepeatedCharPrefixOverlap = branches => {
139+
+ const values = branches.map(normalizeSimpleBranch).filter(Boolean);
140+
+
141+
+ for (let i = 0; i < values.length; i++) {
142+
+ for (let j = i + 1; j < values.length; j++) {
143+
+ const a = values[i];
144+
+ const b = values[j];
145+
+ const char = a[0];
146+
+
147+
+ if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
148+
+ continue;
149+
+ }
150+
+
151+
+ if (a === b || a.startsWith(b) || b.startsWith(a)) {
152+
+ return true;
153+
+ }
154+
+ }
155+
+ }
156+
+
157+
+ return false;
158+
+};
159+
+
160+
+const parseRepeatedExtglob = (pattern, requireEnd = true) => {
161+
+ if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') {
162+
+ return;
163+
+ }
164+
+
165+
+ let bracket = 0;
166+
+ let paren = 0;
167+
+ let quote = 0;
168+
+ let escaped = false;
169+
+
170+
+ for (let i = 1; i < pattern.length; i++) {
171+
+ const ch = pattern[i];
172+
+
173+
+ if (escaped === true) {
174+
+ escaped = false;
175+
+ continue;
176+
+ }
177+
+
178+
+ if (ch === '\\') {
179+
+ escaped = true;
180+
+ continue;
181+
+ }
182+
+
183+
+ if (ch === '"') {
184+
+ quote = quote === 1 ? 0 : 1;
185+
+ continue;
186+
+ }
187+
+
188+
+ if (quote === 1) {
189+
+ continue;
190+
+ }
191+
+
192+
+ if (ch === '[') {
193+
+ bracket++;
194+
+ continue;
195+
+ }
196+
+
197+
+ if (ch === ']' && bracket > 0) {
198+
+ bracket--;
199+
+ continue;
200+
+ }
201+
+
202+
+ if (bracket > 0) {
203+
+ continue;
204+
+ }
205+
+
206+
+ if (ch === '(') {
207+
+ paren++;
208+
+ continue;
209+
+ }
210+
+
211+
+ if (ch === ')') {
212+
+ paren--;
213+
+
214+
+ if (paren === 0) {
215+
+ if (requireEnd === true && i !== pattern.length - 1) {
216+
+ return;
217+
+ }
218+
+
219+
+ return {
220+
+ type: pattern[0],
221+
+ body: pattern.slice(2, i),
222+
+ end: i
223+
+ };
224+
+ }
225+
+ }
226+
+ }
227+
+};
228+
+
229+
+const getStarExtglobSequenceOutput = pattern => {
230+
+ let index = 0;
231+
+ const chars = [];
232+
+
233+
+ while (index < pattern.length) {
234+
+ const match = parseRepeatedExtglob(pattern.slice(index), false);
235+
+
236+
+ if (!match || match.type !== '*') {
237+
+ return;
238+
+ }
239+
+
240+
+ const branches = splitTopLevel(match.body).map(branch => branch.trim());
241+
+ if (branches.length !== 1) {
242+
+ return;
243+
+ }
244+
+
245+
+ const branch = normalizeSimpleBranch(branches[0]);
246+
+ if (!branch || branch.length !== 1) {
247+
+ return;
248+
+ }
249+
+
250+
+ chars.push(branch);
251+
+ index += match.end + 1;
252+
+ }
253+
+
254+
+ if (chars.length < 1) {
255+
+ return;
256+
+ }
257+
+
258+
+ const source = chars.length === 1
259+
+ ? utils.escapeRegex(chars[0])
260+
+ : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`;
261+
+
262+
+ return `${source}*`;
263+
+};
264+
+
265+
+const repeatedExtglobRecursion = pattern => {
266+
+ let depth = 0;
267+
+ let value = pattern.trim();
268+
+ let match = parseRepeatedExtglob(value);
269+
+
270+
+ while (match) {
271+
+ depth++;
272+
+ value = match.body.trim();
273+
+ match = parseRepeatedExtglob(value);
274+
+ }
275+
+
276+
+ return depth;
277+
+};
278+
+
279+
+const analyzeRepeatedExtglob = (body, options) => {
280+
+ if (options.maxExtglobRecursion === false) {
281+
+ return { risky: false };
282+
+ }
283+
+
284+
+ const max =
285+
+ typeof options.maxExtglobRecursion === 'number'
286+
+ ? options.maxExtglobRecursion
287+
+ : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
288+
+
289+
+ const branches = splitTopLevel(body).map(branch => branch.trim());
290+
+
291+
+ if (branches.length > 1) {
292+
+ if (
293+
+ branches.some(branch => branch === '') ||
294+
+ branches.some(branch => /^[*?]+$/.test(branch)) ||
295+
+ hasRepeatedCharPrefixOverlap(branches)
296+
+ ) {
297+
+ return { risky: true };
298+
+ }
299+
+ }
300+
+
301+
+ for (const branch of branches) {
302+
+ const safeOutput = getStarExtglobSequenceOutput(branch);
303+
+ if (safeOutput) {
304+
+ return { risky: true, safeOutput };
305+
+ }
306+
+
307+
+ if (repeatedExtglobRecursion(branch) > max) {
308+
+ return { risky: true };
309+
+ }
310+
+ }
311+
+
312+
+ return { risky: false };
313+
+};
314+
+
315+
+
316+
/**
317+
* Parse the given input string.
318+
* @param {String} input
319+
@@ -225,6 +497,8 @@ const parse = (input, options) => {
320+
token.prev = prev;
321+
token.parens = state.parens;
322+
token.output = state.output;
323+
+ token.startIndex = state.index;
324+
+ token.tokensIndex = tokens.length;
325+
const output = (opts.capture ? '(' : '') + token.open;
326+
327+
increment('parens');
328+
@@ -234,6 +508,34 @@ const parse = (input, options) => {
329+
};
330+
331+
const extglobClose = token => {
332+
+ const literal = input.slice(token.startIndex, state.index + 1);
333+
+ const body = input.slice(token.startIndex + 2, state.index);
334+
+ const analysis = analyzeRepeatedExtglob(body, opts);
335+
+
336+
+ if ((token.type === 'plus' || token.type === 'star') && analysis.risky) {
337+
+ const safeOutput = analysis.safeOutput
338+
+ ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput)
339+
+ : undefined;
340+
+ const open = tokens[token.tokensIndex];
341+
+
342+
+ open.type = 'text';
343+
+ open.value = literal;
344+
+ open.output = safeOutput || utils.escapeRegex(literal);
345+
+
346+
+ for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
347+
+ tokens[i].value = '';
348+
+ tokens[i].output = '';
349+
+ delete tokens[i].suffix;
350+
+ }
351+
+
352+
+ state.output = token.output + open.output;
353+
+ state.backtrack = true;
354+
+
355+
+ push({ type: 'paren', extglob: true, value, output: '' });
356+
+ decrement('parens');
357+
+ return;
358+
+ }
359+
+
360+
let output = token.close + (opts.capture ? ')' : '');
361+
let rest;
362+
363+
--
364+
2.45.4
365+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
From 1a67b91ced4ac0d7c90ff429f7cee28ca9c7f038 Mon Sep 17 00:00:00 2001
2+
From: AllSpark <allspark@microsoft.com>
3+
Date: Mon, 13 Apr 2026 14:27:43 +0000
4+
Subject: [PATCH] Backport: add __proto__: null to POSIX_REGEX_SOURCE in
5+
picomatch constants to prevent prototype pollution (from original.patch)
6+
7+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
8+
Upstream-reference: AI Backport of https://github.com/micromatch/picomatch/commit/4516eb521f13a46b2fe1a1d2c9ef6b20ddc0e903.patch
9+
---
10+
.../tinyglobby/node_modules/picomatch/lib/constants.js | 1 +
11+
1 file changed, 1 insertion(+)
12+
13+
diff --git a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
14+
index 3f7ef7e5..2bdb5a07 100644
15+
--- a/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
16+
+++ b/deps/npm/node_modules/tinyglobby/node_modules/picomatch/lib/constants.js
17+
@@ -69,6 +69,7 @@ const WINDOWS_CHARS = {
18+
*/
19+
20+
const POSIX_REGEX_SOURCE = {
21+
+ __proto__: null,
22+
alnum: 'a-zA-Z0-9',
23+
alpha: 'a-zA-Z',
24+
ascii: '\\x00-\\x7F',
25+
--
26+
2.45.4
27+

0 commit comments

Comments
 (0)