public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/nodejs22] rawhide: Fix CVE-2027-13149 (npm/brace-expansion)
@ 2026-07-31 8:03 tjuhasz
0 siblings, 0 replies; only message in thread
From: tjuhasz @ 2026-07-31 8:03 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/nodejs22
Branch : rawhide
Commit : acecb42c35b58d4f60bc171a46bcedf55bdac1e7
Author : tjuhasz <tjuhasz@redhat.com>
Date : 2026-07-30T15:46:49+02:00
Stats : +204/-0 in 3 file(s)
URL : https://src.fedoraproject.org/rpms/nodejs22/c/acecb42c35b58d4f60bc171a46bcedf55bdac1e7?branch=rawhide
Log:
Fix CVE-2027-13149 (npm/brace-expansion)
Backport fix for CVE-2026-13149, an unbound recursion
vulnerability in the brace-expansion module vendored by npm.
The upstream TypeScript fix was manually adapted to the
JavaScript version (brace-expansion 2.0.2) shipped in
Node.js 22. Key changes: wrap expand() body in a for-loop
to avoid recursive stack exhaustion on non-expanding {}
groups, defer post-expansion until a brace set is confirmed
to expand (preventing O(2^n) blowup), and compute post
inline for the $ prefix case.
CVE: CVE-2026-13149
Upstream patches:
- https://github.com/juliangruber/brace-expansion/commit/c7e33ec13ac1a684c116720843ce24e208611754.patch
Resolves: rhbz#2494970
---
diff --git a/0004-CVE-2026-25547-braces-expansion.patch b/0004-CVE-2026-25547-braces-expansion.patch
new file mode 100644
index 0000000..726ec71
--- /dev/null
+++ b/0004-CVE-2026-25547-braces-expansion.patch
@@ -0,0 +1,102 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: tjuhasz <tjuhasz@redhat.com>
+Date: Tue, 25 Feb 2026 14:21:26 +0100
+Subject: [PATCH] CVE-2026-25547: Fix brace expansion vulnerability
+
+Add expansion limit to prevent DoS attacks through excessive
+brace expansion in the brace-expansion module.
+
+---
+ deps/npm/node_modules/brace-expansion/index.js | 20 ++++++++++++--------
+ 1 file changed, 12 insertions(+), 8 deletions(-)
+
+diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js
+--- a/deps/npm/node_modules/brace-expansion/index.js 2026-01-12 23:55:24.000000000 +0100
++++ b/deps/npm/node_modules/brace-expansion/index.js 2026-02-25 14:21:26.829483831 +0100
+@@ -8,6 +8,8 @@
+ var escComma = '\0COMMA'+Math.random()+'\0';
+ var escPeriod = '\0PERIOD'+Math.random()+'\0';
+
++const EXPANSION_MAX = 100_000;
++
+ function numeric(str) {
+ return parseInt(str, 10) == str
+ ? parseInt(str, 10)
+@@ -61,9 +63,11 @@
+ return parts;
+ }
+
+-function expandTop(str) {
++function expandTop(str, options = {}) {
+ if (!str)
+ return [];
++
++ const { max = EXPANSION_MAX } = options;
+
+ // I don't know why Bash 4.3 does this, but it does.
+ // Anything starting with {} will have the first two bytes preserved
+@@ -75,7 +79,7 @@
+ str = '\\{\\}' + str.substr(2);
+ }
+
+- return expand(escapeBraces(str), true).map(unescapeBraces);
++ return expand(escapeBraces(str), max, true).map(unescapeBraces);
+ }
+
+ function embrace(str) {
+@@ -92,7 +96,7 @@
+ return i >= y;
+ }
+
+-function expand(str, isTop) {
++function expand(str, max, isTop) {
+ var expansions = [];
+
+ var m = balanced('{', '}', str);
+@@ -101,11 +105,11 @@
+ // 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, false)
++ ? expand(m.post, max, false)
+ : [''];
+
+ if (/\$$/.test(m.pre)) {
+- for (var k = 0; k < post.length; k++) {
++ for (var k = 0; k < post.length && k < max; k++) {
+ var expansion = pre+ '{' + m.body + '}' + post[k];
+ expansions.push(expansion);
+ }
+@@ -118,7 +122,7 @@
+ // {a},b}
+ if (m.post.match(/,(?!,).*\}/)) {
+ str = m.pre + '{' + m.body + escClose + m.post;
+- return expand(str);
++ return expand(str, max, true);
+ }
+ return [str];
+ }
+@@ -130,7 +134,7 @@
+ n = parseCommaParts(m.body);
+ if (n.length === 1) {
+ // x{{a,b}}y ==> x{a}y x{b}y
+- n = expand(n[0], false).map(embrace);
++ n = expand(n[0], max, false).map(embrace);
+ if (n.length === 1) {
+ return post.map(function(p) {
+ return m.pre + n[0] + p;
+@@ -185,12 +189,12 @@
+ N = [];
+
+ for (var j = 0; j < n.length; j++) {
+- N.push.apply(N, expand(n[j], false));
++ N.push.apply(N, expand(n[j], max, false));
+ }
+ }
+
+ for (var j = 0; j < N.length; j++) {
+- for (var k = 0; k < post.length; k++) {
++ for (var k = 0; k < post.length && expansions.length < max; k++) {
+ var expansion = pre + N[j] + post[k];
+ if (!isTop || isSequence || expansion)
+ expansions.push(expansion);
diff --git a/0005-CVE-2026-13149-brace-expansion-unbound-recursion.patch b/0005-CVE-2026-13149-brace-expansion-unbound-recursion.patch
new file mode 100644
index 0000000..80626ce
--- /dev/null
+++ b/0005-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 <redhat-ymir-agent@redhat.com>
+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 3c8a221..0cfa5ee 100644
--- a/nodejs22.spec
+++ b/nodejs22.spec
@@ -169,6 +169,8 @@ Source101: nodejs.srpm.macros
0001-fips-disable-options.patch
0002-CVE-2026-42338-ip-address-security-fix.patch
0003-CVE-2026-59873-CVE-2026-59874-upgrade-bundled-tar-to-7.5.19.patch
+0004-CVE-2026-25547-braces-expansion.patch
+0005-CVE-2026-13149-brace-expansion-unbound-recursion.patch
%description
Node.js is a platform built on Chrome's JavaScript runtime
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-07-31 8:03 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-31 8:03 [rpms/nodejs22] rawhide: Fix CVE-2027-13149 (npm/brace-expansion) tjuhasz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox