Input validation (URL parsing) in source map handling for dynamic imports

MEDIUM
nodejs/node
Commit: afc30b71a8f0
Affected: pre-25.9.0 (Node.js 25.x releases prior to 25.9.0)
2026-04-05 11:51 UTC

Description

The commit hardens parsing of sourceMappingURL for dynamic imports. Previously, code resolved the source map URL using new URL(sourceMappingURL, sourceURL).href, which could incorrectly accept malformed URLs and proceed to load source maps, potentially leading to crashes or unsafe behavior during dynamic imports. The fix introduces a dedicated URL parser (URLParse) and a null-check to reject invalid URLs early, preventing processing of invalid source maps and reducing attack surface. An accompanying test validates that invalid URLs are tolerated rather than causing crashes.

Proof of Concept

/* PoC: invalid sourceMappingURL handling in dynamic imports (pre-fix vulnerability path) */ // Run in Node.js with an ES module environment const moduleSources = [ `//# sourceMappingURL=unknown-protocol://invalid/url\n//# sourceURL=unknown-protocol://invalid/url`, `//# sourceMappingURL=file://invalid:/file/url\n//# sourceURL=file://invalid:/file/url`, `//# sourceMappingURL=invalid-url\n//# sourceURL=invalid-url`, ]; for (const src of moduleSources) { const b64 = Buffer.from(src).toString('base64'); import(`data:text/javascript;base64,${b64}`) .then(() => console.log('import ok')) .catch((e) => console.error('import failed', e)); }

Commit Details

Author: Chengzhong Wu

Date: 2026-03-03 20:35 UTC

Message:

lib: fix source map url parse in dynamic imports PR-URL: https://github.com/nodejs/node/pull/61990 Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>

Triage Assessment

Vulnerability Type: Input validation (URL parsing)

Confidence: MEDIUM

Reasoning:

The commit changes how source map URLs are parsed for dynamic imports, adding a null-check and using a dedicated URL parser to validate URLs. This prevents invalid/ Malformed URLs from being processed, which could otherwise lead to crashes or unsafe behavior during dynamic imports. The accompanying test adds coverage for invalid URLs, indicating a security-conscious hardening of input handling.

Verification Assessment

Vulnerability Type: Input validation (URL parsing) in source map handling for dynamic imports

Confidence: MEDIUM

Affected Versions: pre-25.9.0 (Node.js 25.x releases prior to 25.9.0)

Code Diff

diff --git a/lib/internal/source_map/source_map_cache.js b/lib/internal/source_map/source_map_cache.js index a4a95ad0ae49c3..95b09090c4739c 100644 --- a/lib/internal/source_map/source_map_cache.js +++ b/lib/internal/source_map/source_map_cache.js @@ -247,7 +247,10 @@ function dataFromUrl(sourceURL, sourceMappingURL) { } } - const mapURL = new URL(sourceMappingURL, sourceURL).href; + const mapURL = URLParse(sourceMappingURL, sourceURL); + if (mapURL === null) { + return null; + } return sourceMapFromFile(mapURL); } @@ -276,7 +279,7 @@ function lineLengths(content) { /** * Read source map from file. - * @param {string} mapURL - file url of the source map + * @param {URL} mapURL - file url of the source map * @returns {object} deserialized source map JSON object */ function sourceMapFromFile(mapURL) { diff --git a/test/parallel/test-source-map-invalid-url.js b/test/parallel/test-source-map-invalid-url.js new file mode 100644 index 00000000000000..a968040b4f1e3d --- /dev/null +++ b/test/parallel/test-source-map-invalid-url.js @@ -0,0 +1,56 @@ +// Flags: --enable-source-maps +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const childProcess = require('node:child_process'); + +// TODO(legendecas): c8 will complain if non-file-scheme is used in source maps. +// https://github.com/bcoe/c8/blob/ee2f1cfc5584d41bb2d51b788d0953dab0c798f8/lib/report.js#L517 +if (process.env.NODE_V8_COVERAGE) { + console.log('Spawning with coverage disabled.'); + const { status } = childProcess.spawnSync( + process.execPath, + [__filename], + { + stdio: 'inherit', + env: { + ...process.env, + NODE_V8_COVERAGE: '', + }, + }, + ); + assert.strictEqual(status, 0); + return; +} + +const sources = [ + ` + //# sourceMappingURL=unknown-protocol://invalid/url + //# sourceURL=unknown-protocol://invalid/url + `, + ` + //# sourceMappingURL=file://invalid:/file/url + //# sourceURL=file://invalid:/file/url + `, + ` + //# sourceMappingURL=invalid-url + //# sourceURL=invalid-url + `, +]; + +sources.forEach(test); + +function test(source) { + // Eval should tolerate invalid source map url + eval(source); + + const base64 = Buffer.from(source).toString('base64url'); + + // Dynamic import should tolerate invalid source map url + import(`data:text/javascript;base64,${base64}`) + .then(common.mustCall()); +} + +// eslint-disable-next-line @stylistic/js/spaced-comment +//# sourceMappingURL=invalid-url
← Back to Alerts View on GitHub →