GeezerVison is showing github.com — tools,doc: add guards against prototype pollution when creating proxies · nodejs/node@812140c
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: doc/contributing/primordials.md +24Lines changed: 24 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -705,3 +705,27 @@ class SomeClass { | 705 | 705 | ObjectDefineProperty(SomeClass.prototype, 'readOnlyProperty', kEnumerableProperty); | 706 | 706 | console.log(new SomeClass().readOnlyProperty); // genuine data | 707 | 707 | ``` | | 708 | + | | 709 | +### Defining a `Proxy` handler | | 710 | + | | 711 | +When defining a `Proxy`, the handler object could be at risk of prototype | | 712 | +pollution when using a plain object literal: | | 713 | + | | 714 | +```js | | 715 | +// User-land | | 716 | +Object.prototype.get = () => 'Unrelated user-provided data'; | | 717 | + | | 718 | +// Core | | 719 | +const objectToProxy = { someProperty: 'genuine value' }; | | 720 | + | | 721 | +const proxyWithPlainObjectLiteral = new Proxy(objectToProxy, { | | 722 | + has() { return false; }, | | 723 | +}); | | 724 | +console.log(proxyWithPlainObjectLiteral.someProperty); // Unrelated user-provided data | | 725 | + | | 726 | +const proxyWithNullPrototypeObject = new Proxy(objectToProxy, { | | 727 | + __proto__: null, | | 728 | + has() { return false; }, | | 729 | +}); | | 730 | +console.log(proxyWithNullPrototypeObject.someProperty); // genuine value | | 731 | +``` |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/debugger/inspect.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -117,6 +117,7 @@ function createAgentProxy(domain, client) { | 117 | 117 | }; | 118 | 118 |
| 119 | 119 | return new Proxy(agent, { | | 120 | + __proto__: null, | 120 | 121 | get(target, name) { | 121 | 122 | if (name in target) return target[name]; | 122 | 123 | return function callVirtualMethod(params) { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/http2/core.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -987,6 +987,7 @@ function trackAssignmentsTypedArray(typedArray) { | 987 | 987 | } | 988 | 988 |
| 989 | 989 | return new Proxy(typedArray, { | | 990 | + __proto__: null, | 990 | 991 | get(obj, prop, receiver) { | 991 | 992 | if (prop === 'copyAssigned') { | 992 | 993 | return copyAssigned; | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/cjs/loader.js +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -210,6 +210,8 @@ const wrapper = [ | 210 | 210 | ]; | 211 | 211 |
| 212 | 212 | let wrapperProxy = new Proxy(wrapper, { | | 213 | + __proto__: null, | | 214 | + | 213 | 215 | set(target, property, value, receiver) { | 214 | 216 | patched = true; | 215 | 217 | return ReflectSet(target, property, value, receiver); | @@ -718,6 +720,8 @@ function emitCircularRequireWarning(prop) { | 718 | 720 | // A Proxy that can be used as the prototype of a module.exports object and | 719 | 721 | // warns when non-existent properties are accessed. | 720 | 722 | const CircularRequirePrototypeWarningProxy = new Proxy({}, { | | 723 | + __proto__: null, | | 724 | + | 721 | 725 | get(target, prop) { | 722 | 726 | // Allow __esModule access in any case because it is used in the output | 723 | 727 | // of transpiled code to determine whether something comes from an | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-eslint-avoid-prototype-pollution.js +20Lines changed: 20 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -45,6 +45,10 @@ new RuleTester({ | 45 | 45 | 'ReflectDefineProperty({}, "key", { "__proto__": null })', | 46 | 46 | 'ObjectDefineProperty({}, "key", { \'__proto__\': null })', | 47 | 47 | 'ReflectDefineProperty({}, "key", { \'__proto__\': null })', | | 48 | + 'new Proxy({}, otherObject)', | | 49 | + 'new Proxy({}, someFactory())', | | 50 | + 'new Proxy({}, { __proto__: null })', | | 51 | + 'new Proxy({}, { __proto__: null, ...{} })', | 48 | 52 | ], | 49 | 53 | invalid: [ | 50 | 54 | { | @@ -183,5 +187,21 @@ new RuleTester({ | 183 | 187 | code: 'StringPrototypeSplit("some string", /some regex/)', | 184 | 188 | errors: [{ message: /looks up the Symbol\.split property/ }], | 185 | 189 | }, | | 190 | + { | | 191 | + code: 'new Proxy({}, {})', | | 192 | + errors: [{ message: /null-prototype/ }] | | 193 | + }, | | 194 | + { | | 195 | + code: 'new Proxy({}, { [`__proto__`]: null })', | | 196 | + errors: [{ message: /null-prototype/ }] | | 197 | + }, | | 198 | + { | | 199 | + code: 'new Proxy({}, { __proto__: Object.prototype })', | | 200 | + errors: [{ message: /null-prototype/ }] | | 201 | + }, | | 202 | + { | | 203 | + code: 'new Proxy({}, { ...{ __proto__: null } })', | | 204 | + errors: [{ message: /null-prototype/ }] | | 205 | + }, | 186 | 206 | ] | 187 | 207 | }); |
Collapse file Copy file name to clipboardExpand all lines: tools/eslint-rules/avoid-prototype-pollution.js +17Lines changed: 17 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -128,6 +128,23 @@ module.exports = { | 128 | 128 | ...createUnsafeStringMethodReport(context, 'StringPrototypeReplaceAll', 'Symbol.replace'), | 129 | 129 | ...createUnsafeStringMethodReport(context, 'StringPrototypeSearch', 'Symbol.search'), | 130 | 130 | ...createUnsafeStringMethodReport(context, 'StringPrototypeSplit', 'Symbol.split'), | | 131 | + | | 132 | + 'NewExpression[callee.name="Proxy"][arguments.1.type="ObjectExpression"]'(node) { | | 133 | + for (const { key, value } of node.arguments[1].properties) { | | 134 | + if ( | | 135 | + key != null && value != null && | | 136 | + ((key.type === 'Identifier' && key.name === '__proto__') || | | 137 | + (key.type === 'Literal' && key.value === '__proto__')) && | | 138 | + value.type === 'Literal' && value.value === null | | 139 | + ) { | | 140 | + return; | | 141 | + } | | 142 | + } | | 143 | + context.report({ | | 144 | + node, | | 145 | + message: 'Proxy handler must be a null-prototype object' | | 146 | + }); | | 147 | + } | 131 | 148 | }; | 132 | 149 | }, | 133 | 150 | }; |
|