GeezerVison is showing github.com — tools: add `avoid-prototype-pollution` lint rule · nodejs/node@cf8a115
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/.eslintrc.yaml +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -156,6 +156,7 @@ rules: | 156 | 156 | - name: SubtleCrypto | 157 | 157 | message: Use `const { SubtleCrypto } = require('internal/crypto/webcrypto');` instead of the global. | 158 | 158 | # Custom rules in tools/eslint-rules | | 159 | + node-core/avoid-prototype-pollution: error | 159 | 160 | node-core/lowercase-name-for-primitive: error | 160 | 161 | node-core/non-ascii-character: error | 161 | 162 | node-core/no-array-destructuring: error | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/per_context/primordials.js +5-5Lines changed: 5 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -78,7 +78,7 @@ function copyPropsRenamed(src, dest, prefix) { | 78 | 78 | copyAccessor(dest, prefix, newKey, desc); | 79 | 79 | } else { | 80 | 80 | const name = `${prefix}${newKey}`; | 81 | | - ReflectDefineProperty(dest, name, desc); | | 81 | + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); | 82 | 82 | if (varargsMethods.includes(name)) { | 83 | 83 | ReflectDefineProperty(dest, `${name}Apply`, { | 84 | 84 | __proto__: null, | @@ -105,7 +105,7 @@ function copyPropsRenamedBound(src, dest, prefix) { | 105 | 105 | } | 106 | 106 |
| 107 | 107 | const name = `${prefix}${newKey}`; | 108 | | - ReflectDefineProperty(dest, name, desc); | | 108 | + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); | 109 | 109 | if (varargsMethods.includes(name)) { | 110 | 110 | ReflectDefineProperty(dest, `${name}Apply`, { | 111 | 111 | __proto__: null, | @@ -129,7 +129,7 @@ function copyPrototype(src, dest, prefix) { | 129 | 129 | } | 130 | 130 |
| 131 | 131 | const name = `${prefix}${newKey}`; | 132 | | - ReflectDefineProperty(dest, name, desc); | | 132 | + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); | 133 | 133 | if (varargsMethods.includes(name)) { | 134 | 134 | ReflectDefineProperty(dest, `${name}Apply`, { | 135 | 135 | __proto__: null, | @@ -312,7 +312,7 @@ const copyProps = (src, dest) => { | 312 | 312 | ReflectDefineProperty( | 313 | 313 | dest, | 314 | 314 | key, | 315 | | - ReflectGetOwnPropertyDescriptor(src, key)); | | 315 | + { __proto__: null, ...ReflectGetOwnPropertyDescriptor(src, key) }); | 316 | 316 | } | 317 | 317 | }); | 318 | 318 | }; | @@ -340,7 +340,7 @@ const makeSafe = (unsafe, safe) => { | 340 | 340 | return new SafeIterator(this); | 341 | 341 | }; | 342 | 342 | } | 343 | | - ReflectDefineProperty(safe.prototype, key, desc); | | 343 | + ReflectDefineProperty(safe.prototype, key, { __proto__: null, ...desc }); | 344 | 344 | } | 345 | 345 | }); | 346 | 346 | } else { | |
Collapse file Copy file name to clipboard +147Lines changed: 147 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 Copy file name to clipboard +92Lines changed: 92 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +}; |
|