From f23b2a30661ea84c1d4ef83f431253a9d3e49b54 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 11 Aug 2023 08:29:14 +0200 Subject: [PATCH] esm: protect `ERR_UNSUPPORTED_DIR_IMPORT` against prototype pollution PR-URL: https://github.com/nodejs/node/pull/49060 Reviewed-By: Geoffrey Booth Reviewed-By: Jacob Smith --- lib/internal/errors.js | 7 +++-- lib/internal/modules/esm/resolve.js | 4 +-- test/es-module/test-esm-main-lookup.mjs | 38 ++++++++++++------------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index ac2e62147e41d1..a335cca7df11db 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1696,8 +1696,11 @@ E('ERR_UNKNOWN_FILE_EXTENSION', (ext, path, suggestion) => { E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s for URL %s', RangeError); E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError); -E('ERR_UNSUPPORTED_DIR_IMPORT', "Directory import '%s' is not supported " + -'resolving ES modules imported from %s', Error); +E('ERR_UNSUPPORTED_DIR_IMPORT', function(path, base, exactUrl) { + lazyInternalUtil().setOwnProperty(this, 'url', exactUrl); + return `Directory import '${path}' is not supported ` + + `resolving ES modules imported from ${base}`; +}, Error); E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => { let msg = `Only URLs with a scheme in: ${formatList(supported)} are supported by the default ESM loader`; if (isWindows && url.protocol.length === 2) { diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index ce5e4e27fd5128..8160206da4c044 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -215,9 +215,7 @@ function finalizeResolution(resolved, base, preserveSymlinks) { // Check for stats.isDirectory() if (stats === 1) { - const err = new ERR_UNSUPPORTED_DIR_IMPORT(path, fileURLToPath(base)); - err.url = String(resolved); - throw err; + throw new ERR_UNSUPPORTED_DIR_IMPORT(path, fileURLToPath(base), String(resolved)); } else if (stats !== 0) { // Check for !stats.isFile() if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { diff --git a/test/es-module/test-esm-main-lookup.mjs b/test/es-module/test-esm-main-lookup.mjs index 4694d1c8e626e0..4f4f1c378914d7 100644 --- a/test/es-module/test-esm-main-lookup.mjs +++ b/test/es-module/test-esm-main-lookup.mjs @@ -1,24 +1,22 @@ -import '../common/index.mjs'; +import { mustNotCall } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; import assert from 'assert'; -async function main() { - let mod; - try { - mod = await import('../fixtures/es-modules/pjson-main'); - } catch (e) { - assert.strictEqual(e.code, 'ERR_UNSUPPORTED_DIR_IMPORT'); - } +Object.defineProperty(Error.prototype, 'url', { + get: mustNotCall('get %Error.prototype%.url'), + set: mustNotCall('set %Error.prototype%.url'), +}); +Object.defineProperty(Object.prototype, 'url', { + get: mustNotCall('get %Object.prototype%.url'), + set: mustNotCall('set %Object.prototype%.url'), +}); - assert.strictEqual(mod, undefined); +await assert.rejects(import('../fixtures/es-modules/pjson-main'), { + code: 'ERR_UNSUPPORTED_DIR_IMPORT', + url: fixtures.fileURL('es-modules/pjson-main').href, +}); - try { - mod = await import('../fixtures/es-modules/pjson-main/main.mjs'); - } catch (e) { - console.log(e); - assert.fail(); - } - - assert.strictEqual(mod.main, 'main'); -} - -main(); +assert.deepStrictEqual( + { ...await import('../fixtures/es-modules/pjson-main/main.mjs') }, + { main: 'main' }, +);