fix(ses): Scope proxy defense against property descriptor prototype pollution
MODIFY
packages/ses/src/scope-handler.js
packages/ses/src/scope-handler.js
@@ -7,6 +7,7 @@ import {
getOwnPropertyDescriptor,
globalThis,
immutableObject,
+ objectHasOwnProperty,
reflectGet,
reflectSet,
} from './commons.js';
@@ -112,7 +113,7 @@ export const createScopeHandler = (
// Properties of the localObject.
if (prop in localObject) {
const desc = getOwnPropertyDescriptor(localObject, prop);
- if ('value' in desc) {
+ if (objectHasOwnProperty(desc, 'value')) {
// Work around a peculiar behavior in the specs, where
// value properties are defined on the receiver.
return reflectSet(localObject, prop, value);

ADD
packages/ses/test/test-scope-handler-pollution.js
+import test from 'ava';
+import { objectHasOwnProperty } from '../src/commons.js';
+import { createScopeHandler } from '../src/scope-handler.js';
+
+// Pollute the object prototype such to trick 'value' in propertyDescriptor.
+// eslint-disable-next-line no-extend-native
+Object.prototype.value = null;
+
+test('scopeHandler - defends against prototype pollution of property descriptors', t => {
+ // This verifies that in the face of a polluted 'value' property on
+ // Object.prototype, the scope handler is sensitive only to an owned 'value'
+ // property on a property-descriptor to behave correctly.
+ // The globalLexicals object should not leak to the receiver on the
+ // globalLexicals getter or setter.
+
+ const globalObject = {};
+ let gotcha;
+ let receiver;
+
+ const globalLexicals = {
+ get gotcha() {
+ receiver = this;
+ return gotcha;
+ },
+ set gotcha(value) {
+ receiver = this;
+ gotcha = value;
+ },
+ };
+
+ // Verify our assumptions.
+ t.assert('value' in {});
+ const prop = Object.getOwnPropertyDescriptor(globalLexicals, 'gotcha');
+ t.assert(objectHasOwnProperty(prop, 'get'));
+ t.assert(objectHasOwnProperty(prop, 'set'));
+ t.assert(!objectHasOwnProperty(prop, 'value'));
+ t.assert('value' in prop); // Due to pollution
+
+ const { scopeHandler: handler } = createScopeHandler(
+ globalObject,
+ globalLexicals,
+ );
+
+ handler.set(globalObject, 'gotcha', 42);
+ t.is(globalObject, receiver);
+
+ t.is(42, handler.get(globalObject, 'gotcha'));
+ t.is(globalObject, receiver);
+
+ t.is(42, gotcha);
+ t.is(undefined, globalObject.gotcha);
+});

fix(ses): Scope proxy defense against property descriptor prototype pollution