feat(eventual-send): Harden HandledPromise modulo intrinsic exit points
MODIFY
packages/eventual-send/src/handled-promise.js
packages/eventual-send/src/handled-promise.js
@@ -19,6 +19,7 @@ const { Fail, details: X, quote: q } = assert;
const {
create,
+ freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
defineProperties,
@@ -28,7 +29,7 @@ const {
is: objectIs,
} = Object;
-const { apply, construct } = Reflect;
+const { apply, construct, ownKeys } = Reflect;
const SEND_ONLY_RE = /^(.*)SendOnly$/;
@@ -563,14 +564,19 @@ export const makeHandledPromise = () => {
// FIXME: This is really ugly to bypass the type system, but it will be better
// once we use Promise.delegated and don't have any [[Constructor]] behaviours.
- /** @type {unknown} */
- const unknownBaseHandledPromise = baseHandledPromise;
- HandledPromise = /** @type {typeof HandledPromise} */ (
- unknownBaseHandledPromise
- );
-
- // We cannot harden(HandledPromise) because we're a vetted shim which
- // runs before lockdown() allows harden to function. In that case,
- // though, globalThis.HandledPromise will be hardened after lockdown.
+ // @ts-expect-error cast
+ HandledPromise = baseHandledPromise;
+
+ // We're a vetted shim which runs before `lockdown` allows
+ // `harden(HandledPromise)` to function, but single-level `freeze` is a
+ // suitable replacement because all mutable objects reachable afterwards are
+ // intrinsics hardened by lockdown.
+ freeze(HandledPromise);
+ for (const key of ownKeys(HandledPromise)) {
+ // prototype is the intrinsic Promise.prototype to be hardened by lockdown.
+ if (key !== 'prototype') {
+ freeze(HandledPromise[key]);
+ }
+ }
return HandledPromise;
};

MODIFY
packages/eventual-send/test/test-hp.js
packages/eventual-send/test/test-hp.js
@@ -3,6 +3,36 @@ import { test } from './prepare-test-env-ava.js';
import { HandledPromise } from './get-hp.js';
+const { getPrototypeOf, isFrozen } = Object;
+const { ownKeys, getOwnPropertyDescriptor } = Reflect;
+
+const { quote: q } = assert;
+
+test('sufficiently hardened', t => {
+ const expectedReachableIntrinsics = new Set([
+ Function.prototype,
+ Promise,
+ Promise.prototype,
+ ]);
+ const checkSufficientlyHardened = (val, path = []) => {
+ if (Object(val) !== val || expectedReachableIntrinsics.has(val)) {
+ return;
+ }
+ t.true(isFrozen(val), `${q(path)} is frozen`);
+ for (const key of ownKeys(val)) {
+ const keyPath = path.concat(key);
+ const desc = getOwnPropertyDescriptor(val, key);
+ t.true(
+ ownKeys(desc).includes('value'),
+ `${q(keyPath)} is a data property`,
+ );
+ checkSufficientlyHardened(desc.value, keyPath);
+ }
+ checkSufficientlyHardened(getPrototypeOf(val), path.concat('__proto__'));
+ };
+ checkSufficientlyHardened(HandledPromise, ['HandledPromise']);
+});
+
test('chained properties', async t => {
const pr = {};
const data = {};

feat(eventual-send): Harden HandledPromise modulo intrinsic exit points (#1468)