Introduction
A flaw in Babel's SystemJS module transform plugin allows specially crafted JavaScript source code to produce compiled output containing arbitrary executable code, turning the build process itself into an attack vector. With roughly 367 million annual npm downloads for babel-core and a four year window of affected versions, this vulnerability sits squarely in the supply chain risk category that security teams have been increasingly focused on.
CVE-2026-44728 carries a CVSS 8.2 (HIGH) score and affects @babel/plugin-transform-modules-systemjs from version 7.12.0 through 7.29.3, as well as the 8.x alpha line from 8.0.0-alpha.0 through 8.0.0-alpha.12. The vulnerability is classified under both CWE-94 (Code Injection) and CWE-843 (Type Confusion), reflecting two distinct weakness mechanisms that converge on the same outcome.
Technical Information
Vulnerable Component
The vulnerable component is @babel/plugin-transform-modules-systemjs, a Babel plugin responsible for transforming ECMAScript module syntax (import/export statements and import() expressions) into SystemJS compatible format. This plugin is also included in @babel/preset-env when configured with the modules: "systemjs" option, meaning projects using preset-env with that setting are transitively affected. No other plugins under the @babel/ namespace are impacted by this specific vulnerability.
Root Cause
The root cause lies in the constructExportCall function inside packages/babel-plugin-transform-modules-systemjs/src/index.ts. When Babel encounters export names that are string specifiers (a feature introduced in ES2022, e.g., export {foo as "default exports"}), the vulnerable code path unconditionally used t.identifier(exportName) to build MemberExpression AST nodes.
The problem is that t.identifier() does not sanitize or validate the string content it receives. An identifier node in the AST is meant to hold valid JavaScript identifiers, not arbitrary strings. When a carefully crafted string export name is passed through t.identifier(), it can break out of the intended AST structure entirely. The resulting compiled output would contain the attacker's string interpreted as raw JavaScript code rather than as a quoted property name.
For example, consider an export like export {foo as "malicious;payload()//"}. In the vulnerable code path, this string would be placed into an identifier node and rendered as dot notation in the output: _exportObj.malicious;payload()//. The semicolon terminates the intended statement, payload() executes as a function call, and // comments out the rest of the line. The compiled output now contains executable code that was never present in the original source.
This is why the vulnerability carries both CWE-94 (Code Injection) and CWE-843 (Type Confusion) classifications. The type confusion occurs when a string specifier (which should be treated as a string literal) is incorrectly processed as an identifier. The code injection is the consequence: attacker controlled data influences the structure of the generated output code.
CVSS Vector Breakdown
The CVSS vector CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H reveals several important characteristics:
| Component | Value | Meaning |
|---|---|---|
| Attack Vector | Local (AV:L) | Attacker must provide code to the Babel compiler locally or through a pipeline |
| Attack Complexity | Low (AC:L) | No special conditions required beyond submitting crafted code |
| Privileges Required | Low (PR:L) | Minimal access needed to submit source for compilation |
| User Interaction | Required (UI:R) | A user or automated process must compile the malicious code |
| Scope | Changed (S:C) | Impact extends beyond the vulnerable component |
| Confidentiality | High (C:H) | Total information disclosure possible |
| Integrity | High (I:H) | Total system integrity compromise possible |
| Availability | High (A:H) | Total availability disruption possible |
The "Scope: Changed" designation is particularly significant. It indicates that compromise of the build toolchain can cascade into production environments: the malicious code injected during compilation would be present in the final bundled output served to users or executed on servers.
Attack Flow
-
Craft malicious source: The attacker creates a JavaScript file containing an export statement with a carefully constructed string specifier, such as
export {foo as "crafted_payload"}, where the payload string is designed to produce valid, executable JavaScript when incorrectly placed into an identifier position. -
Submit for compilation: The attacker submits this file to a system that compiles it using Babel with the SystemJS module transform enabled. This could be a CI/CD pipeline processing pull requests, a build server handling dependency updates, or any workflow that compiles third party code.
-
Babel processes the input: During the AST transformation phase,
@babel/plugin-transform-modules-systemjsencounters the string export specifier and passes it tot.identifier()instead oft.stringLiteral(). -
Malicious output generated: The compiled output contains the attacker's payload as executable JavaScript code. When this output is subsequently loaded or executed (in a browser, Node.js runtime, or test environment), the injected code runs with the privileges of the execution context.
Key Constraint
The advisory explicitly states that this vulnerability only affects users who compile untrusted or third party code. Projects that strictly control their input source code and never compile external contributions face significantly lower risk. This distinction is critical for risk assessment and prioritization.
Patch Information
The Babel team addressed CVE-2026-44728 through a targeted fix in commit 5abf7bb (PR #17973), authored by Huáng Jùnliàng (@JLHwung) and reviewed by Nicolò Ribaudo (@nicolo-ribaudo). The fix was published on May 5, 2026, and the advisory (GHSA-fv7c-fp4j-7gwp) was formally released on May 8, 2026.
The fix is precise and addresses the root cause directly. It introduces a check for whether the export name is a string specifier, and when true, wraps the name in t.stringLiteral() (which properly escapes the string) and marks the MemberExpression as computed. Here is the critical portion of the diff:
// In constructExportCall — the member expression branch: for (let i = 0; i < exportNames.length; i++) { const exportName = exportNames[i]; + const computed = stringSpecifiers.has(exportName); + const exportNameNode = computed + ? t.stringLiteral(exportName) + : t.identifier(exportName); const exportValue = exportValues[i]; statements.push( @@ -140,7 +140,8 @@ function constructExportCall( "=", t.memberExpression( t.identifier(exportObj), - t.identifier(exportName), + exportNameNode, + computed, ), exportValue, ),
This means the generated code now uses bracket notation with a properly escaped string literal (_exportObj["default exports"]) instead of the unsafe dot notation (_exportObj.default exports), which would have caused the injected string to be interpreted as executable code.
A parallel fix was applied in the same commit to the object property construction path, ensuring consistent safe handling in both code generation branches:
for (let i = 0; i < exportNames.length; i++) { const exportName = exportNames[i]; + const exportNameNode = stringSpecifiers.has(exportName) + ? t.stringLiteral(exportName) + : t.identifier(exportName); const exportValue = exportValues[i]; - objectProperties.push( - t.objectProperty( - stringSpecifiers.has(exportName) - ? t.stringLiteral(exportName) - : t.identifier(exportName), - exportValue, - ), - ); + objectProperties.push(t.objectProperty(exportNameNode, exportValue));
The commit also includes new test fixtures that validate correct output for string name exports combined with export * re-exports, specifically testing cases like export {foo as "default exports", bar} from "./other.mjs" alongside export * from "./other.mjs".
Fixed Versions
| Package | Fixed Version |
|---|---|
@babel/plugin-transform-modules-systemjs | 7.29.4 (for 7.x) |
@babel/plugin-transform-modules-systemjs | 8.0.0-alpha.13 (for 8.x alpha) |
@babel/preset-env | 7.29.5 (pulls in the patched plugin) |
Upgrading can be done via npm install @babel/[email protected] or npm audit fix.
Ecosystem Response
Multiple high profile projects issued dependency bumps within days of the advisory:
| Project | Action | Date |
|---|---|---|
| axios | Bumped plugin to 7.29.4 | May 9, 2026 |
| MetaMask Snaps | Dependency bump to 7.29.4 | May 9, 2026 |
| Apache (mailing list PR) | Bumped from 7.18.6 to 7.29.4 | May 9, 2026 |
| OpenGeoscience/geojs | Bumped from 7.29.0 to 7.29.4 | Post advisory |
Affected Systems and Versions
The following packages and version ranges are affected:
| Package | Affected Range | Fixed Version |
|---|---|---|
@babel/plugin-transform-modules-systemjs | >= 7.12.0 and < 7.29.4 | 7.29.4 |
@babel/plugin-transform-modules-systemjs | >= 8.0.0-alpha.0 and < 8.0.0-alpha.13 | 8.0.0-alpha.13 |
@babel/preset-env (with modules: "systemjs") | Same ranges (transitive) | 7.29.5 |
The vulnerability was introduced in version 7.12.0, which added support for ES2022 string name specifiers. This means the exposure window spans approximately four years of releases across the v7 line.
Vulnerable configurations: Any project is affected if it meets both of the following conditions:
- Uses
@babel/plugin-transform-modules-systemjsdirectly, or uses@babel/preset-envwith themodules: "systemjs"option. - Compiles untrusted or third party source code through Babel.
Projects using CommonJS, AMD, or native ES module output are not affected by this specific vulnerability. Projects that compile only their own trusted source code are at significantly lower risk.
To identify affected configurations, audit Babel configuration files (.babelrc, babel.config.js, babel.config.json) for the modules: "systemjs" setting or direct references to @babel/plugin-transform-modules-systemjs.
Vendor Security History
CVE-2026-44728 is the second arbitrary code execution vulnerability discovered in Babel in recent years. The prior vulnerability, CVE-2023-45133, affected @babel/traverse, a core component of the Babel compilation pipeline, and was exploitable through the path.evaluate() function when processing crafted input.
| CVE ID | Component | CWE | Fixed Version |
|---|---|---|---|
| CVE-2023-45133 | @babel/traverse | CWE-697, CWE-184 | 7.23.2, 8.0.0-alpha.4 |
| CVE-2026-44728 | @babel/plugin-transform-modules-systemjs | CWE-94, CWE-843 | 7.29.4, 8.0.0-alpha.13 |
A pattern emerges from these two vulnerabilities: both enable arbitrary code execution when Babel compiles specially crafted input, but they reside in different components and involve different root causes. CVE-2023-45133 affected the core traversal engine used by many plugins, while CVE-2026-44728 is isolated to the SystemJS module transform. The different CWE classifications (improper comparison and incomplete input filtering vs. code injection and type confusion) indicate distinct weakness mechanisms converging on the same outcome.
Babel is maintained by a small group of volunteers rather than a corporate entity with dedicated security staff. The original maintainer described working on Babel as a side project alongside his role as a JavaScript Engineer at Adobe. While the project responded promptly with fixes in both the v7 and v8 alpha channels for CVE-2026-44728, the four year exposure window highlights the challenge of proactively identifying such vulnerabilities in a volunteer maintained codebase. The Shenlong CVE Platform lists 4 total CVEs for the Babel product.
Organizations that depend on Babel should anticipate that further compilation time vulnerabilities may be discovered and should implement defense in depth around their build toolchains accordingly.
References
- GitHub Advisory GHSA-fv7c-fp4j-7gwp
- Babel Security Advisory GHSA-fv7c-fp4j-7gwp
- GitHub Advisory API
- Fix Commit 5abf7bb
- GitLab Advisory for CVE-2026-44728
- Feedly CVE Tracking: CVE-2026-44728
- NVD: CVE-2023-45133 (prior Babel vulnerability)
- axios Issue #10870: Security bump for CVE-2026-44728
- MetaMask Snaps Dependency Bump
- Apache Mailing List: Dependency Bump PR
- OpenGeoscience/geojs Dependency Bump
- Babel Official Documentation: plugin-transform-modules-systemjs
- Babel GitHub Repository
- Babel Security Policy
- OpenSearch Dashboards Issue #11930



