diff --git a/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch b/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch new file mode 100644 index 0000000..80626ce --- /dev/null +++ b/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch @@ -0,0 +1,100 @@ +From 213dc171de329a09f34c7a3222cad723ee65d693 Mon Sep 17 00:00:00 2001 +From: RHEL Packaging Agent +Date: Tue, 14 Jul 2026 08:27:05 +0000 +Subject: [PATCH] CVE-2026-13149: Fix unbound recursion in brace-expansion + +A run of non-expanding {} groups expanded post once per group before the +early returns that never use it, doubling the work on every group. expand() +ran in O(2^n) and blocked for minutes on a ~90 byte input. + +Defer expanding post until a brace set is known to expand, and turn the +{a},b} restart into a loop so a long run of {} groups can't exhaust the +call stack. + +Adapted from upstream TypeScript fix to the JavaScript version (2.0.2) +vendored in Node.js. + +Upstream: https://github.com/juliangruber/brace-expansion/commit/c7e33ec +--- + .../npm/node_modules/brace-expansion/index.js | 49 ++++++++++++------- + 1 file changed, 32 insertions(+), 17 deletions(-) + +diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js +index d084bac4..5d5f61ff 100644 +--- a/deps/npm/node_modules/brace-expansion/index.js ++++ b/deps/npm/node_modules/brace-expansion/index.js +@@ -99,21 +99,27 @@ function gte(i, y) { + function expand(str, max, isTop) { + var expansions = []; + +- var m = balanced('{', '}', str); +- if (!m) return [str]; +- +- // no need to expand pre, since it is guaranteed to be free of brace-sets +- var pre = m.pre; +- var post = m.post.length +- ? expand(m.post, max, false) +- : ['']; +- +- if (/\$$/.test(m.pre)) { +- for (var k = 0; k < post.length && k < max; k++) { +- var expansion = pre+ '{' + m.body + '}' + post[k]; +- expansions.push(expansion); ++ // The {a},b} rewrite below restarts expansion on a rewritten string with ++ // the same max and isTop = true. Loop instead of recursing so a long run ++ // of non-expanding {} groups can't exhaust the call stack. ++ for (;;) { ++ var m = balanced('{', '}', str); ++ if (!m) return [str]; ++ ++ // no need to expand pre, since it is guaranteed to be free of brace-sets ++ var pre = m.pre; ++ ++ if (/\$$/.test(m.pre)) { ++ var post = m.post.length ++ ? expand(m.post, max, false) ++ : ['']; ++ for (var k = 0; k < post.length && k < max; k++) { ++ var expansion = pre+ '{' + m.body + '}' + post[k]; ++ expansions.push(expansion); ++ } ++ return expansions; + } +- } else { ++ + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; +@@ -122,11 +128,20 @@ function expand(str, max, isTop) { + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; +- return expand(str, max, true); ++ isTop = true; ++ continue; + } + return [str]; + } + ++ // Only expand post once we know this brace set actually expands. Computing ++ // it before the early returns above expanded post a second time on every ++ // non-expanding {}, which is what made inputs like a{},{},{}... blow up ++ // exponentially. ++ var post = m.post.length ++ ? expand(m.post, max, false) ++ : ['']; ++ + var n; + if (isSequence) { + n = m.body.split(/\.\./); +@@ -200,8 +215,8 @@ function expand(str, max, isTop) { + expansions.push(expansion); + } + } +- } + +- return expansions; ++ return expansions; ++ } + } + diff --git a/nodejs22.spec b/nodejs22.spec index e62e1db..2b87d1d 100644 --- a/nodejs22.spec +++ b/nodejs22.spec @@ -182,6 +182,7 @@ Patch: 0001-CVE-2026-25547-braces-expansion.patch # npm deps patches Patch: 0002-CVE-2026-42338-npm-ip-address-security-fix.patch Patch: 0003-CVE-2026-59873-CVE-2026-59874-upgrade-bundled-tar-to-7.5.19.patch +Patch: 0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch %if 0%{?nodejs_default} %global pkgname nodejs