diff --git a/lib/diagnostics_channel.js b/lib/diagnostics_channel.js index 8d2d374dc8e6ae..f0aedbd6ef29b6 100644 --- a/lib/diagnostics_channel.js +++ b/lib/diagnostics_channel.js @@ -33,7 +33,7 @@ const { triggerUncaughtException } = internalBinding('errors'); const dc_binding = internalBinding('diagnostics_channel'); const { subscribers: subscriberCounts } = dc_binding; -const { WeakReference } = require('internal/util'); +const { WeakReference, kEmptyObject } = require('internal/util'); const { isPromise } = require('internal/util/types'); // Can't delete when weakref count reaches 0 as it could increment again. @@ -390,7 +390,7 @@ class BoundedChannel { return done; } - withScope(context = {}) { + withScope(context = kEmptyObject) { return new BoundedChannelScope(this, context); } @@ -514,7 +514,7 @@ class TracingChannel { return done; } - traceSync(fn, context = {}, thisArg, ...args) { + traceSync(fn, context = { __proto__: null }, thisArg, ...args) { if (!this.hasSubscribers) { return ReflectApply(fn, thisArg, args); } @@ -534,7 +534,7 @@ class TracingChannel { } } - tracePromise(fn, context = {}, thisArg, ...args) { + tracePromise(fn, context = { __proto__: null }, thisArg, ...args) { if (!this.hasSubscribers) { const result = ReflectApply(fn, thisArg, args); if (typeof result?.then !== 'function') { @@ -589,7 +589,7 @@ class TracingChannel { } } - traceCallback(fn, position = -1, context = {}, thisArg, ...args) { + traceCallback(fn, position = -1, context = kEmptyObject, thisArg, ...args) { if (!this.hasSubscribers) { return ReflectApply(fn, thisArg, args); } diff --git a/lib/fs.js b/lib/fs.js index 043e29211a1580..db65f74c446174 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1840,7 +1840,7 @@ function readdirSync(path, options) { * ) => any} [callback] * @returns {void} */ -function fstat(fd, options = { bigint: false }, callback) { +function fstat(fd, options = { __proto__: null, bigint: false }, callback) { if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -1867,7 +1867,7 @@ function fstat(fd, options = { bigint: false }, callback) { * ) => any} callback * @returns {void} */ -function lstat(path, options = { bigint: false }, callback) { +function lstat(path, options = { __proto__: null, bigint: false }, callback) { if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -1899,7 +1899,7 @@ function lstat(path, options = { bigint: false }, callback) { * ) => any} callback * @returns {void} */ -function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback) { +function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }, callback) { if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -1922,7 +1922,7 @@ function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback) binding.stat(getValidatedPath(path), options.bigint, req, options.throwIfNoEntry); } -function statfs(path, options = { bigint: false }, callback) { +function statfs(path, options = { __proto__: null, bigint: false }, callback) { if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -1957,7 +1957,7 @@ function statfs(path, options = { bigint: false }, callback) { * @param {{ bigint?: boolean; }} [options] * @returns {Stats | undefined} */ -function fstatSync(fd, options = { bigint: false }) { +function fstatSync(fd, options = { __proto__: null, bigint: false }) { const h = vfsState.handlers; if (h !== null) { const result = h.fstatSync(fd); @@ -1980,7 +1980,7 @@ function fstatSync(fd, options = { bigint: false }) { * }} [options] * @returns {Stats | undefined} */ -function lstatSync(path, options = { bigint: false, throwIfNoEntry: true }) { +function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { const h = vfsState.handlers; if (h !== null) { const result = h.lstatSync(path, options); @@ -2014,7 +2014,7 @@ function lstatSync(path, options = { bigint: false, throwIfNoEntry: true }) { * }} [options] * @returns {Stats} */ -function statSync(path, options = { bigint: false, throwIfNoEntry: true }) { +function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { const h = vfsState.handlers; if (h !== null) { const result = h.statSync(path, options); @@ -2032,7 +2032,7 @@ function statSync(path, options = { bigint: false, throwIfNoEntry: true }) { return getStatsFromBinding(stats); } -function statfsSync(path, options = { bigint: false }) { +function statfsSync(path, options = { __proto__: null, bigint: false }) { const h = vfsState.handlers; if (h !== null) { const result = h.statfsSync(path, options); diff --git a/lib/internal/async_local_storage/async_context_frame.js b/lib/internal/async_local_storage/async_context_frame.js index 8390ba92cbe848..0fd20b79535d20 100644 --- a/lib/internal/async_local_storage/async_context_frame.js +++ b/lib/internal/async_local_storage/async_context_frame.js @@ -13,6 +13,7 @@ const AsyncContextFrame = require('internal/async_context_frame'); const { AsyncResource } = require('async_hooks'); const RunScope = require('internal/async_local_storage/run_scope'); +const { kEmptyObject } = require('internal/util'); class AsyncLocalStorage { #defaultValue = undefined; @@ -26,7 +27,7 @@ class AsyncLocalStorage { /** * @param {AsyncLocalStorageOptions} [options] */ - constructor(options = {}) { + constructor(options = kEmptyObject) { validateObject(options, 'options'); this.#defaultValue = options.defaultValue; diff --git a/lib/internal/async_local_storage/async_hooks.js b/lib/internal/async_local_storage/async_hooks.js index 552092d89bf305..dab76538845a95 100644 --- a/lib/internal/async_local_storage/async_hooks.js +++ b/lib/internal/async_local_storage/async_hooks.js @@ -20,6 +20,7 @@ const { } = require('async_hooks'); const RunScope = require('internal/async_local_storage/run_scope'); +const { kEmptyObject } = require('internal/util'); const storageList = []; const storageHook = createHook({ @@ -44,7 +45,7 @@ class AsyncLocalStorage { /** * @param {AsyncLocalStorageOptions} [options] */ - constructor(options = {}) { + constructor(options = kEmptyObject) { this.kResourceStore = Symbol('kResourceStore'); this.enabled = false; validateObject(options, 'options'); diff --git a/lib/internal/blob.js b/lib/internal/blob.js index f16e520e197cdd..20f7078d1d0915 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -541,7 +541,7 @@ function createBlobReaderStream(reader) { // unbounded memory growth when the DataQueue has a large burst of data. const kMaxBatchChunks = 16; -async function* createBlobReaderIterable(reader, options = {}) { +async function* createBlobReaderIterable(reader, options = kEmptyObject) { const { getReadError } = options; let wakeup = PromiseWithResolvers(); reader.setWakeup(wakeup.resolve); diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js index d622d35d85faf8..61728364986898 100644 --- a/lib/internal/crypto/cipher.js +++ b/lib/internal/crypto/cipher.js @@ -58,7 +58,7 @@ const assert = require('internal/assert'); const LazyTransform = require('internal/streams/lazy_transform'); -const { normalizeEncoding } = require('internal/util'); +const { normalizeEncoding, kEmptyObject } = require('internal/util'); const { StringDecoder } = require('string_decoder'); @@ -255,7 +255,7 @@ addCipherPrototypeFunctions(Decipheriv); const kMinNid = 1; const kMaxNid = 2_147_483_647; -function getCipherInfo(nameOrNid, options = {}) { +function getCipherInfo(nameOrNid, options = kEmptyObject) { validateObject(options, 'options'); let { keyLength, ivLength } = options; if (keyLength !== undefined) { diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 45072c1581703d..18bde698728a39 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -1715,7 +1715,7 @@ async function symlink(target, path, type) { ); } -async function fstat(handle, options = { bigint: false }) { +async function fstat(handle, options = { __proto__: null, bigint: false }) { const result = await PromisePrototypeThen( binding.fstat(handle.fd, options.bigint, kUsePromises), undefined, @@ -1724,7 +1724,7 @@ async function fstat(handle, options = { bigint: false }) { return getStatsFromBinding(result); } -async function lstat(path, options = { bigint: false }) { +async function lstat(path, options = { __proto__: null, bigint: false }) { const h = vfsState.handlers; if (h !== null) { const promise = h.lstat(path, options); @@ -1743,7 +1743,7 @@ async function lstat(path, options = { bigint: false }) { return getStatsFromBinding(result); } -async function stat(path, options = { bigint: false, throwIfNoEntry: true }) { +async function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { const h = vfsState.handlers; if (h !== null) { const promise = h.stat(path, options); @@ -1761,7 +1761,7 @@ async function stat(path, options = { bigint: false, throwIfNoEntry: true }) { return getStatsFromBinding(result); } -async function statfs(path, options = { bigint: false }) { +async function statfs(path, options = { __proto__: null, bigint: false }) { const h = vfsState.handlers; if (h !== null) { const result = h.statfs(path, options); diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 7c009d60b95821..e7e3260f7f4cee 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -3813,7 +3813,7 @@ function getUnpackedSettings(buf, options = kEmptyObject) { return settings; } -function performServerHandshake(socket, options = {}) { +function performServerHandshake(socket, options = kEmptyObject) { options = initializeOptions(options); return new ServerHttp2Session(options, socket, undefined); } diff --git a/lib/internal/inspector/network_http.js b/lib/internal/inspector/network_http.js index 46bdd827c094a1..1bb08762bdb418 100644 --- a/lib/internal/inspector/network_http.js +++ b/lib/internal/inspector/network_http.js @@ -19,6 +19,7 @@ const { } = require('internal/inspector/network'); const { Network } = require('inspector'); const EventEmitter = require('events'); +const { kEmptyObject } = require('internal/util'); const kRequestUrl = Symbol('kRequestUrl'); @@ -36,7 +37,7 @@ function getRequestURL(request, host) { } // Convert a Headers object (Map) to a plain object (Map) -const convertHeaderObject = (headers = {}) => { +const convertHeaderObject = (headers = kEmptyObject) => { // The 'host' header that contains the host and port of the URL. let host; let charset; diff --git a/lib/internal/inspector/network_http2.js b/lib/internal/inspector/network_http2.js index 0f0751c44dd1f9..0f4aabf59cfd32 100644 --- a/lib/internal/inspector/network_http2.js +++ b/lib/internal/inspector/network_http2.js @@ -30,11 +30,12 @@ const { } = internalBinding('http2').constants; const EventEmitter = require('events'); const { Buffer } = require('buffer'); +const { kEmptyObject } = require('internal/util'); const kRequestUrl = Symbol('kRequestUrl'); // Convert a Headers object (Map) to a plain object (Map) -function convertHeaderObject(headers = {}) { +function convertHeaderObject(headers = kEmptyObject) { let scheme; let authority; let path; diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index 78ffb5f834a989..67ccc9683ee50e 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -354,7 +354,7 @@ class AsyncLoaderHooksOnLoaderHookWorker { * @param {object} context Metadata about the module * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} */ - async load(url, context = {}) { + async load(url, context = kEmptyObject) { const chain = this.#chains.load; const meta = { chainFinished: null, diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index b028f44f013886..7359eeacc7cca1 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -30,7 +30,7 @@ const { getOptionValue } = require('internal/options'); // Do not eagerly grab .manifest, it may be in TDZ const { sep, posix: { relative: relativePosixPath }, resolve } = require('path'); const { URL, pathToFileURL, fileURLToPath, isURL, URLParse } = require('internal/url'); -const { getCWDURL, setOwnProperty } = require('internal/util'); +const { getCWDURL, setOwnProperty, kEmptyObject } = require('internal/util'); const { canParse: URLCanParse } = internalBinding('url'); const { legacyMainResolve: FSLegacyMainResolve } = internalBinding('fs'); const { @@ -944,7 +944,7 @@ function throwIfInvalidParentURL(parentURL) { * @param {string[]} [context.conditions] - The conditions for resolving the specifier. * @returns {{url: string, format?: string}} */ -function defaultResolve(specifier, context = {}) { +function defaultResolve(specifier, context = kEmptyObject) { let { parentURL, conditions } = context; throwIfInvalidParentURL(parentURL); diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index e519a35b5396e5..d284d4a54fbcf8 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -543,14 +543,14 @@ function hasObserver(type) { } -function startPerf(target, key, context = {}) { +function startPerf(target, key, context = kEmptyObject) { target[key] = { ...context, startTime: now(), }; } -function stopPerf(target, key, context = {}) { +function stopPerf(target, key, context = kEmptyObject) { const ctx = target[key]; if (!ctx) { return; diff --git a/lib/internal/readline/emitKeypressEvents.js b/lib/internal/readline/emitKeypressEvents.js index 1ac1091d70cab2..0878bd8154e375 100644 --- a/lib/internal/readline/emitKeypressEvents.js +++ b/lib/internal/readline/emitKeypressEvents.js @@ -31,7 +31,7 @@ const ESCAPE_CODE_TIMEOUT = 500; * accepts a readable Stream instance and makes it emit "keypress" events */ -function emitKeypressEvents(stream, iface = {}) { +function emitKeypressEvents(stream, iface = { __proto__: null }) { if (stream[KEYPRESS_DECODER]) return; stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); diff --git a/lib/internal/streams/fast-utf8-stream.js b/lib/internal/streams/fast-utf8-stream.js index 68601cf5c388c7..d6b94963206956 100644 --- a/lib/internal/streams/fast-utf8-stream.js +++ b/lib/internal/streams/fast-utf8-stream.js @@ -12,6 +12,7 @@ const { const { sleep, + kEmptyObject, } = require('internal/util'); const { @@ -131,7 +132,7 @@ class Utf8Stream extends EventEmitter { contentMode = kContentModeUtf8, mode, // Provides for a custom fs implementation. Mostly useful for testing. - fs: overrideFs = {}, + fs: overrideFs = kEmptyObject, } = options; super(); diff --git a/lib/internal/v8/cpu_profiler.js b/lib/internal/v8/cpu_profiler.js index 4bfda1bced3e42..7b25f0d93dfd94 100644 --- a/lib/internal/v8/cpu_profiler.js +++ b/lib/internal/v8/cpu_profiler.js @@ -4,6 +4,7 @@ const { MathFloor, } = primordials; +const { kEmptyObject } = require('internal/util'); const { validateNumber, validateObject, @@ -14,7 +15,7 @@ const kMaxSamplingIntervalUs = 0x7FFFFFFF; const kMaxSamplingIntervalMs = kMaxSamplingIntervalUs / kMicrosPerMilli; const kMaxSamplesUnlimited = 0xFFFF_FFFF; -function normalizeCpuProfileOptions(options = {}) { +function normalizeCpuProfileOptions(options = kEmptyObject) { validateObject(options, 'options'); // TODO(ishabi): add support for 'mode' and 'filterContext' options diff --git a/lib/internal/v8/heap_profile.js b/lib/internal/v8/heap_profile.js index ea4679b6c05704..45a181d49b565b 100644 --- a/lib/internal/v8/heap_profile.js +++ b/lib/internal/v8/heap_profile.js @@ -1,5 +1,6 @@ 'use strict'; +const { kEmptyObject } = require('internal/util'); const { validateBoolean, validateInteger, @@ -14,7 +15,7 @@ const { kSamplingIncludeObjectsCollectedByMinorGC, } = internalBinding('v8'); -function normalizeHeapProfileOptions(options = {}) { +function normalizeHeapProfileOptions(options = kEmptyObject) { validateObject(options, 'options'); const { sampleInterval = 512 * 1024, diff --git a/lib/internal/vfs/stats.js b/lib/internal/vfs/stats.js index fdec6fe87cad26..e41e52728c5842 100644 --- a/lib/internal/vfs/stats.js +++ b/lib/internal/vfs/stats.js @@ -18,6 +18,7 @@ const { } = internalBinding('constants'); const { getStatsFromBinding } = require('internal/fs/utils'); +const { kEmptyObject } = require('internal/util'); // Default block size for virtual files (4KB) const kDefaultBlockSize = 4096; @@ -91,7 +92,7 @@ function fillBigIntStatsArray( * @param {boolean} [options.bigint] Return BigIntStats * @returns {Stats} */ -function createFileStats(size, options = {}) { +function createFileStats(size, options = kEmptyObject) { const now = DateNow(); const mode = (options.mode ?? 0o644) | S_IFREG; const nlink = options.nlink ?? 1; @@ -150,7 +151,7 @@ function createFileStats(size, options = {}) { * @param {number} [options.birthtimeMs] Birth time in ms * @returns {Stats} */ -function createDirectoryStats(options = {}) { +function createDirectoryStats(options = kEmptyObject) { const now = DateNow(); const mode = (options.mode ?? 0o755) | S_IFDIR; const uid = options.uid ?? (process.getuid?.() ?? 0); @@ -208,7 +209,7 @@ function createDirectoryStats(options = {}) { * @param {number} [options.birthtimeMs] Birth time in ms * @returns {Stats} */ -function createSymlinkStats(size, options = {}) { +function createSymlinkStats(size, options = kEmptyObject) { const now = DateNow(); const mode = (options.mode ?? 0o777) | S_IFLNK; const uid = options.uid ?? (process.getuid?.() ?? 0); diff --git a/lib/internal/vfs/watcher.js b/lib/internal/vfs/watcher.js index 2b6b807ee85b44..0f23c1588b17b5 100644 --- a/lib/internal/vfs/watcher.js +++ b/lib/internal/vfs/watcher.js @@ -18,6 +18,7 @@ const { setInterval, clearInterval, } = require('timers'); +const { kEmptyObject } = require('internal/util'); /** * VFSWatcher - Polling-based file/directory watcher for VFS. @@ -47,7 +48,7 @@ class VFSWatcher extends EventEmitter { * @param {boolean} [options.recursive] Watch subdirectories (default: false) * @param {AbortSignal} [options.signal] AbortSignal for cancellation */ - constructor(provider, path, options = {}) { + constructor(provider, path, options = kEmptyObject) { super(); this.#vfs = provider; @@ -395,7 +396,7 @@ class VFSStatWatcher extends EventEmitter { * @param {number} [options.interval] Polling interval in ms (default: 5007) * @param {boolean} [options.persistent] Keep process alive (default: true) */ - constructor(provider, path, options = {}) { + constructor(provider, path, options = kEmptyObject) { super(); this.#vfs = provider; @@ -586,7 +587,7 @@ class VFSWatchAsyncIterable { * @param {string} path The path to watch (provider-relative) * @param {object} [options] Options */ - constructor(provider, path, options = {}) { + constructor(provider, path, options = kEmptyObject) { // Strip signal from options passed to VFSWatcher - we handle abort // at the iterable level to reject pending next() with AbortError // instead of resolving with done:true via the 'close' event. diff --git a/lib/repl.js b/lib/repl.js index 60ea4457ab1bf6..17aab1c409beca 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -967,7 +967,7 @@ class REPLServer extends Interface { self.displayPrompt(); } - setupHistory(historyConfig = {}, cb) { + setupHistory(historyConfig = { __proto__: null }, cb) { // TODO(puskin94): necessary because historyConfig can be a string for backwards compatibility const options = typeof historyConfig === 'string' ? { filePath: historyConfig } : diff --git a/lib/util.js b/lib/util.js index adebd890adcd71..6d70951b9b7de7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -96,6 +96,7 @@ const { getSystemErrorMessage: internalErrorMessage, promisify, defineLazyProperties, + kEmptyObject, } = require('internal/util'); let abortController; @@ -583,7 +584,7 @@ function getCallSites(frameCount = 10, options) { }; // Public util.deprecate API -function deprecate(fn, msg, code, { modifyPrototype } = {}) { +function deprecate(fn, msg, code, { modifyPrototype } = kEmptyObject) { return internalDeprecate(fn, msg, code, undefined, modifyPrototype); } diff --git a/lib/vm.js b/lib/vm.js index ae710806201893..2c7446af607637 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -24,6 +24,7 @@ const { ArrayPrototypeForEach, ObjectFreeze, + ObjectPrototype, PromiseReject, ReflectApply, Symbol, @@ -222,7 +223,7 @@ function getContextOptions(options) { } let defaultContextNameIndex = 1; -function createContext(contextObject = {}, options = kEmptyObject) { +function createContext(contextObject = { __proto__: ObjectPrototype }, options = kEmptyObject) { if (contextObject !== vm_context_no_contextify && isContext(contextObject)) { return contextObject; } diff --git a/lib/wasi.js b/lib/wasi.js index 9971b533e72394..b81835dfede521 100644 --- a/lib/wasi.js +++ b/lib/wasi.js @@ -111,7 +111,7 @@ class WASI { finalizeBindings(instance, { memory = instance?.exports?.memory, - } = {}) { + } = kEmptyObject) { if (this[kStarted]) { throw new ERR_WASI_ALREADY_STARTED(); } diff --git a/test/parallel/test-eslint-avoid-prototype-pollution.js b/test/parallel/test-eslint-avoid-prototype-pollution.js index c6b0fe638655e4..98dc951a906456 100644 --- a/test/parallel/test-eslint-avoid-prototype-pollution.js +++ b/test/parallel/test-eslint-avoid-prototype-pollution.js @@ -11,7 +11,7 @@ const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester; const rule = require('../../tools/eslint-rules/avoid-prototype-pollution'); new RuleTester() - .run('property-descriptor-no-prototype-pollution', rule, { + .run('avoid-prototype-pollution', rule, { valid: [ 'ObjectDefineProperties({}, {})', 'ObjectCreate(null, {})', @@ -63,6 +63,18 @@ new RuleTester() 'new Proxy({}, { __proto__: null, ...{} })', 'async function name(){return await SafePromiseAll([])}', 'async function name(){const val = await SafePromiseAll([])}', + 'async function name(options = kEmptyObject){}', + 'function name(options = kEmptyObject){}', + 'function name(options = { __proto__: ObjectPrototype }){}', + 'function name(options = { __proto__: null }){}', + 'new class { name(options = kEmptyObject){} }', + 'const name = (options = kEmptyObject) => {}', + 'async function name(options = { __proto__: null, key: 1 }){}', + 'const name = ({ destr } = { destr: 1 }) => {}', + 'function name({ destr } = kEmptyObject){}', + 'function name({ [Symbol.match]: m } = kEmptyObject){}', + 'function name({ [Symbol.match]: m } = { __proto__: null, [Symbol.match]: 1 }){}', + 'function name({ [Symbol.match]: m, c } = { __proto__: null, c: 1 }){}', ], invalid: [ { @@ -330,5 +342,53 @@ new RuleTester() code: 'ArrayPrototypeConcat([])', errors: [{ message: /\bisConcatSpreadable\b/ }] }, + { + code: 'function name(options = {}) {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'async function name(options = { key: 1 }) {}', + errors: [{ message: /\b__proto__: null\b/ }] + }, + { + code: 'new class { name(options = {}) {} }', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'new class { async name(options = {}) {} }', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'const name = (options = {}) => {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'const name = async (options = {}) => {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'function name({ destr } = {}) {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'async function name({ destr } = {}) {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'new class { name({ destr } = {}) {} }', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'new class { async name({ destr } = {}) {} }', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'const name = ({ destr } = {}) => {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, + { + code: 'const name = async ({ destr } = {}) => {}', + errors: [{ message: /\bkEmptyObject\b/ }] + }, ] }); diff --git a/tools/eslint-rules/avoid-prototype-pollution.js b/tools/eslint-rules/avoid-prototype-pollution.js index da13653e3e36e3..79468a0002744c 100644 --- a/tools/eslint-rules/avoid-prototype-pollution.js +++ b/tools/eslint-rules/avoid-prototype-pollution.js @@ -232,6 +232,28 @@ module.exports = { 'which can be subject to prototype pollution', }); }, + + 'AssignmentPattern[right.type="ObjectExpression"]'(node) { + if (!node.right.properties.length) { + context.report({ + node: node.right, + message: 'Use kEmptyObject instead of declaring a new empty object, or define a __proto__ property', + }); + return; + } + const propertyIsIdentifier = (p) => p.key.type === 'Identifier'; + if (node.left.type === 'ObjectPattern' && + node.left.properties.every(propertyIsIdentifier) && + node.right.properties.every(propertyIsIdentifier)) { + const rightNames = node.right.properties.map((p) => p.key.name); + if (node.left.properties.every((p) => rightNames.includes(p.key.name))) return; + } + if (node.right.properties.some((p) => p.key.name === '__proto__')) return; + context.report({ + node: node.right, + message: `Add '__proto__: null' to avoid inheriting from Object.prototype, or '__proto__: ObjectPrototype' if inheritance is desirable`, + }); + }, }; }, };