Skip to content

Commit b665171

Browse files
aduh95juanarbol
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 <[email protected]>
1 parent 1022ece commit b665171

File tree

8 files changed

+96
-15
lines changed

8 files changed

+96
-15
lines changed

‎lib/internal/modules/cjs/helpers.js‎

Lines changed: 2 additions & 1 deletion
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
constuserConditions=getOptionValue('--conditions');
2829

2930
letdebug=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‎

Lines changed: 10 additions & 11 deletions
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
constvm=require('vm');
8484
constassert=require('internal/assert');
8585
constfs=require('fs');
@@ -172,7 +172,7 @@ const moduleParentCache = new SafeWeakMap();
172172
functionModule(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-
constparsed=JSONParse(json);
316-
constfiltered={
317-
name: parsed.name,
318-
main: parsed.main,
319-
exports: parsed.exports,
320-
imports: parsed.imports,
321-
type: parsed.type
322-
};
315+
constfiltered=filterOwnProperties(JSONParse(json),[
316+
'name',
317+
'main',
318+
'exports',
319+
'imports',
320+
'type',
321+
]);
323322
packageJsonCache.set(jsonPath,filtered);
324323
returnfiltered;
325324
}catch(e){
@@ -1191,7 +1190,7 @@ Module._extensions['.json'] = function(module, filename){
11911190
}
11921191

11931192
try{
1194-
module.exports=JSONParse(stripBOM(content));
1193+
setOwnProperty(module,'exports',JSONParse(stripBOM(content)));
11951194
}catch(err){
11961195
err.message=filename+': '+err.message;
11971196
throwerr;

‎lib/internal/modules/esm/package_config.js‎

Lines changed: 4 additions & 2 deletions
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
constpackageJsonReader=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+
constexports=ObjectPrototypeHasOwnProperty(packageJSON,'exports') ? packageJSON.exports : undefined;
7173
if(typeofimports!=='object'||imports===null){
7274
imports=undefined;
7375
}

‎lib/internal/util.js‎

Lines changed: 32 additions & 0 deletions
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,
@@ -518,6 +519,35 @@ ObjectFreeze(kEnumerableProperty);
518519

519520
constkEmptyObject=ObjectFreeze(ObjectCreate(null));
520521

522+
functionfilterOwnProperties(source,keys){
523+
constfiltered=ObjectCreate(null);
524+
for(leti=0;i<keys.length;i++){
525+
constkey=keys[i];
526+
if(ObjectPrototypeHasOwnProperty(source,key)){
527+
filtered[key]=source[key];
528+
}
529+
}
530+
531+
returnfiltered;
532+
}
533+
534+
/**
535+
* Mimics `obj[key] = value` but ignoring potential prototype inheritance.
536+
* @param{any} obj
537+
* @param{string} key
538+
* @param{any} value
539+
* @returns{any}
540+
*/
541+
functionsetOwnProperty(obj,key,value){
542+
returnObjectDefineProperty(obj,key,{
543+
__proto__: null,
544+
configurable: true,
545+
enumerable: true,
546+
value,
547+
writable: true,
548+
});
549+
}
550+
521551
module.exports={
522552
assertCrypto,
523553
cachedResult,
@@ -530,6 +560,7 @@ module.exports ={
530560
emitExperimentalWarning,
531561
exposeInterface,
532562
filterDuplicateStrings,
563+
filterOwnProperties,
533564
getConstructorOf,
534565
getSystemErrorMap,
535566
getSystemErrorName,
@@ -560,4 +591,5 @@ module.exports ={
560591

561592
kEmptyObject,
562593
kEnumerableProperty,
594+
setOwnProperty,
563595
};
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
importexplicitfrom'explicit-main';
22
importimplicitfrom'implicit-main';
33
importimplicitModulefrom'implicit-main-type-module';
4+
importnoMainfrom'no-main-field';
45

56
functiongetImplicitCommonjs(){
67
returnimport('implicit-main-type-commonjs');
78
}
89

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

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

Lines changed: 2 additions & 0 deletions
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‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constfixtures=require('../common/fixtures');
4+
constassert=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)