whitelist: allow `ESCAPE` prefix to avoid swallowing `__proto__`
MODIFY
src/bundle/removeProperties.js
src/bundle/removeProperties.js
@@ -91,6 +91,10 @@ export default function removeProperties(global, whitelist) {
if (hop(permit, name)) {
return permit[name];
}
+ // Allow escaping of magical names like '__proto__'.
+ if (hop(permit, `ESCAPE${name}`)) {
+ return permit[`ESCAPE${name}`];
+ }
}
// eslint-disable-next-line no-constant-condition
while (true) {

MODIFY
src/bundle/whitelist.js
src/bundle/whitelist.js
@@ -273,7 +273,9 @@ export default {
prototype: {
// B.2.2
- __proto__: t,
+ // We need to prefix __proto__ with ESCAPE so that it doesn't
+ // just change the prototype of this object.
+ ESCAPE__proto__: 'maybeAccessor',
__defineGetter__: t,
__defineSetter__: t,
__lookupGetter__: t,

MODIFY
test/test-removal.js
test/test-removal.js
@@ -69,3 +69,33 @@ test('remove RegExp.prototype.compile', t => {
t.equal(s.evaluate('const r = /./; typeof r.compile'), 'undefined');
t.end();
});
+
+test('do not remove Object.prototype.__proto__', t => {
+ try {
+ const s = SES.makeSESRootRealm();
+ t.equal(
+ s.evaluate('({}).__proto__'),
+ s.global.Object.prototype,
+ '({}).__proto__ is accessible',
+ );
+ t.equal(
+ s.evaluate('[].__proto__'),
+ s.global.Array.prototype,
+ '[].__proto__ is accessible',
+ );
+ t.equal(
+ s.evaluate(`\
+function X () {};
+X.prototype.__proto__ = Array.prototype;
+const x=new X();
+x.slice;
+`),
+ s.global.Array.prototype.slice,
+ `prototype __proto__ inheritance works`,
+ );
+ } catch (e) {
+ t.isNot(e, e, 'unexpected exception');
+ } finally {
+ t.end();
+ }
+});

whitelist: allow `ESCAPE` prefix to avoid swallowing `__proto__`