Skip to content

Commit 05802c2

Browse files
aduh95ruyadorno
authored andcommitted
module: protect against prototype mutation
Ensures that mutating the `Object` prototype does not influence the parsing of `package.json` files. PR-URL: #44007 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent 4755ad5 commit 05802c2

File tree

8 files changed

+96
-15
lines changed

8 files changed

+96
-15
lines changed

lib/internal/modules/cjs/helpers.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const path = require('path');
2424
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
2525

2626
const { getOptionValue } = require('internal/options');
27+
const { setOwnProperty } = require('internal/util');
2728
const userConditions = getOptionValue('--conditions');
2829

2930
let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
@@ -117,7 +118,7 @@ function makeRequireFunction(mod, redirects) {
117118

118119
resolve.paths = paths;
119120

120-
require.main = process.mainModule;
121+
setOwnProperty(require, 'main', process.mainModule);
121122

122123
// Enable support to add extra extension types.
123124
require.extensions = Module._extensions;

lib/internal/modules/cjs/loader.js

+10-11
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const {
7979
maybeCacheSourceMap,
8080
} = require('internal/source_map/source_map_cache');
8181
const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
82-
const { deprecate, kEmptyObject } = require('internal/util');
82+
const { deprecate, kEmptyObject, filterOwnProperties, setOwnProperty } = require('internal/util');
8383
const vm = require('vm');
8484
const assert = require('internal/assert');
8585
const fs = require('fs');
@@ -172,7 +172,7 @@ const moduleParentCache = new SafeWeakMap();
172172
function Module(id = '', parent) {
173173
this.id = id;
174174
this.path = path.dirname(id);
175-
this.exports = {};
175+
setOwnProperty(this, 'exports', {});
176176
moduleParentCache.set(this, parent);
177177
updateChildren(parent, this, false);
178178
this.filename = null;
@@ -312,14 +312,13 @@ function readPackage(requestPath) {
312312
}
313313

314314
try {
315-
const parsed = JSONParse(json);
316-
const filtered = {
317-
name: parsed.name,
318-
main: parsed.main,
319-
exports: parsed.exports,
320-
imports: parsed.imports,
321-
type: parsed.type
322-
};
315+
const filtered = filterOwnProperties(JSONParse(json), [
316+
'name',
317+
'main',
318+
'exports',
319+
'imports',
320+
'type',
321+
]);
323322
packageJsonCache.set(jsonPath, filtered);
324323
return filtered;
325324
} catch (e) {
@@ -1185,7 +1184,7 @@ Module._extensions['.json'] = function(module, filename) {
11851184
}
11861185

11871186
try {
1188-
module.exports = JSONParse(stripBOM(content));
1187+
setOwnProperty(module, 'exports', JSONParse(stripBOM(content)));
11891188
} catch (err) {
11901189
err.message = filename + ': ' + err.message;
11911190
throw err;

lib/internal/modules/esm/package_config.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
JSONParse,
5+
ObjectPrototypeHasOwnProperty,
56
SafeMap,
67
StringPrototypeEndsWith,
78
} = primordials;
@@ -11,6 +12,7 @@ const {
1112
} = require('internal/errors').codes;
1213

1314
const packageJsonReader = require('internal/modules/package_json_reader');
15+
const { filterOwnProperties } = require('internal/util');
1416

1517

1618
/**
@@ -66,8 +68,8 @@ function getPackageConfig(path, specifier, base) {
6668
);
6769
}
6870

69-
let { imports, main, name, type } = packageJSON;
70-
const { exports } = packageJSON;
71+
let { imports, main, name, type } = filterOwnProperties(packageJSON, ['imports', 'main', 'name', 'type']);
72+
const exports = ObjectPrototypeHasOwnProperty(packageJSON, 'exports') ? packageJSON.exports : undefined;
7173
if (typeof imports !== 'object' || imports === null) {
7274
imports = undefined;
7375
}

lib/internal/util.js

+32
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
ObjectGetOwnPropertyDescriptors,
1515
ObjectGetPrototypeOf,
1616
ObjectFreeze,
17+
ObjectPrototypeHasOwnProperty,
1718
ObjectSetPrototypeOf,
1819
Promise,
1920
ReflectApply,
@@ -507,6 +508,35 @@ ObjectFreeze(kEnumerableProperty);
507508

508509
const kEmptyObject = ObjectFreeze(ObjectCreate(null));
509510

511+
function filterOwnProperties(source, keys) {
512+
const filtered = ObjectCreate(null);
513+
for (let i = 0; i < keys.length; i++) {
514+
const key = keys[i];
515+
if (ObjectPrototypeHasOwnProperty(source, key)) {
516+
filtered[key] = source[key];
517+
}
518+
}
519+
520+
return filtered;
521+
}
522+
523+
/**
524+
* Mimics `obj[key] = value` but ignoring potential prototype inheritance.
525+
* @param {any} obj
526+
* @param {string} key
527+
* @param {any} value
528+
* @returns {any}
529+
*/
530+
function setOwnProperty(obj, key, value) {
531+
return ObjectDefineProperty(obj, key, {
532+
__proto__: null,
533+
configurable: true,
534+
enumerable: true,
535+
value,
536+
writable: true,
537+
});
538+
}
539+
510540
module.exports = {
511541
assertCrypto,
512542
cachedResult,
@@ -519,6 +549,7 @@ module.exports = {
519549
emitExperimentalWarning,
520550
exposeInterface,
521551
filterDuplicateStrings,
552+
filterOwnProperties,
522553
getConstructorOf,
523554
getSystemErrorMap,
524555
getSystemErrorName,
@@ -549,4 +580,5 @@ module.exports = {
549580

550581
kEmptyObject,
551582
kEnumerableProperty,
583+
setOwnProperty,
552584
};
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import explicit from 'explicit-main';
22
import implicit from 'implicit-main';
33
import implicitModule from 'implicit-main-type-module';
4+
import noMain from 'no-main-field';
45

56
function getImplicitCommonjs () {
67
return import('implicit-main-type-commonjs');
78
}
89

9-
export {explicit, implicit, implicitModule, getImplicitCommonjs};
10+
export {explicit, implicit, implicitModule, getImplicitCommonjs, noMain};
1011
export default 'success';

test/fixtures/es-module-specifiers/node_modules/no-main-field/index.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/no-main-field/package.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
const common = require('../common');
3+
const fixtures = require('../common/fixtures');
4+
const assert = require('assert');
5+
6+
Object.defineProperty(Object.prototype, 'name', {
7+
__proto__: null,
8+
get: common.mustNotCall('get %Object.prototype%.name'),
9+
set: common.mustNotCall('set %Object.prototype%.name'),
10+
enumerable: false,
11+
});
12+
Object.defineProperty(Object.prototype, 'main', {
13+
__proto__: null,
14+
get: common.mustNotCall('get %Object.prototype%.main'),
15+
set: common.mustNotCall('set %Object.prototype%.main'),
16+
enumerable: false,
17+
});
18+
Object.defineProperty(Object.prototype, 'type', {
19+
__proto__: null,
20+
get: common.mustNotCall('get %Object.prototype%.type'),
21+
set: common.mustNotCall('set %Object.prototype%.type'),
22+
enumerable: false,
23+
});
24+
Object.defineProperty(Object.prototype, 'exports', {
25+
__proto__: null,
26+
get: common.mustNotCall('get %Object.prototype%.exports'),
27+
set: common.mustNotCall('set %Object.prototype%.exports'),
28+
enumerable: false,
29+
});
30+
Object.defineProperty(Object.prototype, 'imports', {
31+
__proto__: null,
32+
get: common.mustNotCall('get %Object.prototype%.imports'),
33+
set: common.mustNotCall('set %Object.prototype%.imports'),
34+
enumerable: false,
35+
});
36+
37+
assert.strictEqual(
38+
require(fixtures.path('es-module-specifiers', 'node_modules', 'no-main-field')),
39+
'no main field'
40+
);
41+
42+
import(fixtures.fileURL('es-module-specifiers', 'index.mjs'))
43+
.then(common.mustCall((module) => assert.strictEqual(module.noMain, 'no main field')));

0 commit comments

Comments
 (0)