VYPR
Medium severity6.2NVD Advisory· Published Mar 11, 2025· Updated Apr 15, 2026

CVE-2025-27789

CVE-2025-27789

Description

Babel is a compiler for writing next generation JavaScript. When using versions of Babel prior to 7.26.10 and 8.0.0-alpha.17 to compile regular expression named capturing groups, Babel will generate a polyfill for the .replace method that has quadratic complexity on some specific replacement pattern strings (i.e. the second argument passed to .replace). Generated code is vulnerable if all the following conditions are true: Using Babel to compile regular expression named capturing groups, using the .replace method on a regular expression that contains named capturing groups, and the code using untrusted strings as the second argument of .replace. This problem has been fixed in @babel/helpers and @babel/runtime 7.26.10 and 8.0.0-alpha.17. It's likely that individual users do not directly depend on @babel/helpers, and instead depend on @babel/core (which itself depends on @babel/helpers). Upgrading to @babel/core 7.26.10 is not required, but it guarantees use of a new enough @babel/helpers version. Note that just updating Babel dependencies is not enough; one will also need to re-compile the code. No known workarounds are available.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@babel/helpersnpm
< 7.26.107.26.10
@babel/runtimenpm
< 7.26.107.26.10
@babel/runtime-corejs2npm
< 7.26.107.26.10
@babel/runtime-corejs3npm
< 7.26.107.26.10
@babel/helpersnpm
>= 8.0.0-alpha.0, < 8.0.0-alpha.178.0.0-alpha.17
@babel/runtimenpm
>= 8.0.0-alpha.0, < 8.0.0-alpha.178.0.0-alpha.17
@babel/runtime-corejs2npm
>= 8.0.0-alpha.0, < 8.0.0-alpha.178.0.0-alpha.17
@babel/runtime-corejs3npm
>= 8.0.0-alpha.0, < 8.0.0-alpha.178.0.0-alpha.17

Patches

3
d5952e80c0fa

Fix processing of replacement pattern with named capture groups (#17173)

https://github.com/babel/babelmmmsssttt404Mar 11, 2025via ghsa
5 files changed · +146 14
  • packages/babel-helpers/src/helpers-generated.ts+2 2 modified
    @@ -1414,10 +1414,10 @@ const helpers: Record<string, Helper> = {
           },
         },
       ),
    -  // size: 1163, gzip size: 540
    +  // size: 1213, gzip size: 560
       wrapRegExp: helper(
         "7.19.0",
    -    'function _wrapRegExp(){_wrapRegExp=function(e,r){return new BabelRegExp(e,void 0,r)};var e=RegExp.prototype,r=new WeakMap;function BabelRegExp(e,t,p){var o=RegExp(e,t);return r.set(o,p||r.get(e)),setPrototypeOf(o,BabelRegExp.prototype)}function buildGroups(e,t){var p=r.get(t);return Object.keys(p).reduce((function(r,t){var o=p[t];if("number"==typeof o)r[t]=e[o];else{for(var i=0;void 0===e[o[i]]&&i+1<o.length;)i++;r[t]=e[o[i]]}return r}),Object.create(null))}return inherits(BabelRegExp,RegExp),BabelRegExp.prototype.exec=function(r){var t=e.exec.call(this,r);if(t){t.groups=buildGroups(t,this);var p=t.indices;p&&(p.groups=buildGroups(p,this))}return t},BabelRegExp.prototype[Symbol.replace]=function(t,p){if("string"==typeof p){var o=r.get(this);return e[Symbol.replace].call(this,t,p.replace(/\\$<([^>]+)>/g,(function(e,r){var t=o[r];return"$"+(Array.isArray(t)?t.join("$"):t)})))}if("function"==typeof p){var i=this;return e[Symbol.replace].call(this,t,(function(){var e=arguments;return"object"!=typeof e[e.length-1]&&(e=[].slice.call(e)).push(buildGroups(e,i)),p.apply(this,e)}))}return e[Symbol.replace].call(this,t,p)},_wrapRegExp.apply(this,arguments)}',
    +    'function _wrapRegExp(){_wrapRegExp=function(e,r){return new BabelRegExp(e,void 0,r)};var e=RegExp.prototype,r=new WeakMap;function BabelRegExp(e,t,p){var o=RegExp(e,t);return r.set(o,p||r.get(e)),setPrototypeOf(o,BabelRegExp.prototype)}function buildGroups(e,t){var p=r.get(t);return Object.keys(p).reduce((function(r,t){var o=p[t];if("number"==typeof o)r[t]=e[o];else{for(var i=0;void 0===e[o[i]]&&i+1<o.length;)i++;r[t]=e[o[i]]}return r}),Object.create(null))}return inherits(BabelRegExp,RegExp),BabelRegExp.prototype.exec=function(r){var t=e.exec.call(this,r);if(t){t.groups=buildGroups(t,this);var p=t.indices;p&&(p.groups=buildGroups(p,this))}return t},BabelRegExp.prototype[Symbol.replace]=function(t,p){if("string"==typeof p){var o=r.get(this);return e[Symbol.replace].call(this,t,p.replace(/\\$<([^>]+)(>|$)/g,(function(e,r,t){if(""===t)return e;var p=o[r];return Array.isArray(p)?"$"+p.join("$"):"number"==typeof p?"$"+p:""})))}if("function"==typeof p){var i=this;return e[Symbol.replace].call(this,t,(function(){var e=arguments;return"object"!=typeof e[e.length-1]&&(e=[].slice.call(e)).push(buildGroups(e,i)),p.apply(this,e)}))}return e[Symbol.replace].call(this,t,p)},_wrapRegExp.apply(this,arguments)}',
         {
           globals: ["RegExp", "WeakMap", "Object", "Symbol", "Array"],
           locals: {
    
  • packages/babel-helpers/src/helpers/wrapRegExp.ts+15 9 modified
    @@ -5,7 +5,7 @@ import inherits from "./inherits.ts";
     
     // Define interfaces for clarity and type safety
     interface GroupMap {
    -  [key: string]: string | [number, number];
    +  [key: string]: number | [number, number];
     }
     
     declare class BabelRegExp extends RegExp {
    @@ -72,9 +72,18 @@ export default function _wrapRegExp(this: any): RegExp {
           ).call(
             this,
             str,
    -        substitution.replace(/\$<([^>]+)>/g, function (_, name) {
    -          var group = groups[name];
    -          return "$" + (Array.isArray(group) ? group.join("$") : group);
    +        substitution.replace(/\$<([^>]+)(>|$)/g, function (match, name, end) {
    +          if (end === "") {
    +            // return unterminated group name as-is
    +            return match;
    +          } else {
    +            var group = groups[name];
    +            return Array.isArray(group)
    +              ? "$" + group.join("$")
    +              : typeof group === "number"
    +                ? "$" + group
    +                : "";
    +          }
             }),
           );
         } else if (typeof substitution === "function") {
    @@ -116,13 +125,10 @@ export default function _wrapRegExp(this: any): RegExp {
           if (typeof i === "number") groups[name] = result[i];
           else {
             var k = 0;
    -        while (
    -          result[(i as [number, number])[k]] === undefined &&
    -          k + 1 < i.length
    -        ) {
    +        while (result[i[k]] === undefined && k + 1 < i.length) {
               k++;
             }
    -        groups[name] = result[(i as [number, number])[k]];
    +        groups[name] = result[i[k]];
           }
           return groups;
         }, Object.create(null));
    
  • packages/babel-runtime-corejs3/helpers/esm/wrapRegExp.js+4 3 modified
    @@ -42,9 +42,10 @@ function _wrapRegExp() {
       }, BabelRegExp.prototype[_Symbol$replace] = function (t, p) {
         if ("string" == typeof p) {
           var o = r.get(this);
    -      return e[_Symbol$replace].call(this, t, p.replace(/\$<([^>]+)>/g, function (e, r) {
    -        var t = o[r];
    -        return "$" + (_Array$isArray(t) ? t.join("$") : t);
    +      return e[_Symbol$replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) {
    +        if ("" === t) return e;
    +        var p = o[r];
    +        return _Array$isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : "";
           }));
         }
         if ("function" == typeof p) {
    
  • packages/babel-runtime/test/package.json+3 0 added
    @@ -0,0 +1,3 @@
    +{
    +  "type": "module"
    +}
    
  • packages/babel-runtime/test/unittests/wrapRegExp.test.js+122 0 added
    @@ -0,0 +1,122 @@
    +import wrapRegExp from "../../helpers/wrapRegExp.js";
    +
    +describe("wrapRegExp", () => {
    +  describe("should handle maliciously crafted substitutions", () => {
    +    it("$<$<...$<group", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foofoo";
    +      const replacement = "$<".repeat(1e5) + "group";
    +      const startTime = Date.now();
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      // This test will fail if 2000ms is passed
    +      const timeTaken = Date.now() - startTime;
    +      expect(timeTaken).toBeLessThan(2000);
    +      expect(result).toBe(replacement + "foo");
    +    });
    +
    +    it("$<$<...$<group>", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foofoo";
    +      const replacement = "$<".repeat(1e5) + "group>";
    +      const startTime = Date.now();
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      // This test will fail if 2000ms is passed
    +      const timeTaken = Date.now() - startTime;
    +      expect(timeTaken).toBeLessThan(2000);
    +      expect(result).toBe("foo");
    +    });
    +
    +    it("$<g$<g...$<group", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foofoo";
    +      const replacement = "$<g".repeat(1e5) + "group";
    +      const startTime = Date.now();
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      // This test will fail if 2000ms is passed
    +      const timeTaken = Date.now() - startTime;
    +      expect(timeTaken).toBeLessThan(2000);
    +      expect(result).toBe(replacement + "foo");
    +    });
    +
    +    it("$<g$<g...$<group>", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foofoo";
    +      const replacement = "$<g".repeat(1e5) + "group>";
    +      const startTime = Date.now();
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      // This test will fail when 2000ms is passed
    +      const timeTaken = Date.now() - startTime;
    +      expect(timeTaken).toBeLessThan(2000);
    +      expect(result).toBe("foo");
    +    });
    +
    +    it("$<_$>", () => {
    +      const pattern = "(foo)";
    +      const groups = { _$: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<_$>$<_$>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("foofoobar");
    +    });
    +
    +    it("$<hasOwnProperty>", () => {
    +      const pattern = "(foo)";
    +      const groups = { hasOwnProperty: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<hasOwnProperty>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("foobar");
    +    });
    +
    +    it("$<__proto__>", () => {
    +      const pattern = "(foo)";
    +      const groups = { ["__proto__"]: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<__proto__>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("foobar");
    +    });
    +  });
    +  describe("substitutions", () => {
    +    it("unknown group", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<UNKNOWN>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("bar");
    +    });
    +
    +    it("$<hasOwnProperty> - unknown group", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<hasOwnProperty>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("bar");
    +    });
    +
    +    it("$<__proto__> -- unknown group", () => {
    +      const pattern = "(foo)";
    +      const groups = { group: 1 };
    +      const myRegExp = wrapRegExp(pattern, groups);
    +      const targetStr = "foobar";
    +      const replacement = "$<__proto__>";
    +      const result = myRegExp[Symbol.replace](targetStr, replacement);
    +      expect(result).toBe("bar");
    +    });
    +  });
    +});
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.