Skip to content

Commit 254358c

Browse files
aduh95richardlau
authored andcommitted
tools: refactor avoid-prototype-pollution lint rule
The lint rule was not catching all occurences of unsafe primordials use, and was too strict on some methods. PR-URL: #43476 Backport-PR-URL: #44926 Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent db31de6 commit 254358c

3 files changed

Lines changed: 71 additions & 23 deletions

File tree

‎lib/internal/net.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ const IPv6Reg = new RegExp('^(' +
2929
')(%[0-9a-zA-Z-.:]{1,})?$');
3030

3131
functionisIPv4(s){
32+
// TODO(aduh95): Replace RegExpPrototypeTest with RegExpPrototypeExec when it
33+
// no longer creates a perf regression in the dns benchmark.
34+
// eslint-disable-next-line node-core/avoid-prototype-pollution
3235
returnRegExpPrototypeTest(IPv4Reg,s);
3336
}
3437

3538
functionisIPv6(s){
39+
// TODO(aduh95): Replace RegExpPrototypeTest with RegExpPrototypeExec when it
40+
// no longer creates a perf regression in the dns benchmark.
41+
// eslint-disable-next-line node-core/avoid-prototype-pollution
3642
returnRegExpPrototypeTest(IPv6Reg,s);
3743
}
3844

‎test/parallel/test-eslint-avoid-prototype-pollution.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ new RuleTester({
4545
'ReflectDefineProperty({}, "key", { "__proto__": null })',
4646
'ObjectDefineProperty({}, "key", { \'__proto__\': null })',
4747
'ReflectDefineProperty({}, "key", { \'__proto__\': null })',
48+
'StringPrototypeReplace("some string", "some string", "some replacement")',
49+
'StringPrototypeReplaceAll("some string", "some string", "some replacement")',
50+
'StringPrototypeSplit("some string", "some string")',
4851
'new Proxy({}, otherObject)',
4952
'new Proxy({}, someFactory())',
5053
'new Proxy({}, { __proto__: null })',
@@ -167,18 +170,38 @@ new RuleTester({
167170
code: 'StringPrototypeMatch("some string", /some regex/)',
168171
errors: [{message: /looksuptheSymbol\.matchproperty/}],
169172
},
173+
{
174+
code: 'let v = StringPrototypeMatch("some string", /some regex/)',
175+
errors: [{message: /looksuptheSymbol\.matchproperty/}],
176+
},
177+
{
178+
code: 'let v = StringPrototypeMatch("some string", new RegExp("some regex"))',
179+
errors: [{message: /looksuptheSymbol\.matchproperty/}],
180+
},
170181
{
171182
code: 'StringPrototypeMatchAll("some string", /some regex/)',
172183
errors: [{message: /looksuptheSymbol\.matchAllproperty/}],
173184
},
185+
{
186+
code: 'let v = StringPrototypeMatchAll("some string", new RegExp("some regex"))',
187+
errors: [{message: /looksuptheSymbol\.matchAllproperty/}],
188+
},
174189
{
175190
code: 'StringPrototypeReplace("some string", /some regex/, "some replacement")',
176191
errors: [{message: /looksuptheSymbol\.replaceproperty/}],
177192
},
193+
{
194+
code: 'StringPrototypeReplace("some string", new RegExp("some regex"), "some replacement")',
195+
errors: [{message: /looksuptheSymbol\.replaceproperty/}],
196+
},
178197
{
179198
code: 'StringPrototypeReplaceAll("some string", /some regex/, "some replacement")',
180199
errors: [{message: /looksuptheSymbol\.replaceproperty/}],
181200
},
201+
{
202+
code: 'StringPrototypeReplaceAll("some string", new RegExp("some regex"), "some replacement")',
203+
errors: [{message: /looksuptheSymbol\.replaceproperty/}],
204+
},
182205
{
183206
code: 'StringPrototypeSearch("some string", /some regex/)',
184207
errors: [{message: /looksuptheSymbol\.searchproperty/}],

‎tools/eslint-rules/avoid-prototype-pollution.js‎

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
constCallExpression=(fnName)=>`CallExpression[callee.name=${fnName}]`;
4+
35
functioncheckProperties(context,node){
46
if(
57
node.type==='CallExpression'&&
@@ -64,8 +66,10 @@ function checkPropertyDescriptor(context, node) {
6466
}
6567

6668
functioncreateUnsafeStringMethodReport(context,name,lookedUpProperty){
69+
constlastDotPosition='$String.prototype.'.length;
70+
constunsafePrimordialName=`StringPrototype${name.charAt(lastDotPosition).toUpperCase()}${name.slice(lastDotPosition+1,-1)}`;
6771
return{
68-
[`${CallExpression}[expression.callee.name=${JSON.stringify(name)}]`](node){
72+
[CallExpression(unsafePrimordialName)](node){
6973
context.report({
7074
node,
7175
message: `${name} looks up the ${lookedUpProperty} property on the first argument`,
@@ -74,31 +78,46 @@ function createUnsafeStringMethodReport(context, name, lookedUpProperty) {
7478
};
7579
}
7680

77-
constCallExpression='ExpressionStatement[expression.type="CallExpression"]';
81+
functioncreateUnsafeStringMethodOnRegexReport(context,name,lookedUpProperty){
82+
constdotPosition='Symbol.'.length;
83+
constsafePrimordialName=`RegExpPrototypeSymbol${lookedUpProperty.charAt(dotPosition).toUpperCase()}${lookedUpProperty.slice(dotPosition+1)}`;
84+
constlastDotPosition='$String.prototype.'.length;
85+
constunsafePrimordialName=`StringPrototype${name.charAt(lastDotPosition).toUpperCase()}${name.slice(lastDotPosition+1,-1)}`;
86+
return{
87+
[[
88+
`${CallExpression(unsafePrimordialName)}[arguments.1.type=Literal][arguments.1.regex]`,
89+
`${CallExpression(unsafePrimordialName)}[arguments.1.type=NewExpression][arguments.1.callee.name=RegExp]`,
90+
].join(',')](node){
91+
context.report({
92+
node,
93+
message: `${name} looks up the ${lookedUpProperty} property of the passed regex, use ${safePrimordialName} directly`,
94+
});
95+
}
96+
};
97+
}
98+
7899
module.exports={
79100
meta: {hasSuggestions: true},
80101
create(context){
81102
return{
82-
[`${CallExpression}[expression.callee.name=${/^(Object|Reflect)DefinePropert(ies|y)$/}]`](
83-
node
84-
){
85-
switch(node.expression.callee.name){
103+
[CallExpression(/^(Object|Reflect)DefinePropert(ies|y)$/)](node){
104+
switch(node.callee.name){
86105
case'ObjectDefineProperties':
87-
checkProperties(context,node.expression.arguments[1]);
106+
checkProperties(context,node.arguments[1]);
88107
break;
89108
case'ReflectDefineProperty':
90109
case'ObjectDefineProperty':
91-
checkPropertyDescriptor(context,node.expression.arguments[2]);
110+
checkPropertyDescriptor(context,node.arguments[2]);
92111
break;
93112
default:
94113
thrownewError('Unreachable');
95114
}
96115
},
97116

98-
[`${CallExpression}[expression.callee.name="ObjectCreate"][expression.arguments.length=2]`](node){
99-
checkProperties(context,node.expression.arguments[1]);
117+
[`${CallExpression('ObjectCreate')}[arguments.length=2]`](node){
118+
checkProperties(context,node.arguments[1]);
100119
},
101-
[`${CallExpression}[expression.callee.name="RegExpPrototypeTest"]`](node){
120+
[CallExpression('RegExpPrototypeTest')](node){
102121
context.report({
103122
node,
104123
message: '%RegExp.prototype.test% looks up the "exec" property of `this` value',
@@ -116,18 +135,18 @@ module.exports = {
116135
}],
117136
});
118137
},
119-
[`${CallExpression}[expression.callee.name=${/^RegExpPrototypeSymbol(Match|MatchAll|Search)$/}]`](node){
138+
[CallExpression(/^RegExpPrototypeSymbol(Match|MatchAll|Search)$/)](node){
120139
context.report({
121140
node,
122-
message: node.expression.callee.name+' looks up the "exec" property of `this` value',
141+
message: node.callee.name+' looks up the "exec" property of `this` value',
123142
});
124143
},
125-
...createUnsafeStringMethodReport(context,'StringPrototypeMatch','Symbol.match'),
126-
...createUnsafeStringMethodReport(context,'StringPrototypeMatchAll','Symbol.matchAll'),
127-
...createUnsafeStringMethodReport(context,'StringPrototypeReplace','Symbol.replace'),
128-
...createUnsafeStringMethodReport(context,'StringPrototypeReplaceAll','Symbol.replace'),
129-
...createUnsafeStringMethodReport(context,'StringPrototypeSearch','Symbol.search'),
130-
...createUnsafeStringMethodReport(context,'StringPrototypeSplit','Symbol.split'),
144+
...createUnsafeStringMethodReport(context,'%String.prototype.match%','Symbol.match'),
145+
...createUnsafeStringMethodReport(context,'%String.prototype.matchAll%','Symbol.matchAll'),
146+
...createUnsafeStringMethodOnRegexReport(context,'%String.prototype.replace%','Symbol.replace'),
147+
...createUnsafeStringMethodOnRegexReport(context,'%String.prototype.replaceAll%','Symbol.replace'),
148+
...createUnsafeStringMethodReport(context,'%String.prototype.search%','Symbol.search'),
149+
...createUnsafeStringMethodOnRegexReport(context,'%String.prototype.split%','Symbol.split'),
131150

132151
'NewExpression[callee.name="Proxy"][arguments.1.type="ObjectExpression"]'(node){
133152
for(const{ key, value }ofnode.arguments[1].properties){
@@ -146,15 +165,15 @@ module.exports = {
146165
});
147166
},
148167

149-
[`${CallExpression}[expression.callee.name=PromisePrototypeCatch]`](node){
168+
[CallExpression('PromisePrototypeCatch')](node){
150169
context.report({
151170
node,
152171
message: '%Promise.prototype.catch% look up the `then` property of '+
153172
'the `this` argument, use PromisePrototypeThen instead',
154173
});
155174
},
156175

157-
[`${CallExpression}[expression.callee.name=PromisePrototypeFinally]`](node){
176+
[CallExpression('PromisePrototypeFinally')](node){
158177
context.report({
159178
node,
160179
message: '%Promise.prototype.finally% look up the `then` property of '+
@@ -163,10 +182,10 @@ module.exports = {
163182
});
164183
},
165184

166-
[`${CallExpression}[expression.callee.name=${/^Promise(All(Settled)?|Any|Race)/}]`](node){
185+
[CallExpression(/^Promise(All(Settled)?|Any|Race)/)](node){
167186
context.report({
168187
node,
169-
message: `Use Safe${node.expression.callee.name} instead of ${node.expression.callee.name}`,
188+
message: `Use Safe${node.callee.name} instead of ${node.callee.name}`,
170189
});
171190
},
172191
};

0 commit comments

Comments
 (0)