VYPR
Low severity3.2GHSA Advisory· Published Jun 15, 2026· Updated Jun 15, 2026

@babel/core: Arbitrary File Read via sourceMappingURL Comment

CVE-2026-49356

Description

@babel/core before 7.29.6 and 8.0.0-rc.6 allows arbitrary source-map file read via a crafted #sourceMappingURL comment when compiling untrusted code.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

@babel/core before 7.29.6 and 8.0.0-rc.6 allows arbitrary source-map file read via a crafted #sourceMappingURL comment when compiling untrusted code.

Vulnerability

CVE-2026-49356 is a low-severity vulnerability in @babel/core (npm package, versions <= 7.29.0 and >= 8.0.0-alpha.0, < 8.0.0-rc.5). When compiling untrusted input code, Babel follows the #sourceMappingURL comment to load a source map from the filesystem. An attacker who controls the input source code can set this comment to an arbitrary file path, causing Babel to read that file as a source map [2]. The bug is in the source-map resolution logic, reachable whenever Babel processes user-supplied code. Only users who compile untrusted code are affected [3].

Exploitation

To exploit, an attacker must: (1) control the input source code passed to @babel/core (e.g., by submitting a pull request with a malicious file, or via a CI pipeline that compiles user-contributed code); (2) be able to read the output source code (e.g., the compiled output is made visible to the attacker); and (3) know the path of the source map file they wish to read on the system running Babel. The attacker inserts a #sourceMappingURL=</path/to/target> comment in the source code. When Babel processes the file, it follows the URL to read the target file (e.g., /etc/passwd) as a source map, and embeds its contents into the output source map (or output code) under certain conditions. The attacker then extracts the file content from the output [2].

Impact

If successfully exploited, the attacker can read any arbitrary file on the system that the Babel process has read access to, by tricking Babel into treating that file as a source map. The file content is leaked through the compiled output or generated source map, compromising confidentiality. The attacker does not gain the ability to modify files or execute arbitrary code. The privilege level is that of the Babel process itself (typically the user running the build). The vulnerability is limited to scenarios where untrusted source code is compiled and the attacker can observe the compilation result [2][3].

Mitigation

The vulnerability has been patched in @babel/core@7.29.6 and @babel/core@8.0.0-rc.6 [2]. Users who cannot upgrade should set `inputSourceMap: false` in their Babel configuration to disable automatic source-map loading [1][2]. As a more robust workaround, callers can manually extract the #sourceMappingURL comment, validate whether the referenced path is allowed, and pass the validated path as an object to inputSourceMap (or false if invalid). No CISA KEV listing has been published as of the disclosure date [3].

AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

2
99f498a9b9fa

[7.x packport]Improve input source map handling (#18001)

https://github.com/babel/babelHuáng JùnliàngMay 22, 2026Fixed in 7.29.6via llm-release-walk
41 files changed · +1872 698
  • eslint.config.mts+0 1 modified
    @@ -2,7 +2,6 @@ import babelParser from "@babel/eslint-parser/experimental-worker";
     import globals from "globals";
     import js from "@eslint/js";
     import { defineConfig } from "eslint/config";
    -// @ts-expect-error no types
     import pluginImport from "eslint-plugin-import";
     import pluginJest from "eslint-plugin-jest";
     import pluginN from "eslint-plugin-n";
    
  • packages/babel-core/package.json+3 1 modified
    @@ -41,9 +41,11 @@
         "./lib/config/files/index.js": "./lib/config/files/index-browser.js",
         "./lib/config/resolve-targets.js": "./lib/config/resolve-targets-browser.js",
         "./lib/transform-file.js": "./lib/transform-file-browser.js",
    +    "./lib/transformation/read-input-source-map-file.js": "./lib/transformation/read-input-source-map-file-browser.js",
         "./src/config/files/index.ts": "./src/config/files/index-browser.ts",
         "./src/config/resolve-targets.ts": "./src/config/resolve-targets-browser.ts",
    -    "./src/transform-file.ts": "./src/transform-file-browser.ts"
    +    "./src/transform-file.ts": "./src/transform-file-browser.ts",
    +    "./src/transformation/read-input-source-map-file.ts": "./src/transformation/read-input-source-map-file-browser.ts"
       },
       "dependencies": {
         "@babel/code-frame": "workspace:^",
    
  • packages/babel-core/src/transformation/normalize-file.ts+7 9 modified
    @@ -1,12 +1,11 @@
    -import fs from "node:fs";
    -import path from "node:path";
     import buildDebug from "debug";
     import type { Handler } from "gensync";
     import { file, traverseFast } from "@babel/types";
     import type * as t from "@babel/types";
     import type { PluginPasses } from "../config/index.ts";
     import convertSourceMap from "convert-source-map";
     import type { SourceMapConverter as Converter } from "convert-source-map";
    +import readInputSourceMapFile from "./read-input-source-map-file.ts";
     import File from "./file/file.ts";
     import parser from "../parser/index.ts";
     import cloneDeep from "./util/clone-deep.ts";
    @@ -82,14 +81,13 @@ export default function* normalizeFile(
           if (typeof options.filename === "string" && lastComment) {
             try {
               // when `lastComment` is non-null, EXTERNAL_SOURCEMAP_REGEX must have matches
    -          const match: [string, string] = EXTERNAL_SOURCEMAP_REGEX.exec(
    -            lastComment,
    -          ) as any;
    -          const inputMapContent = fs.readFileSync(
    -            path.resolve(path.dirname(options.filename), match[1]),
    -            "utf8",
    +          const inputMapURL: string =
    +            EXTERNAL_SOURCEMAP_REGEX.exec(lastComment)[1];
    +          inputMap = readInputSourceMapFile(
    +            options.filename,
    +            options.root,
    +            inputMapURL,
               );
    -          inputMap = convertSourceMap.fromJSON(inputMapContent);
             } catch (err) {
               debug("discarding unknown file input sourcemap", err);
             }
    
  • packages/babel-core/src/transformation/read-input-source-map-file-browser.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export default function readInputSourceMapFile(): never {
    +  throw new Error(
    +    "Reading input source map files is not supported in browsers",
    +  );
    +}
    
  • packages/babel-core/src/transformation/read-input-source-map-file.ts+89 0 added
    @@ -0,0 +1,89 @@
    +import fs from "node:fs";
    +import path from "node:path";
    +import buildDebug from "debug";
    +import convertSourceMap from "convert-source-map";
    +import type { SourceMapConverter } from "convert-source-map";
    +
    +const debug = buildDebug("babel:transform:file");
    +
    +// Revised from https://github.com/sindresorhus/find-up-simple/blob/f10133c55dcbf36f84a246c6f1bbfed178dcb774/index.js#L36
    +// for Node.js 6 compatibility
    +function findUpSync(
    +  name: string,
    +  {
    +    cwd,
    +    stopAt,
    +  }: {
    +    cwd?: string;
    +    stopAt?: string;
    +  } = {},
    +) {
    +  let directory = path.resolve(cwd || "");
    +  const { root } = path.parse(directory);
    +  stopAt = path.resolve(directory, stopAt || root);
    +  const isAbsoluteName = path.isAbsolute(name);
    +
    +  while (directory) {
    +    const filePath = isAbsoluteName ? name : path.join(directory, name);
    +
    +    try {
    +      const stats = fs.statSync(filePath);
    +      if (stats.isFile()) {
    +        return filePath;
    +      }
    +    } catch (_) {}
    +
    +    if (directory === stopAt || directory === root) {
    +      break;
    +    }
    +
    +    directory = path.dirname(directory);
    +  }
    +}
    +
    +function getInputMapPath(
    +  filename: string,
    +  root: string,
    +  inputMapURL: string,
    +): string | null {
    +  const inputFileDir = path.dirname(filename);
    +  const inputMapPath = path.resolve(inputFileDir, inputMapURL);
    +  const relativeToInputFileDir = path.relative(inputFileDir, inputMapPath);
    +
    +  if (
    +    relativeToInputFileDir.startsWith("..") ||
    +    path.isAbsolute(relativeToInputFileDir)
    +  ) {
    +    const inputPackageJSONPath = findUpSync("package.json", {
    +      cwd: inputFileDir,
    +      stopAt: root,
    +    });
    +    const inputFileRoot = inputPackageJSONPath
    +      ? path.dirname(inputPackageJSONPath)
    +      : root;
    +    const relativeInputMapPath = path.relative(inputFileRoot, inputMapPath);
    +    if (
    +      relativeInputMapPath.startsWith("..") ||
    +      path.isAbsolute(relativeInputMapPath)
    +    ) {
    +      debug(
    +        `discarding input sourcemap "${inputMapPath}" outside of package root "${inputFileRoot}"`,
    +      );
    +      return null;
    +    }
    +  }
    +  return inputMapPath;
    +}
    +
    +export default function readInputSourceMapFile(
    +  filename: string,
    +  root: string,
    +  inputMapURL: string,
    +): SourceMapConverter | null {
    +  const inputMapPath = getInputMapPath(filename, root, inputMapURL);
    +  if (inputMapPath) {
    +    const inputMapContent = fs.readFileSync(inputMapPath, "utf8");
    +    return convertSourceMap.fromJSON(inputMapContent);
    +  }
    +  return null;
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules/babel.config.input.js.json+7 0 added
    @@ -0,0 +1,7 @@
    +{
    +  "sourceMaps": true,
    +  "inputSourceMap": true,
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules/node_modules/corp-lib/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules/node_modules/corp-lib/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-lib"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-url-not-start-with-double-dot/babel.config.input.js.json+7 0 added
    @@ -0,0 +1,7 @@
    +{
    +  "sourceMaps": true,
    +  "inputSourceMap": true,
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-url-not-start-with-double-dot/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-url-not-start-with-double-dot/node_modules/corp-lib/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=sub/../../../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-url-not-start-with-double-dot/node_modules/corp-lib/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-lib"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-url-not-start-with-double-dot/node_modules/corp-lib/sub/mod.js+1 0 added
    @@ -0,0 +1 @@
    +// Intentional empty file to keep the sub directory tracked in git.
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-within-root/babel.config.input.js.json+7 0 added
    @@ -0,0 +1,7 @@
    +{
    +  "sourceMaps": true,
    +  "inputSourceMap": true,
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-within-root/root/sub/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-within-root/root/sub/node_modules/corp-lib/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-node-modules-within-root/root/sub/node_modules/corp-lib/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-lib"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root/root/sub/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root-within-node-modules/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root-within-node-modules/node_modules/corp-app/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root-within-node-modules/node_modules/corp-app/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-app"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/invalid-relative-up-across-root-within-node-modules/node_modules/corp-app/root/sub/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=input.js.map
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down-into-node-modules/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down-into-node-modules/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=node_modules/corp-source-maps/input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down-into-node-modules/node_modules/corp-source-maps/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-down-into-node-modules/node_modules/corp-source-maps/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-source-maps"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-node-modules/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-node-modules/node_modules/corp-lib/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-node-modules/node_modules/corp-lib/package.json+4 0 added
    @@ -0,0 +1,4 @@
    +{
    +  "private": true,
    +  "name": "corp-lib"
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-node-modules/node_modules/corp-lib/sub/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../input.js.map
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-root/babel.config.input.js.json+5 0 added
    @@ -0,0 +1,5 @@
    +{
    +  "generatorOpts": {
    +    "compact": true
    +  }
    +}
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-root/root/input.js.map+1 0 added
    @@ -0,0 +1 @@
    +{"version":3,"file":"input.js","names":["Msg"],"sources":["input.ts"],"sourcesContent":["const enum Msg {\n  ThisMapShouldBeLoaded = \"This map should be loaded\"\n}\n"],"mappings":"IAAWA,GAAG,0BAAHA,GAAG;EAAHA,GAAG;EAAA,OAAHA,GAAG;AAAA,EAAHA,GAAG","ignoreList":[]}
    \ No newline at end of file
    
  • packages/babel-core/test/fixtures/input-source-map-resolution/relative-up-within-root/root/sub/input.js+6 0 added
    @@ -0,0 +1,6 @@
    +var Msg = /*#__PURE__*/function (Msg) {
    +  Msg["ThisMapShouldBeLoaded"] = "This map should be loaded";
    +  return Msg;
    +}(Msg || {});
    +
    +//# sourceMappingURL=../input.js.map
    
  • packages/babel-core/test/input-source-map-resolution.js+385 0 added
    @@ -0,0 +1,385 @@
    +import * as babel from "../lib/index.js";
    +import convertSourceMap from "convert-source-map";
    +import path from "node:path";
    +import fs from "node:fs";
    +import { commonJS } from "$repo-utils";
    +
    +const { __dirname } = commonJS(import.meta.url);
    +
    +// @see packages/babel-core/src/transformation/normalize-file.ts
    +const EXTERNAL_SOURCEMAP_REGEX =
    +  /^\/\/[@#][ \t]+sourceMappingURL=([^\s'"`]+)[ \t]*$/m;
    +
    +function dirTreeDisplay(dirPath, indent = "") {
    +  let result = "";
    +  const entries = fs.readdirSync(dirPath);
    +  for (let i = 0; i < entries.length; i++) {
    +    const entry = entries[i];
    +    const entryPath = path.join(dirPath, entry);
    +    // readdirSync { withFileTypes: true } does not follow symlinks
    +    const stats = fs.lstatSync(entryPath);
    +    const isLast = i === entries.length - 1;
    +    result += indent + (isLast ? "└─ " : "├─ ") + entry + "\n";
    +    if (stats.isDirectory()) {
    +      result += dirTreeDisplay(entryPath, indent + (isLast ? "   " : "│  "));
    +    }
    +  }
    +  return result;
    +}
    +
    +function getInputSourceMapURL(inputFilePath) {
    +  const inputCode = fs.readFileSync(inputFilePath, "utf8");
    +  const match = inputCode.match(EXTERNAL_SOURCEMAP_REGEX);
    +  return match ? match[1] : null;
    +}
    +
    +function verifyInputFileSourceMapExistence(inputFilePath) {
    +  const sourceMapURL = getInputSourceMapURL(inputFilePath);
    +  const sourceMapPath = path.resolve(path.dirname(inputFilePath), sourceMapURL);
    +  const sourceMap = convertSourceMap
    +    .fromJSON(fs.readFileSync(sourceMapPath, "utf8"))
    +    .toObject();
    +  expect(sourceMap.sourcesContent).toHaveLength(1);
    +  expect(sourceMap.sourcesContent[0]).toContain("const enum Msg");
    +}
    +
    +function expectInputSourceMapIsApplied(result) {
    +  expect(result.map.sourcesContent).toHaveLength(1);
    +  expect(result.map.sourcesContent[0]).toContain("const enum Msg");
    +  expect(result.map.sourcesContent[0]).toContain("This map should be loaded");
    +}
    +
    +function expectInputSourceMapIsPresentButNotApplied(result, inputFilePath) {
    +  expect(result.map.sourcesContent).toHaveLength(1);
    +  expect(result.map.sourcesContent[0]).not.toContain("const enum Msg");
    +  verifyInputFileSourceMapExistence(inputFilePath);
    +}
    +
    +describe("input source map resolution", function () {
    +  const base = path.join(__dirname, "fixtures/input-source-map-resolution");
    +
    +  let cwd;
    +
    +  beforeEach(function () {
    +    cwd = process.cwd();
    +    process.chdir(base);
    +  });
    +
    +  afterEach(function () {
    +    process.chdir(cwd);
    +  });
    +
    +  it("should resolve input source map downward relative to the input file", function () {
    +    const cwd = path.join(base, "relative-down");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(cwd, "input.js");
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      ├─ input.js
    +      └─ input.js.map
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsApplied(result);
    +  });
    +
    +  it("should resolve input source map downward across package.json boundaries", function () {
    +    const cwd = path.join(base, "relative-down-into-node-modules");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(cwd, "input.js");
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      ├─ input.js
    +      └─ node_modules
    +         └─ corp-source-maps
    +            ├─ input.js.map
    +            └─ package.json
    +      "
    +    `);
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"node_modules/corp-source-maps/input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsApplied(result);
    +  });
    +
    +  it("should resolve input source map upward within the root boundary", function () {
    +    const cwd = path.join(base, "relative-up-within-root");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(cwd, "root", "sub", "input.js");
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      └─ root
    +         ├─ input.js.map
    +         └─ sub
    +            └─ input.js
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      root: path.join(cwd, "root"),
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsApplied(result);
    +  });
    +
    +  it("should resolve input source map upward within the package.json boundary", function () {
    +    const cwd = path.join(base, "relative-up-within-node-modules");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(
    +      cwd,
    +      "node_modules",
    +      "corp-lib",
    +      "sub",
    +      "input.js",
    +    );
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      └─ node_modules
    +         └─ corp-lib
    +            ├─ input.js.map
    +            ├─ package.json
    +            └─ sub
    +               └─ input.js
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsApplied(result);
    +  });
    +
    +  it("should not resolve input source map upward across the package.json boundary", function () {
    +    const cwd = path.join(base, "invalid-relative-up-across-node-modules");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(
    +      cwd,
    +      "node_modules",
    +      "corp-lib",
    +      "input.js",
    +    );
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      ├─ input.js.map
    +      └─ node_modules
    +         └─ corp-lib
    +            ├─ input.js
    +            └─ package.json
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsPresentButNotApplied(result, inputFilePath);
    +  });
    +
    +  it("should not resolve input source map upward across the root boundary", function () {
    +    const cwd = path.join(base, "invalid-relative-up-across-root");
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(process.cwd(), "root", "sub", "input.js");
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      ├─ input.js.map
    +      └─ root
    +         └─ sub
    +            └─ input.js
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      root: path.join(cwd, "root"),
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsPresentButNotApplied(result, inputFilePath);
    +  });
    +
    +  it("should not resolve input source map upward across the root boundary and within the package.json boundary", function () {
    +    const cwd = path.join(
    +      base,
    +      "invalid-relative-up-across-root-within-node-modules",
    +    );
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(
    +      cwd,
    +      "node_modules",
    +      "corp-app",
    +      "root",
    +      "sub",
    +      "input.js",
    +    );
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      └─ node_modules
    +         └─ corp-app
    +            ├─ input.js.map
    +            ├─ package.json
    +            └─ root
    +               └─ sub
    +                  └─ input.js
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      root: path.join(cwd, "node_modules", "corp-app", "root"),
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsPresentButNotApplied(result, inputFilePath);
    +  });
    +
    +  it("should not resolve input source map upward across the package.json boundary and within the root boundary", function () {
    +    const cwd = path.join(
    +      base,
    +      "invalid-relative-up-across-node-modules-within-root",
    +    );
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(
    +      cwd,
    +      "root",
    +      "sub",
    +      "node_modules",
    +      "corp-lib",
    +      "input.js",
    +    );
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      └─ root
    +         └─ sub
    +            ├─ input.js.map
    +            └─ node_modules
    +               └─ corp-lib
    +                  ├─ input.js
    +                  └─ package.json
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"../../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      root: path.join(cwd, "root"),
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsPresentButNotApplied(result, inputFilePath);
    +  });
    +
    +  it("should not resolve input source map across the package.json boundary using url not starting with ../", function () {
    +    const cwd = path.join(
    +      base,
    +      "invalid-relative-up-across-node-modules-url-not-start-with-double-dot",
    +    );
    +    process.chdir(cwd);
    +
    +    const inputFilePath = path.join(
    +      cwd,
    +      "node_modules",
    +      "corp-lib",
    +      "input.js",
    +    );
    +
    +    expect(dirTreeDisplay(cwd)).toMatchInlineSnapshot(`
    +      "├─ babel.config.input.js.json
    +      ├─ input.js.map
    +      └─ node_modules
    +         └─ corp-lib
    +            ├─ input.js
    +            ├─ package.json
    +            └─ sub
    +               └─ mod.js
    +      "
    +    `);
    +
    +    expect(getInputSourceMapURL(inputFilePath)).toMatchInlineSnapshot(
    +      `"sub/../../../input.js.map"`,
    +    );
    +
    +    const result = babel.transformFileSync(inputFilePath, {
    +      babelrc: false,
    +      configFile: path.join(cwd, "./babel.config.input.js.json"),
    +      inputSourceMap: true,
    +      sourceMap: true,
    +    });
    +
    +    expectInputSourceMapIsPresentButNotApplied(result, inputFilePath);
    +  });
    +});
    
  • yarn.lock+1244 687 modified
713d478674cd

v7.29.1

https://github.com/babel/babelBabel BotFeb 4, 2026Fixed in 7.29.1via release-tag
3 files changed · +3 3
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "babel",
    -  "version": "7.29.0",
    +  "version": "7.29.1",
       "version_babel8": "8.0.0-beta.3",
       "private": true,
       "type": "commonjs",
    
  • packages/babel-generator/package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "@babel/generator",
    -  "version": "7.29.0",
    +  "version": "7.29.1",
       "description": "Turns an AST into code.",
       "author": "The Babel Team (https://babel.dev/team)",
       "license": "MIT",
    
  • packages/babel-standalone/package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "@babel/standalone",
    -  "version": "7.29.0",
    +  "version": "7.29.1",
       "description": "Standalone build of Babel for use in non-Node.js environments.",
       "main": "./babel.js",
       "files": [
    

Vulnerability mechanics

Root cause

"Missing path boundary validation when resolving `#sourceMappingURL` comments allows reading source map files outside the project root or package directory."

Attack vector

An attacker who controls the input source code can embed a `#sourceMappingURL` comment that points to an arbitrary file on the filesystem. When `@babel/core` compiles the code with `inputSourceMap: true`, the old code in `normalize-file.ts` resolved the URL relative to the input file's directory without checking whether the resulting path stays within the project root or package boundary. If the attacker also has access to the compiled output (e.g., the build artifact), they can exfiltrate the contents of any source map file whose path they know, because Babel would read and embed it into the output source map. The fix introduces a new `getInputMapPath` function that rejects source map URLs that traverse upward beyond the nearest `package.json` or the configured `root` directory [patch_id=6084654].

What the fix does

The patch replaces the inline `fs.readFileSync(path.resolve(...))` call in `normalize-file.ts` with a call to the new `readInputSourceMapFile` function. That function delegates to `getInputMapPath`, which computes the resolved source map path and checks whether it stays within the package boundary (found by walking up for `package.json`) or the `root` directory. If the resolved path would escape either boundary (i.e., `path.relative` starts with `..`), the function returns `null` and the source map is discarded. This prevents an attacker from using `../` sequences in the `#sourceMappingURL` comment to read source maps located outside the intended project scope [patch_id=6084654].

Preconditions

  • inputThe attacker must control the input source code that Babel compiles.
  • authThe attacker must be able to read the output source code produced by Babel.
  • inputThe attacker must know the filesystem path of the target source map file.
  • configThe Babel configuration must have `inputSourceMap` enabled (the default behavior when a `#sourceMappingURL` comment is present).

Generated on Jun 15, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.