Skip to content

Commit b670954

Browse files
aduh95RafaelGSS
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 Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent a86ef1b commit b670954

File tree

3 files changed

+71
-23
lines changed

3 files changed

+71
-23
lines changed

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
function isIPv4(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
return RegExpPrototypeTest(IPv4Reg, s);
3336
}
3437

3538
function isIPv6(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
return RegExpPrototypeTest(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: /looks up the Symbol\.match property/ }],
169172
},
173+
{
174+
code: 'let v = StringPrototypeMatch("some string", /some regex/)',
175+
errors: [{ message: /looks up the Symbol\.match property/ }],
176+
},
177+
{
178+
code: 'let v = StringPrototypeMatch("some string", new RegExp("some regex"))',
179+
errors: [{ message: /looks up the Symbol\.match property/ }],
180+
},
170181
{
171182
code: 'StringPrototypeMatchAll("some string", /some regex/)',
172183
errors: [{ message: /looks up the Symbol\.matchAll property/ }],
173184
},
185+
{
186+
code: 'let v = StringPrototypeMatchAll("some string", new RegExp("some regex"))',
187+
errors: [{ message: /looks up the Symbol\.matchAll property/ }],
188+
},
174189
{
175190
code: 'StringPrototypeReplace("some string", /some regex/, "some replacement")',
176191
errors: [{ message: /looks up the Symbol\.replace property/ }],
177192
},
193+
{
194+
code: 'StringPrototypeReplace("some string", new RegExp("some regex"), "some replacement")',
195+
errors: [{ message: /looks up the Symbol\.replace property/ }],
196+
},
178197
{
179198
code: 'StringPrototypeReplaceAll("some string", /some regex/, "some replacement")',
180199
errors: [{ message: /looks up the Symbol\.replace property/ }],
181200
},
201+
{
202+
code: 'StringPrototypeReplaceAll("some string", new RegExp("some regex"), "some replacement")',
203+
errors: [{ message: /looks up the Symbol\.replace property/ }],
204+
},
182205
{
183206
code: 'StringPrototypeSearch("some string", /some regex/)',
184207
errors: [{ message: /looks up the Symbol\.search property/ }],

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+
const CallExpression = (fnName) => `CallExpression[callee.name=${fnName}]`;
4+
35
function checkProperties(context, node) {
46
if (
57
node.type === 'CallExpression' &&
@@ -64,8 +66,10 @@ function checkPropertyDescriptor(context, node) {
6466
}
6567

6668
function createUnsafeStringMethodReport(context, name, lookedUpProperty) {
69+
const lastDotPosition = '$String.prototype.'.length;
70+
const unsafePrimordialName = `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-
const CallExpression = 'ExpressionStatement[expression.type="CallExpression"]';
81+
function createUnsafeStringMethodOnRegexReport(context, name, lookedUpProperty) {
82+
const dotPosition = 'Symbol.'.length;
83+
const safePrimordialName = `RegExpPrototypeSymbol${lookedUpProperty.charAt(dotPosition).toUpperCase()}${lookedUpProperty.slice(dotPosition + 1)}`;
84+
const lastDotPosition = '$String.prototype.'.length;
85+
const unsafePrimordialName = `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
throw new Error('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 } of node.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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy