Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cf8a115

Browse filesBrowse files
aduh95danielleadams
authored andcommitted
tools: add avoid-prototype-pollution lint rule
PR-URL: #43308 Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 3b2f7ee commit cf8a115
Copy full SHA for cf8a115

File tree

Expand file treeCollapse file tree

4 files changed

+245
-5
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+245
-5
lines changed
Open diff view settings
Collapse file

‎lib/.eslintrc.yaml‎

Copy file name to clipboardExpand all lines: lib/.eslintrc.yaml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ rules:
156156
- name: SubtleCrypto
157157
message: Use `const { SubtleCrypto } = require('internal/crypto/webcrypto');` instead of the global.
158158
# Custom rules in tools/eslint-rules
159+
node-core/avoid-prototype-pollution: error
159160
node-core/lowercase-name-for-primitive: error
160161
node-core/non-ascii-character: error
161162
node-core/no-array-destructuring: error
Collapse file

‎lib/internal/per_context/primordials.js‎

Copy file name to clipboardExpand all lines: lib/internal/per_context/primordials.js
+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function copyPropsRenamed(src, dest, prefix) {
7878
copyAccessor(dest, prefix, newKey, desc);
7979
} else {
8080
const name = `${prefix}${newKey}`;
81-
ReflectDefineProperty(dest, name, desc);
81+
ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
8282
if (varargsMethods.includes(name)) {
8383
ReflectDefineProperty(dest, `${name}Apply`, {
8484
__proto__: null,
@@ -105,7 +105,7 @@ function copyPropsRenamedBound(src, dest, prefix) {
105105
}
106106

107107
const name = `${prefix}${newKey}`;
108-
ReflectDefineProperty(dest, name, desc);
108+
ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
109109
if (varargsMethods.includes(name)) {
110110
ReflectDefineProperty(dest, `${name}Apply`, {
111111
__proto__: null,
@@ -129,7 +129,7 @@ function copyPrototype(src, dest, prefix) {
129129
}
130130

131131
const name = `${prefix}${newKey}`;
132-
ReflectDefineProperty(dest, name, desc);
132+
ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
133133
if (varargsMethods.includes(name)) {
134134
ReflectDefineProperty(dest, `${name}Apply`, {
135135
__proto__: null,
@@ -312,7 +312,7 @@ const copyProps = (src, dest) => {
312312
ReflectDefineProperty(
313313
dest,
314314
key,
315-
ReflectGetOwnPropertyDescriptor(src, key));
315+
{ __proto__: null, ...ReflectGetOwnPropertyDescriptor(src, key) });
316316
}
317317
});
318318
};
@@ -340,7 +340,7 @@ const makeSafe = (unsafe, safe) => {
340340
return new SafeIterator(this);
341341
};
342342
}
343-
ReflectDefineProperty(safe.prototype, key, desc);
343+
ReflectDefineProperty(safe.prototype, key, { __proto__: null, ...desc });
344344
}
345345
});
346346
} else {
Collapse file
+147Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if ((!common.hasCrypto) || (!common.hasIntl)) {
5+
common.skip('ESLint tests require crypto and Intl');
6+
}
7+
8+
common.skipIfEslintMissing();
9+
10+
const RuleTester = require('../../tools/node_modules/eslint').RuleTester;
11+
const rule = require('../../tools/eslint-rules/avoid-prototype-pollution');
12+
13+
new RuleTester({
14+
parserOptions: { ecmaVersion: 2022 },
15+
})
16+
.run('property-descriptor-no-prototype-pollution', rule, {
17+
valid: [
18+
'ObjectDefineProperties({}, {})',
19+
'ObjectCreate(null, {})',
20+
'ObjectDefineProperties({}, { key })',
21+
'ObjectCreate(null, { key })',
22+
'ObjectDefineProperties({}, { ...spread })',
23+
'ObjectCreate(null, { ...spread })',
24+
'ObjectDefineProperties({}, { key: valueDescriptor })',
25+
'ObjectCreate(null, { key: valueDescriptor })',
26+
'ObjectDefineProperties({}, { key: { ...{}, __proto__: null } })',
27+
'ObjectCreate(null, { key: { ...{}, __proto__: null } })',
28+
'ObjectDefineProperties({}, { key: { __proto__: null } })',
29+
'ObjectCreate(null, { key: { __proto__: null } })',
30+
'ObjectDefineProperties({}, { key: { __proto__: null, enumerable: true } })',
31+
'ObjectCreate(null, { key: { __proto__: null, enumerable: true } })',
32+
'ObjectDefineProperties({}, { key: { "__proto__": null } })',
33+
'ObjectCreate(null, { key: { "__proto__": null } })',
34+
'ObjectDefineProperties({}, { key: { \'__proto__\': null } })',
35+
'ObjectCreate(null, { key: { \'__proto__\': null } })',
36+
'ObjectDefineProperty({}, "key", ObjectCreate(null))',
37+
'ReflectDefineProperty({}, "key", ObjectCreate(null))',
38+
'ObjectDefineProperty({}, "key", valueDescriptor)',
39+
'ReflectDefineProperty({}, "key", valueDescriptor)',
40+
'ObjectDefineProperty({}, "key", { __proto__: null })',
41+
'ReflectDefineProperty({}, "key", { __proto__: null })',
42+
'ObjectDefineProperty({}, "key", { __proto__: null, enumerable: true })',
43+
'ReflectDefineProperty({}, "key", { __proto__: null, enumerable: true })',
44+
'ObjectDefineProperty({}, "key", { "__proto__": null })',
45+
'ReflectDefineProperty({}, "key", { "__proto__": null })',
46+
'ObjectDefineProperty({}, "key", { \'__proto__\': null })',
47+
'ReflectDefineProperty({}, "key", { \'__proto__\': null })',
48+
],
49+
invalid: [
50+
{
51+
code: 'ObjectDefineProperties({}, ObjectGetOwnPropertyDescriptors({}))',
52+
errors: [{ message: /prototype pollution/ }],
53+
},
54+
{
55+
code: 'ObjectCreate(null, ObjectGetOwnPropertyDescriptors({}))',
56+
errors: [{ message: /prototype pollution/ }],
57+
},
58+
{
59+
code: 'ObjectDefineProperties({}, { key: {} })',
60+
errors: [{ message: /null-prototype/ }],
61+
},
62+
{
63+
code: 'ObjectCreate(null, { key: {} })',
64+
errors: [{ message: /null-prototype/ }],
65+
},
66+
{
67+
code: 'ObjectDefineProperties({}, { key: { [void 0]: { ...{ __proto__: null } } } })',
68+
errors: [{ message: /null-prototype/ }],
69+
},
70+
{
71+
code: 'ObjectCreate(null, { key: { [void 0]: { ...{ __proto__: null } } } })',
72+
errors: [{ message: /null-prototype/ }],
73+
},
74+
{
75+
code: 'ObjectDefineProperties({}, { key: { __proto__: Object.prototype } })',
76+
errors: [{ message: /null-prototype/ }],
77+
},
78+
{
79+
code: 'ObjectCreate(null, { key: { __proto__: Object.prototype } })',
80+
errors: [{ message: /null-prototype/ }],
81+
},
82+
{
83+
code: 'ObjectDefineProperties({}, { key: { [`__proto__`]: null } })',
84+
errors: [{ message: /null-prototype/ }],
85+
},
86+
{
87+
code: 'ObjectCreate(null, { key: { [`__proto__`]: null } })',
88+
errors: [{ message: /null-prototype/ }],
89+
},
90+
{
91+
code: 'ObjectDefineProperties({}, { key: { enumerable: true } })',
92+
errors: [{ message: /null-prototype/ }],
93+
},
94+
{
95+
code: 'ObjectCreate(null, { key: { enumerable: true } })',
96+
errors: [{ message: /null-prototype/ }],
97+
},
98+
{
99+
code: 'ObjectDefineProperty({}, "key", {})',
100+
errors: [{ message: /null-prototype/ }],
101+
},
102+
{
103+
code: 'ReflectDefineProperty({}, "key", {})',
104+
errors: [{ message: /null-prototype/ }],
105+
},
106+
{
107+
code: 'ObjectDefineProperty({}, "key", ObjectGetOwnPropertyDescriptor({}, "key"))',
108+
errors: [{ message: /prototype pollution/ }],
109+
},
110+
{
111+
code: 'ReflectDefineProperty({}, "key", ObjectGetOwnPropertyDescriptor({}, "key"))',
112+
errors: [{ message: /prototype pollution/ }],
113+
},
114+
{
115+
code: 'ObjectDefineProperty({}, "key", ReflectGetOwnPropertyDescriptor({}, "key"))',
116+
errors: [{ message: /prototype pollution/ }],
117+
},
118+
{
119+
code: 'ReflectDefineProperty({}, "key", ReflectGetOwnPropertyDescriptor({}, "key"))',
120+
errors: [{ message: /prototype pollution/ }],
121+
},
122+
{
123+
code: 'ObjectDefineProperty({}, "key", { __proto__: Object.prototype })',
124+
errors: [{ message: /null-prototype/ }],
125+
},
126+
{
127+
code: 'ReflectDefineProperty({}, "key", { __proto__: Object.prototype })',
128+
errors: [{ message: /null-prototype/ }],
129+
},
130+
{
131+
code: 'ObjectDefineProperty({}, "key", { [`__proto__`]: null })',
132+
errors: [{ message: /null-prototype/ }],
133+
},
134+
{
135+
code: 'ReflectDefineProperty({}, "key", { [`__proto__`]: null })',
136+
errors: [{ message: /null-prototype/ }],
137+
},
138+
{
139+
code: 'ObjectDefineProperty({}, "key", { enumerable: true })',
140+
errors: [{ message: /null-prototype/ }],
141+
},
142+
{
143+
code: 'ReflectDefineProperty({}, "key", { enumerable: true })',
144+
errors: [{ message: /null-prototype/ }],
145+
},
146+
]
147+
});
Collapse file
+92Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
3+
function checkProperties(context, node) {
4+
if (
5+
node.type === 'CallExpression' &&
6+
node.callee.name === 'ObjectGetOwnPropertyDescriptors'
7+
) {
8+
context.report({
9+
node,
10+
message:
11+
'Property descriptors inherits from the Object prototype, therefore are subject to prototype pollution',
12+
});
13+
}
14+
if (node.type !== 'ObjectExpression') return;
15+
for (const { key, value } of node.properties) {
16+
if (
17+
key != null && value != null &&
18+
!(key.type === 'Identifier' && key.name === '__proto__') &&
19+
!(key.type === 'Literal' && key.value === '__proto__')
20+
) {
21+
checkPropertyDescriptor(context, value);
22+
}
23+
}
24+
}
25+
26+
function checkPropertyDescriptor(context, node) {
27+
if (
28+
node.type === 'CallExpression' &&
29+
(node.callee.name === 'ObjectGetOwnPropertyDescriptor' ||
30+
node.callee.name === 'ReflectGetOwnPropertyDescriptor')
31+
) {
32+
context.report({
33+
node,
34+
message:
35+
'Property descriptors inherits from the Object prototype, therefore are subject to prototype pollution',
36+
suggest: [{
37+
desc: 'Wrap the property descriptor in a null-prototype object',
38+
fix(fixer) {
39+
return [
40+
fixer.insertTextBefore(node, '{ __proto__: null,...'),
41+
fixer.insertTextAfter(node, ' }'),
42+
];
43+
},
44+
}],
45+
});
46+
}
47+
if (node.type !== 'ObjectExpression') return;
48+
49+
for (const { key, value } of node.properties) {
50+
if (
51+
key != null && value != null &&
52+
((key.type === 'Identifier' && key.name === '__proto__') ||
53+
(key.type === 'Literal' && key.value === '__proto__')) &&
54+
value.type === 'Literal' && value.value === null
55+
) {
56+
return true;
57+
}
58+
}
59+
60+
context.report({
61+
node,
62+
message: 'Must use null-prototype object for property descriptors',
63+
});
64+
}
65+
66+
const CallExpression = 'ExpressionStatement[expression.type="CallExpression"]';
67+
module.exports = {
68+
meta: { hasSuggestions: true },
69+
create(context) {
70+
return {
71+
[`${CallExpression}[expression.callee.name=${/^(Object|Reflect)DefinePropert(ies|y)$/}]`](
72+
node
73+
) {
74+
switch (node.expression.callee.name) {
75+
case 'ObjectDefineProperties':
76+
checkProperties(context, node.expression.arguments[1]);
77+
break;
78+
case 'ReflectDefineProperty':
79+
case 'ObjectDefineProperty':
80+
checkPropertyDescriptor(context, node.expression.arguments[2]);
81+
break;
82+
default:
83+
throw new Error('Unreachable');
84+
}
85+
},
86+
87+
[`${CallExpression}[expression.callee.name="ObjectCreate"][expression.arguments.length=2]`](node) {
88+
checkProperties(context, node.expression.arguments[1]);
89+
},
90+
};
91+
},
92+
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.