From 07ff084c87af4e567f6bf4f5e331742be94b6587 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Sat, 18 Jan 2025 19:50:43 -0800 Subject: [PATCH] refactor: prepare for non-trapping integrity trait (#2679) Closes: #XXXX Refs: /~https://github.com/Agoric/agoric-sdk/pull/10795 ## Description Prepare for anticipated introduction and use of the non-trapping integrity trait as explained at /~https://github.com/endojs/endo/blob/b12eb434b6672f0ceae41be55aac7f24c4562b7b/packages/ses/docs/preparing-for-stabilize.md These preparations must work now, before these traits are introduced, and should continue to work after these traits are introduced and used. ### Security Considerations Some things that had been deeply frozen automatically by `harden` are now manually frozen by explicit calls to `freeze`. We need to review these carefully to ensure that nothing has inadvertently be left unfrozen as a result of the changes in this PR. Some proxies will become unhardenable, but they will still be hardenable as of now, so mistaken hardenings will not be detected. ### Scaling Considerations For this PR by itself, none. Using the shim-based implementation of the non-trapping trait will have scaling consequences: /~https://github.com/endojs/endo/pull/2675 ### Documentation Considerations /~https://github.com/endojs/endo/blob/b12eb434b6672f0ceae41be55aac7f24c4562b7b/packages/ses/docs/preparing-for-stabilize.md will need to be reflected in developer docs. ### Testing Considerations Since this PR by itself should be a pure refactor with no observable changes, there is nothing to test at this stage. The testing burden will come with /~https://github.com/endojs/endo/pull/2675 to see how adequate these preparations were. ### Compatibility Considerations The point. This changes to coding patterns that should be compat both with the current status quo and with /~https://github.com/endojs/endo/pull/2675 ### Upgrade Considerations As a pure refactor, none. --- packages/captp/src/trap.js | 22 ++- packages/eventual-send/src/E.js | 32 +++-- packages/eventual-send/src/handled-promise.js | 3 + .../far/test/marshal-far-function.test.js | 2 +- packages/marshal/src/marshal-stringify.js | 16 ++- .../marshal/test/marshal-far-function.test.js | 2 +- packages/pass-style/test/passStyleOf.test.js | 125 ++++++++++++------ packages/ses/docs/preparing-for-stabilize.md | 30 +++++ packages/ses/src/commons.js | 6 - .../src/sloppy-globals-scope-terminator.js | 11 +- packages/ses/src/strict-scope-terminator.js | 13 +- 11 files changed, 191 insertions(+), 71 deletions(-) create mode 100644 packages/ses/docs/preparing-for-stabilize.md diff --git a/packages/captp/src/trap.js b/packages/captp/src/trap.js index 5b7c81afda..f0f4b1060c 100644 --- a/packages/captp/src/trap.js +++ b/packages/captp/src/trap.js @@ -1,5 +1,7 @@ // Lifted mostly from `@endo/eventual-send/src/E.js`. +const { freeze } = Object; + /** * Default implementation of Trap for near objects. * @@ -56,6 +58,22 @@ const TrapProxyHandler = (x, trapImpl) => { }); }; +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const funcTarget = freeze(() => {}); + +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const objTarget = freeze({ __proto__: null }); + /** * @param {import('./types.js').TrapImpl} trapImpl * @returns {import('./ts-types.js').Trap} @@ -63,7 +81,7 @@ const TrapProxyHandler = (x, trapImpl) => { export const makeTrap = trapImpl => { const Trap = x => { const handler = TrapProxyHandler(x, trapImpl); - return harden(new Proxy(() => {}, handler)); + return new Proxy(funcTarget, handler); }; const makeTrapGetterProxy = x => { @@ -77,7 +95,7 @@ export const makeTrap = trapImpl => { return trapImpl.get(x, prop); }, }); - return new Proxy(Object.create(null), handler); + return new Proxy(objTarget, handler); }; Trap.get = makeTrapGetterProxy; diff --git a/packages/eventual-send/src/E.js b/packages/eventual-send/src/E.js index a114e3ccb2..d92d61135d 100644 --- a/packages/eventual-send/src/E.js +++ b/packages/eventual-send/src/E.js @@ -2,7 +2,7 @@ import { trackTurns } from './track-turns.js'; import { makeMessageBreakpointTester } from './message-breakpoints.js'; const { details: X, quote: q, Fail, error: makeError } = assert; -const { assign, create } = Object; +const { assign, freeze } = Object; /** * @import { HandledPromiseConstructor } from './types.js'; @@ -167,6 +167,23 @@ const makeEGetProxyHandler = (x, HandledPromise) => get: (_target, prop) => HandledPromise.get(x, prop), }); +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const funcTarget = freeze(() => {}); + +/** +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const objTarget = freeze({ __proto__: null }); + /** * @param {HandledPromiseConstructor} HandledPromise */ @@ -183,7 +200,7 @@ const makeE = HandledPromise => { * @returns {ECallableOrMethods>} method/function call proxy */ // @ts-expect-error XXX typedef - x => harden(new Proxy(() => {}, makeEProxyHandler(x, HandledPromise))), + x => new Proxy(funcTarget, makeEProxyHandler(x, HandledPromise)), { /** * E.get(x) returns a proxy on which you can get arbitrary properties. @@ -196,11 +213,8 @@ const makeE = HandledPromise => { * @returns {EGetters>} property get proxy * @readonly */ - get: x => - // @ts-expect-error XXX typedef - harden( - new Proxy(create(null), makeEGetProxyHandler(x, HandledPromise)), - ), + // @ts-expect-error XXX typedef + get: x => new Proxy(objTarget, makeEGetProxyHandler(x, HandledPromise)), /** * E.resolve(x) converts x to a handled promise. It is @@ -224,9 +238,7 @@ const makeE = HandledPromise => { */ sendOnly: x => // @ts-expect-error XXX typedef - harden( - new Proxy(() => {}, makeESendOnlyProxyHandler(x, HandledPromise)), - ), + new Proxy(funcTarget, makeESendOnlyProxyHandler(x, HandledPromise)), /** * E.when(x, res, rej) is equivalent to diff --git a/packages/eventual-send/src/handled-promise.js b/packages/eventual-send/src/handled-promise.js index 17785b3529..cef6a957a6 100644 --- a/packages/eventual-send/src/handled-promise.js +++ b/packages/eventual-send/src/handled-promise.js @@ -309,6 +309,9 @@ export const makeHandledPromise = () => { if (proxyOpts) { const { handler: proxyHandler, + // The proxy target can be frozen but should not be hardened + // so it remains trapping. + // See /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md target: proxyTarget, revokerCallback, } = proxyOpts; diff --git a/packages/far/test/marshal-far-function.test.js b/packages/far/test/marshal-far-function.test.js index c86c2b1fb6..28d544e6c6 100644 --- a/packages/far/test/marshal-far-function.test.js +++ b/packages/far/test/marshal-far-function.test.js @@ -58,7 +58,7 @@ test('Data can contain far functions', t => { const arrow = Far('arrow', a => a + 1); t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord'); const mightBeMethod = a => a + 1; - t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), { + t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), { message: /Remotables with non-methods like "x" /, }); }); diff --git a/packages/marshal/src/marshal-stringify.js b/packages/marshal/src/marshal-stringify.js index e581cdefed..c39d97b4d8 100644 --- a/packages/marshal/src/marshal-stringify.js +++ b/packages/marshal/src/marshal-stringify.js @@ -5,6 +5,8 @@ import { makeMarshal } from './marshal.js'; /** @import {Passable} from '@endo/pass-style' */ +const { freeze } = Object; + /** @type {import('./types.js').ConvertValToSlot} */ const doNotConvertValToSlot = val => Fail`Marshal's stringify rejects presences and promises ${val}`; @@ -23,7 +25,14 @@ const badArrayHandler = harden({ }, }); -const badArray = harden(new Proxy(harden([]), badArrayHandler)); +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const arrayTarget = freeze(/** @type {any[]} */ ([])); +const badArray = new Proxy(arrayTarget, badArrayHandler); const { serialize, unserialize } = makeMarshal( doNotConvertValToSlot, @@ -48,7 +57,10 @@ harden(stringify); */ const parse = str => unserialize( - harden({ + // `freeze` but not `harden` since the `badArray` proxy and its target + // must remain trapping. + // See /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + freeze({ body: str, slots: badArray, }), diff --git a/packages/marshal/test/marshal-far-function.test.js b/packages/marshal/test/marshal-far-function.test.js index b546b11621..223fdd27e5 100644 --- a/packages/marshal/test/marshal-far-function.test.js +++ b/packages/marshal/test/marshal-far-function.test.js @@ -60,7 +60,7 @@ test('Data can contain far functions', t => { const arrow = Far('arrow', a => a + 1); t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord'); const mightBeMethod = a => a + 1; - t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), { + t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), { message: /Remotables with non-methods like "x" /, }); }); diff --git a/packages/pass-style/test/passStyleOf.test.js b/packages/pass-style/test/passStyleOf.test.js index d09cd55260..0990da3441 100644 --- a/packages/pass-style/test/passStyleOf.test.js +++ b/packages/pass-style/test/passStyleOf.test.js @@ -13,7 +13,35 @@ const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ ( global.harden ); -const { getPrototypeOf, defineProperty } = Object; +const { getPrototypeOf, defineProperty, freeze } = Object; +/** + * Local alias of `harden` to eventually be switched to whatever applies + * the suppress-trapping integrity trait. For the shim at + * /~https://github.com/endojs/endo/pull/2673 + * that is `suppressTrapping`, which is why we choose that name for the + * placeholder here. But it is a separate definition so these aliased uses + * do not yet depend on the final name. + * + * TODO Once we do have support for an explicit `suppressTrapping` operation, + * we should import that instead, and if necessary rename all uses to that + * operation's final name. + */ +const hardenToBeSuppressTrapping = harden; + +/** + * Local alias of `freeze` to eventually be switched to whatever applies + * the suppress-trapping integrity trait. For the shim at + * /~https://github.com/endojs/endo/pull/2673 + * that is `suppressTrapping`, which is why we choose that name for the + * placeholder here. But it is a separate definition so these aliased uses + * do not yet depend on the final name. + * + * TODO Once we do have support for an explicit `suppressTrapping` operation, + * we should import that instead, and if necessary rename all uses to that + * operation's final name. + */ +const freezeToBeSuppressTrapping = freeze; + const { ownKeys } = Reflect; test('passStyleOf basic success cases', t => { @@ -110,13 +138,17 @@ test('some passStyleOf rejections', t => { }); /** - * For testing purposes, makes a TagRecord-like object with + * For testing purposes, makes a *non-frozen* TagRecord-like object with * non-enumerable PASS_STYLE and Symbol.toStringTag properties. * A valid Remotable must inherit from a valid TagRecord. + * - Before stabilize/suppressTrapping, a valid TagRecord must be frozen. + * - After stabilize/suppressTrapping, a valid TagRecord must also be + * stable/non-trapping, for example, because it was hardened. * * @param {string} [tag] * @param {object|null} [proto] * @returns {{ [PASS_STYLE]: 'remotable', [Symbol.toStringTag]: string }} + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md */ const makeTagishRecord = (tag = 'Remotable', proto = undefined) => { return Object.create(proto === undefined ? Object.prototype : proto, { @@ -192,17 +224,23 @@ test('passStyleOf testing remotables', t => { t.is(passStyleOf(Far('foo', () => 'far function')), 'remotable'); const tagRecord1 = harden(makeTagishRecord('Alleged: manually constructed')); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const farObj1 = harden({ - __proto__: tagRecord1, - }); + const farObj1 = hardenToBeSuppressTrapping({ __proto__: tagRecord1 }); t.is(passStyleOf(farObj1), 'remotable'); const tagRecord2 = makeTagishRecord('Alleged: tagRecord not hardened'); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const farObj2 = Object.freeze({ - __proto__: tagRecord2, - }); + /** + * Do not freeze `tagRecord2` in order to test that an object with + * a non-frozen __proto__ is not passable. + * + * TODO In order to run this test before we have explicit support for a + * non-trapping integrity trait, we have to `freeze` here but not `harden`. + * However, once we do have that support, and `passStyleOf` checks that + * its argument is also non-trapping, we still need to avoid `harden` + * because that would also harden `__proto__`. So we will need to + * explicitly make this non-trapping, which we cannot yet express. + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ + const farObj2 = freezeToBeSuppressTrapping({ __proto__: tagRecord2 }); if (harden.isFake) { t.is(passStyleOf(farObj2), 'remotable'); } else { @@ -212,39 +250,24 @@ test('passStyleOf testing remotables', t => { }); } - const tagRecord3 = Object.freeze( - makeTagishRecord('Alleged: both manually frozen'), - ); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const farObj3 = Object.freeze({ - __proto__: tagRecord3, - }); + const tagRecord3 = harden(makeTagishRecord('Alleged: both manually frozen')); + const farObj3 = hardenToBeSuppressTrapping({ __proto__: tagRecord3 }); t.is(passStyleOf(farObj3), 'remotable'); const tagRecord4 = harden(makeTagishRecord('Remotable')); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const farObj4 = harden({ - __proto__: tagRecord4, - }); + const farObj4 = hardenToBeSuppressTrapping({ __proto__: tagRecord4 }); t.is(passStyleOf(farObj4), 'remotable'); const tagRecord5 = harden(makeTagishRecord('Not alleging')); - const farObj5 = harden({ - __proto__: tagRecord5, - }); + const farObj5 = hardenToBeSuppressTrapping({ __proto__: tagRecord5 }); t.throws(() => passStyleOf(farObj5), { message: /For now, iface "Not alleging" must be "Remotable" or begin with "Alleged: " or "DebugName: "; unimplemented/, }); const tagRecord6 = harden(makeTagishRecord('Alleged: manually constructed')); - const farObjProto6 = harden({ - __proto__: tagRecord6, - }); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const farObj6 = harden({ - __proto__: farObjProto6, - }); + const farObjProto6 = hardenToBeSuppressTrapping({ __proto__: tagRecord6 }); + const farObj6 = hardenToBeSuppressTrapping({ __proto__: farObjProto6 }); t.is(passStyleOf(farObj6), 'remotable', 'tagRecord grandproto is accepted'); // Our current agoric-sdk plans for far classes are to create a class-like @@ -298,7 +321,7 @@ test('passStyleOf testing remotables', t => { const tagRecordA1 = harden( makeTagishRecord('Alleged: null-proto tagRecord proto', null), ); - const farObjA1 = harden({ __proto__: tagRecordA1 }); + const farObjA1 = hardenToBeSuppressTrapping({ __proto__: tagRecordA1 }); t.throws( () => passStyleOf(farObjA1), { message: unusualTagRecordProtoMessage }, @@ -308,8 +331,8 @@ test('passStyleOf testing remotables', t => { const tagRecordA2 = harden( makeTagishRecord('Alleged: null-proto tagRecord grandproto', null), ); - const farObjProtoA2 = harden({ __proto__: tagRecordA2 }); - const farObjA2 = harden({ __proto__: farObjProtoA2 }); + const farObjProtoA2 = hardenToBeSuppressTrapping({ __proto__: tagRecordA2 }); + const farObjA2 = hardenToBeSuppressTrapping({ __proto__: farObjProtoA2 }); t.throws( () => passStyleOf(farObjA2), { message: unusualTagRecordProtoMessage }, @@ -323,12 +346,10 @@ test('passStyleOf testing remotables', t => { const fauxTagRecordB = harden( makeTagishRecord('Alleged: manually constructed', harden({})), ); - const farObjProtoB = harden({ + const farObjProtoB = hardenToBeSuppressTrapping({ __proto__: fauxTagRecordB, }); - const farObjB = harden({ - __proto__: farObjProtoB, - }); + const farObjB = hardenToBeSuppressTrapping({ __proto__: farObjProtoB }); t.throws(() => passStyleOf(farObjB), { message: 'cannot serialize Remotables with non-methods like "Symbol(passStyle)" in "[Alleged: manually constructed]"', @@ -339,7 +360,9 @@ test('passStyleOf testing remotables', t => { ); Object.defineProperty(farObjProtoWithExtra, 'extra', { value: () => {} }); harden(farObjProtoWithExtra); - const badFarObjExtraProtoProp = harden({ __proto__: farObjProtoWithExtra }); + const badFarObjExtraProtoProp = hardenToBeSuppressTrapping({ + __proto__: farObjProtoWithExtra, + }); t.throws(() => passStyleOf(badFarObjExtraProtoProp), { message: 'Unexpected properties on Remotable Proto ["extra"]', }); @@ -387,7 +410,23 @@ test('remotables - safety from the gibson042 attack', t => { }, ); - const makeInput = () => Object.freeze({ __proto__: mercurialProto }); + /** + * Do not freeze `mercurialProto` in order to test that an object with + * a non-frozen __proto__ is not passable. Once we have support for + * non-trapping, we should generalize this test (or add a new one) where + * `mercurialProto` is frozen but still trapping. This test would then + * test that a valid TagRecord must be non-trapping. + * + * TODO In order to run this test before we have explicit support for a + * non-trapping integrity trait, we have to `freeze` here but not `harden`. + * However, once we do have that support, and `passStyleOf` checks that + * its argument is also non-trapping, we still need to avoid `harden` + * because that would also harden `__proto__`. So we will need to + * explicitly make this non-trapping, which we cannot yet express. + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ + const makeInput = () => + freezeToBeSuppressTrapping({ __proto__: mercurialProto }); const input1 = makeInput(); const input2 = makeInput(); @@ -448,14 +487,12 @@ test('Allow toStringTag overrides', t => { t.is(`${alice}`, '[object DebugName: Allison]'); t.is(`${q(alice)}`, '"[DebugName: Allison]"'); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const carol = harden({ __proto__: alice }); + const carol = hardenToBeSuppressTrapping({ __proto__: alice }); t.is(passStyleOf(carol), 'remotable'); t.is(`${carol}`, '[object DebugName: Allison]'); t.is(`${q(carol)}`, '"[DebugName: Allison]"'); - /** @type {any} UNTIL /~https://github.com/microsoft/TypeScript/issues/38385 */ - const bob = harden({ + const bob = hardenToBeSuppressTrapping({ __proto__: carol, [Symbol.toStringTag]: 'DebugName: Robert', }); diff --git a/packages/ses/docs/preparing-for-stabilize.md b/packages/ses/docs/preparing-for-stabilize.md new file mode 100644 index 0000000000..55953a1783 --- /dev/null +++ b/packages/ses/docs/preparing-for-stabilize.md @@ -0,0 +1,30 @@ +# Preparing for the Non-trapping Integrity Trait + +The [Stabilize proposal](/~https://github.com/tc39/proposal-stabilize) is currently at stage 1 of the tc39 process. It proposes three distinct integrity traits whose current placeholder names are: +- ***fixed***: would mitigate the return-override mistake by preventing objects with this trait from being stamped with new class-private-fields. +- ***overridable***: would mitigate the assignment-override mistake by enabling non-writable properties inherited from an object with this trait to be overridden by property assignment on an inheriting object. +- ***non-trapping***: would mitigate proxy-based reentrancy hazards by having a proxy whose target carries this trait never trap to its handler, but rather just perform the default action directly on this non-trapping target. + +Draft PR [feat(non-trapping-shim): shim of the non-trapping integrity trait #2673](/~https://github.com/endojs/endo/pull/2673) is a shim for this non-trapping integrity trait. The names it introduces are placeholders, since the bikeshedding process for these names has not yet concluded. + +Draft PR [feat(ses,pass-style): use non-trapping integrity trait for safety #2675](/~https://github.com/endojs/endo/pull/2675) uses this support for the non-trapping integity trait to mitigate reentrancy attacks from hardened objects, expecially passable copy-data objects like copyLists, copyRecords, and taggeds. To do so, it makes two fundamental changes: +- Where `harden` made the object at every step frozen, that PR changes `harden` to also make those objects non-trapping. +- Where `passStyleOf` checked that objects are frozen, that PR changes `passStyleOf` to also check that those objects are non-trapping. + +## How proxy code should prepare + +[#2673](/~https://github.com/endojs/endo/pull/2673) will *by default* produce proxies that refuse to be made non-trapping. An explicit handler trap (perhaps named `stabilize` or `suppressTrapping`) will need to be explicitly provided to make a proxy that allows itself to be made non-trapping. This is the right default, because proxies on frozen almost-empty objects can still have useful trap behavior for their `get`, `set`, `has`, and `apply` traps. Even on a frozen target +- the `get`, `set`, and `has` traps applied to a non-own property name are still general traps that can have useful trapping behavior. +- the `apply` trap can ignore the target's call behavior and just do its own thing. + +However, to prepare for these changes, we need to avoid hardening both such proxies and their targets. We need to avoid hardening their target because this will bypass the traps. We need to avoid hardening the proxy because such proxies will *by default* refuse to be made non-trapping, and thus refuse to be hardened. + +Some proxies, such as that returned by `E(...)`, exist only to provide such trapping behavior. Their targets will typically be trivial useless empty frozen objects or almost empty frozen functions. Such frozen targets can be safely shared between multiple proxy instances because they are encapsulated within the proxy. +- Before `stabilize`/`suppressTrapping`, this is safe because they are already frozen, and so they cannot be damaged by the proxies that encapsulate them. +- After `stabilize`/`suppressTrapping`, this is safe because the only damage that could be done would be by `stabilize`/`suppressTrapping`. These proxies do not explicitly provide such a trap, and thus will use the default behavior which is to refuse to be made non-trapping. + +Because such trivial targets, when safely encapsulated, can be safely shared, their definitions should typically appear at top level of their module. + +## How passable objects should prepare + +Although we think of `passStyleOf` as requiring its input to be hardened, `passStyleOf` instead checked that each relevant object is frozen. Manually freezing all objects reachable from a root object had been equivalent to hardening that root object. With these changes, even such manual transitive freezing will not make an object passable. To prepare for these changes, use `harden` explicitly instead. diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index 5925c17eae..5211fa075d 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -269,12 +269,6 @@ export const finalizationRegistryUnregister = export const getConstructorOf = fn => reflectGet(getPrototypeOf(fn), 'constructor'); -/** - * immutableObject - * An immutable (frozen) empty object that is safe to share. - */ -export const immutableObject = freeze(create(null)); - /** * isObject tests whether a value is an object. * Today, this is equivalent to: diff --git a/packages/ses/src/sloppy-globals-scope-terminator.js b/packages/ses/src/sloppy-globals-scope-terminator.js index 01e3860856..e1ccfb39e8 100644 --- a/packages/ses/src/sloppy-globals-scope-terminator.js +++ b/packages/ses/src/sloppy-globals-scope-terminator.js @@ -3,7 +3,6 @@ import { create, freeze, getOwnPropertyDescriptors, - immutableObject, reflectSet, } from './commons.js'; import { @@ -11,6 +10,14 @@ import { alwaysThrowHandler, } from './strict-scope-terminator.js'; +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const objTarget = freeze({ __proto__: null }); + /* * createSloppyGlobalsScopeTerminator() * strictScopeTerminatorHandler manages a scopeTerminator Proxy which serves as @@ -45,7 +52,7 @@ export const createSloppyGlobalsScopeTerminator = globalObject => { ); const sloppyGlobalsScopeTerminator = new Proxy( - immutableObject, + objTarget, sloppyGlobalsScopeTerminatorHandler, ); diff --git a/packages/ses/src/strict-scope-terminator.js b/packages/ses/src/strict-scope-terminator.js index 7257afccd1..5f8c500a05 100644 --- a/packages/ses/src/strict-scope-terminator.js +++ b/packages/ses/src/strict-scope-terminator.js @@ -7,12 +7,19 @@ import { freeze, getOwnPropertyDescriptors, globalThis, - immutableObject, } from './commons.js'; import { assert } from './error/assert.js'; const { Fail, quote: q } = assert; +/** + * `freeze` but not `harden` the proxy target so it remains trapping. + * Thus, it should not be shared outside this module. + * + * @see /~https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md + */ +const objTarget = freeze({ __proto__: null }); + /** * alwaysThrowHandler * This is an object that throws if any property is called. It's used as @@ -21,7 +28,7 @@ const { Fail, quote: q } = assert; * create one and share it between all Proxy handlers. */ export const alwaysThrowHandler = new Proxy( - immutableObject, + objTarget, freeze({ get(_shadow, prop) { Fail`Please report unexpected scope handler trap: ${q(String(prop))}`; @@ -88,6 +95,6 @@ export const strictScopeTerminatorHandler = freeze( ); export const strictScopeTerminator = new Proxy( - immutableObject, + objTarget, strictScopeTerminatorHandler, );