Skip to content

Commit 94912bb

Browse files
aduh95ruyadorno
authored andcommitted
lib: add Promise methods to avoid-prototype-pollution lint rule
PR-URL: #43849 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Paolo Insogna <[email protected]>
1 parent a0c5783 commit 94912bb

File tree

10 files changed

+91
-45
lines changed

10 files changed

+91
-45
lines changed

lib/internal/debugger/inspect.js

+23-16
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const {
1111
FunctionPrototypeBind,
1212
Number,
1313
Promise,
14-
PromisePrototypeCatch,
1514
PromisePrototypeThen,
1615
PromiseResolve,
1716
Proxy,
@@ -169,12 +168,17 @@ class NodeInspector {
169168
process.once('SIGTERM', exitCodeZero);
170169
process.once('SIGHUP', exitCodeZero);
171170

172-
PromisePrototypeCatch(PromisePrototypeThen(this.run(), async () => {
173-
const repl = await startRepl();
174-
this.repl = repl;
175-
this.repl.on('exit', exitCodeZero);
176-
this.paused = false;
177-
}), (error) => process.nextTick(() => { throw error; }));
171+
(async () => {
172+
try {
173+
await this.run();
174+
const repl = await startRepl();
175+
this.repl = repl;
176+
this.repl.on('exit', exitCodeZero);
177+
this.paused = false;
178+
} catch (error) {
179+
process.nextTick(() => { throw error; });
180+
}
181+
})();
178182
}
179183

180184
suspendReplWhile(fn) {
@@ -183,16 +187,19 @@ class NodeInspector {
183187
}
184188
this.stdin.pause();
185189
this.paused = true;
186-
return PromisePrototypeCatch(PromisePrototypeThen(new Promise((resolve) => {
187-
resolve(fn());
188-
}), () => {
189-
this.paused = false;
190-
if (this.repl) {
191-
this.repl.resume();
192-
this.repl.displayPrompt();
190+
return (async () => {
191+
try {
192+
await fn();
193+
this.paused = false;
194+
if (this.repl) {
195+
this.repl.resume();
196+
this.repl.displayPrompt();
197+
}
198+
this.stdin.resume();
199+
} catch (error) {
200+
process.nextTick(() => { throw error; });
193201
}
194-
this.stdin.resume();
195-
}), (error) => process.nextTick(() => { throw error; }));
202+
})();
196203
}
197204

198205
killChild() {

lib/internal/http2/core.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const {
1717
ObjectDefineProperty,
1818
ObjectPrototypeHasOwnProperty,
1919
Promise,
20-
PromisePrototypeCatch,
20+
PromisePrototypeThen,
2121
Proxy,
2222
ReflectApply,
2323
ReflectGet,
@@ -2456,8 +2456,8 @@ function processHeaders(oldHeaders, options) {
24562456
function onFileUnpipe() {
24572457
const stream = this.sink[kOwner];
24582458
if (stream.ownsFd)
2459-
PromisePrototypeCatch(this.source.close(),
2460-
FunctionPrototypeBind(stream.destroy, stream));
2459+
PromisePrototypeThen(this.source.close(), undefined,
2460+
FunctionPrototypeBind(stream.destroy, stream));
24612461
else
24622462
this.source.releaseFD();
24632463
}

lib/internal/main/worker_thread.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {
88
ArrayPrototypePushApply,
99
ArrayPrototypeSplice,
1010
ObjectDefineProperty,
11-
PromisePrototypeCatch,
11+
PromisePrototypeThen,
1212
globalThis: { Atomics },
1313
} = primordials;
1414

@@ -185,7 +185,7 @@ port.on('message', (message) => {
185185
evalScript(name, filename);
186186
} else if (doEval === 'module') {
187187
const { evalModule } = require('internal/process/execution');
188-
PromisePrototypeCatch(evalModule(filename), (e) => {
188+
PromisePrototypeThen(evalModule(filename), undefined, (e) => {
189189
workerOnGlobalUncaughtException(e, true);
190190
});
191191
} else {

lib/internal/modules/esm/module_job.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {
88
ObjectCreate,
99
ObjectSetPrototypeOf,
1010
PromiseResolve,
11-
PromisePrototypeCatch,
11+
PromisePrototypeThen,
1212
ReflectApply,
1313
RegExpPrototypeExec,
1414
RegExpPrototypeSymbolReplace,
@@ -88,7 +88,7 @@ class ModuleJob {
8888
this.linked = link();
8989
// This promise is awaited later anyway, so silence
9090
// 'unhandled rejection' warnings.
91-
PromisePrototypeCatch(this.linked, noop);
91+
PromisePrototypeThen(this.linked, undefined, noop);
9292

9393
// instantiated == deep dependency jobs wrappers are instantiated,
9494
// and module wrapper is instantiated.

lib/internal/per_context/primordials.js

-3
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,6 @@ const SafePromise = makeSafe(
409409
}
410410
);
411411

412-
primordials.PromisePrototypeCatch = (thisPromise, onRejected) =>
413-
PromisePrototypeThen(thisPromise, undefined, onRejected);
414-
415412
/**
416413
* Attaches a callback that is invoked when the Promise is settled (fulfilled or
417414
* rejected). The resolved value cannot be modified from the callback.

lib/internal/streams/operators.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const {
2525
NumberIsNaN,
2626
Promise,
2727
PromiseReject,
28-
PromisePrototypeCatch,
28+
PromisePrototypeThen,
2929
Symbol,
3030
} = primordials;
3131

@@ -113,7 +113,7 @@ function map(fn, options) {
113113
queue.push(kEof);
114114
} catch (err) {
115115
const val = PromiseReject(err);
116-
PromisePrototypeCatch(val, onDone);
116+
PromisePrototypeThen(val, undefined, onDone);
117117
queue.push(val);
118118
} finally {
119119
done = true;

lib/internal/webstreams/readablestream.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const {
1616
ObjectDefineProperties,
1717
ObjectSetPrototypeOf,
1818
Promise,
19-
PromisePrototypeCatch,
2019
PromisePrototypeThen,
2120
PromiseResolve,
2221
PromiseReject,
@@ -1334,7 +1333,7 @@ function readableStreamPipeTo(
13341333
if (stream[kState].state === 'errored')
13351334
action(stream[kState].storedError);
13361335
else
1337-
PromisePrototypeCatch(promise, action);
1336+
PromisePrototypeThen(promise, undefined, action);
13381337
}
13391338

13401339
function watchClosed(stream, promise, action) {
@@ -1503,8 +1502,9 @@ function readableStreamTee(stream, cloneForBranch2) {
15031502
branch2 =
15041503
createTeeReadableStream(nonOpStart, pullAlgorithm, cancel2Algorithm);
15051504

1506-
PromisePrototypeCatch(
1505+
PromisePrototypeThen(
15071506
reader[kState].close.promise,
1507+
undefined,
15081508
(error) => {
15091509
readableStreamDefaultControllerError(branch1[kState].controller, error);
15101510
readableStreamDefaultControllerError(branch2[kState].controller, error);

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

+20
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,25 @@ new RuleTester({
203203
code: 'new Proxy({}, { ...{ __proto__: null } })',
204204
errors: [{ message: /null-prototype/ }]
205205
},
206+
{
207+
code: 'PromisePrototypeCatch(promise, ()=>{})',
208+
errors: [{ message: /\bPromisePrototypeThen\b/ }]
209+
},
210+
{
211+
code: 'PromiseAll([])',
212+
errors: [{ message: /\bSafePromiseAll\b/ }]
213+
},
214+
{
215+
code: 'PromiseAllSettled([])',
216+
errors: [{ message: /\bSafePromiseAllSettled\b/ }]
217+
},
218+
{
219+
code: 'PromiseAny([])',
220+
errors: [{ message: /\bSafePromiseAny\b/ }]
221+
},
222+
{
223+
code: 'PromiseRace([])',
224+
errors: [{ message: /\bSafePromiseRace\b/ }]
225+
},
206226
]
207227
});

test/parallel/test-primordials-promise.js

+8-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const common = require('../common');
55
const assert = require('assert');
66

77
const {
8-
PromisePrototypeCatch,
98
PromisePrototypeThen,
109
SafePromiseAll,
1110
SafePromiseAllSettled,
@@ -14,16 +13,15 @@ const {
1413
SafePromiseRace,
1514
} = require('internal/test/binding').primordials;
1615

17-
Array.prototype[Symbol.iterator] = common.mustNotCall();
18-
Promise.all = common.mustNotCall();
19-
Promise.allSettled = common.mustNotCall();
20-
Promise.any = common.mustNotCall();
21-
Promise.race = common.mustNotCall();
22-
Promise.prototype.catch = common.mustNotCall();
23-
Promise.prototype.finally = common.mustNotCall();
24-
Promise.prototype.then = common.mustNotCall();
16+
Array.prototype[Symbol.iterator] = common.mustNotCall('%Array.prototype%[@@iterator]');
17+
Promise.all = common.mustNotCall('%Promise%.all');
18+
Promise.allSettled = common.mustNotCall('%Promise%.allSettled');
19+
Promise.any = common.mustNotCall('%Promise%.any');
20+
Promise.race = common.mustNotCall('%Promise%.race');
21+
Promise.prototype.catch = common.mustNotCall('%Promise.prototype%.catch');
22+
Promise.prototype.finally = common.mustNotCall('%Promise.prototype%.finally');
23+
Promise.prototype.then = common.mustNotCall('%Promise.prototype%.then');
2524

26-
assertIsPromise(PromisePrototypeCatch(Promise.reject(), common.mustCall()));
2725
assertIsPromise(PromisePrototypeThen(test(), common.mustCall()));
2826
assertIsPromise(SafePromisePrototypeFinally(test(), common.mustCall()));
2927

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

+28-4
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ module.exports = {
109109
testRange.start = testRange.start + 'RegexpPrototype'.length;
110110
testRange.end = testRange.start + 'Test'.length;
111111
return [
112-
fixer.replaceTextRange(node.range, 'Exec'),
112+
fixer.replaceTextRange(testRange, 'Exec'),
113113
fixer.insertTextAfter(node, ' !== null'),
114114
];
115115
}
116-
}]
116+
}],
117117
});
118118
},
119119
[`${CallExpression}[expression.callee.name=${/^RegExpPrototypeSymbol(Match|MatchAll|Search)$/}]`](node) {
@@ -142,9 +142,33 @@ module.exports = {
142142
}
143143
context.report({
144144
node,
145-
message: 'Proxy handler must be a null-prototype object'
145+
message: 'Proxy handler must be a null-prototype object',
146146
});
147-
}
147+
},
148+
149+
[`${CallExpression}[expression.callee.name=PromisePrototypeCatch]`](node) {
150+
context.report({
151+
node,
152+
message: '%Promise.prototype.catch% look up the `then` property of ' +
153+
'the `this` argument, use PromisePrototypeThen instead',
154+
});
155+
},
156+
157+
[`${CallExpression}[expression.callee.name=PromisePrototypeFinally]`](node) {
158+
context.report({
159+
node,
160+
message: '%Promise.prototype.finally% look up the `then` property of ' +
161+
'the `this` argument, use SafePromisePrototypeFinally or ' +
162+
'try/finally instead',
163+
});
164+
},
165+
166+
[`${CallExpression}[expression.callee.name=${/^Promise(All(Settled)?|Any|Race)/}]`](node) {
167+
context.report({
168+
node,
169+
message: `Use Safe${node.expression.callee.name} instead of ${node.expression.callee.name}`,
170+
});
171+
},
148172
};
149173
},
150174
};

0 commit comments

Comments
 (0)