Skip to content

Commit

Permalink
feat(ses,module-source): Add ModuleSource shim (#2463)
Browse files Browse the repository at this point in the history
Refs: #2252

## Description

To test feature the degree of compatibility between a version of XS, XS
with Endo shims, and Node.js with Endo shims, we need a shim for
ModuleSource proper. This lets us create an environment with a global
`ModuleSource`, where `ModuleSource` is a shared intrinsic of all
Compartments.

This change both introduces the shim and the necessary accommodations
for the existence of a global ModuleSource shared intrinsic in SES.

### Security Considerations

The new ModuleSource is subject to hardening of shared intrinsics during
SES lockdown. A failure to harden the shared intrinsic would lead to a
potential for interference or communication between isolated
compartments.

### Scaling Considerations

None.

### Documentation Considerations

None.

### Testing Considerations

This includes a test for SES that verifies that ModuleSource is properly
propagated and hardened.

### Compatibility Considerations

None.

### Upgrade Considerations

None.
  • Loading branch information
kriskowal authored Sep 24, 2024
2 parents 05f18c1 + f78f9d9 commit 349f0e8
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 4 deletions.
5 changes: 5 additions & 0 deletions packages/module-source/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
User-visible changes in `@endo/module-source`:

# Next release

- Adds `@endo/module-source/shim.js` to shim `globalThis.ModuleSource`.
The shim currently replaces the native `globalThis.ModuleSource` if present.

# v1.0.0 (2024-07-30)

- Renamed from `@endo/static-module-record` to `@endo/module-source` exporting
Expand Down
1 change: 1 addition & 0 deletions packages/module-source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"main": "./index.js",
"exports": {
".": "./index.js",
"./shim.js": "./shim.js",
"./package.json": "./package.json"
},
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions packages/module-source/shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* global globalThis */

import { ModuleSource } from './index.js';

Object.defineProperty(globalThis, 'ModuleSource', {
value: ModuleSource,
enumerable: false,
writable: true,
configurable: true,
});
29 changes: 29 additions & 0 deletions packages/module-source/src/module-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,32 @@ export function ModuleSource(source, opts = {}) {
this.__needsImportMeta__ = needsImportMeta;
freeze(this);
}

// AbstractModuleSource
// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
//
// We are attempting to ensure that a JavaScript shim (particularly ses) is
// forward-compatible as the engine evolves beneath it, with or without this
// ModuleSource shim, and with our without a native AbstractModuleSource which
// remains undecided.
// Lockdown does not gracefully handle the presence of an unexpected prototype,
// but can tolerate the absence of an expected prototype.
// So, we are providing AbstractModuleSource since we can better tolerate the
// various uncertain futures.
//
// WebAssembly and ModuleSource are both in motion.
// The Source Phase Imports proposal implies an additional AbstractModuleSource
// layer above the existing WebAssembly.Module that would be shared by
// the JavaScript ModuleSource prototype chains.
// At time of writing, no version of WebAssembly provides the shared base
// class, and the ModuleSource *shim* gains nothing from sharing one when that
// prototype when it comes into being.
// So, we do not attempt to entangle our AbstractModuleSource with
// WebAssembly.Module.

function AbstractModuleSource() {
// no-op, safe to super()
}

Object.setPrototypeOf(ModuleSource, AbstractModuleSource);
Object.setPrototypeOf(ModuleSource.prototype, AbstractModuleSource.prototype);
2 changes: 2 additions & 0 deletions packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ User-visible changes in `ses`:
- Node 18, Node 20, and all browsers have `structuredClone`
- Node <= 16 have neither, but are also no longer supported by Endo.
- Now exports separate layer for console shim: `ses/console-shim.js`.
- Adds permits for `ModuleSource` if present, either the native implementation
or from `@endo/module-source/shim.js`.

# v1.8.0 (2024-08-27)

Expand Down
14 changes: 14 additions & 0 deletions packages/ses/src/get-anonymous-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,19 @@ export const getAnonymousIntrinsics = () => {
);
}

if (globalThis.ModuleSource) {
const AbstractModuleSourcePrototype = getPrototypeOf(
globalThis.ModuleSource.prototype,
);
intrinsics['%AbstractModuleSourcePrototype%'] =
AbstractModuleSourcePrototype;
intrinsics['%AbstractModuleSource%'] =
AbstractModuleSourcePrototype.constructor;
}

if (globalThis.ModuleSource) {
intrinsics['%ModuleSourcePrototype%'] = globalThis.ModuleSource.prototype;
}

return intrinsics;
};
10 changes: 7 additions & 3 deletions packages/ses/src/permits-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ export default function whitelistIntrinsics(
return;
}

// We can't clean [[prototype]], therefore abort.
throw TypeError(`Unexpected intrinsic ${path}.__proto__ at ${protoName}`);
// We can't clean [[Prototype]], therefore abort.
throw TypeError(
`Unexpected [[Prototype]] at ${path}.__proto__ (expected ${protoName || '%ObjectPrototype%'})`,
);
}

/*
Expand Down Expand Up @@ -212,7 +214,9 @@ export default function whitelistIntrinsics(
}
}

throw TypeError(`Unexpected whitelist permit ${permit} at ${path}`);
throw TypeError(
`Unexpected property ${prop} with permit ${permit} at ${path}`,
);
}

/*
Expand Down
23 changes: 23 additions & 0 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ export const universalPropertyNames = {

// ESNext

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
ModuleSource: 'ModuleSource',

lockdown: 'lockdown',
harden: 'harden',

HandledPromise: 'HandledPromise', // TODO: Until Promise.delegate (see below).
};

Expand Down Expand Up @@ -1505,6 +1509,25 @@ export const permitted = {
resolve: fn,
},

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
'%AbstractModuleSourcePrototype%': {
constructor: '%AbstractModuleSource%',
'@@toStringTag': getter,
},
'%AbstractModuleSource%': {
'[[Proto]]': '%FunctionPrototype%',
prototype: '%AbstractModuleSourcePrototype%',
},
'%ModuleSourcePrototype%': {
'[[Proto]]': '%AbstractModuleSourcePrototype%',
constructor: 'ModuleSource',
'@@toStringTag': getter,
},
ModuleSource: {
'[[Proto]]': '%AbstractModuleSource%',
prototype: '%ModuleSourcePrototype%',
},

Promise: {
// Properties of the Promise Constructor
'[[Proto]]': '%FunctionPrototype%',
Expand Down
21 changes: 20 additions & 1 deletion packages/ses/test/module-source.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
/// <reference types="ses">

import test from 'ava';
import '../index.js';
import { ModuleSource } from '@endo/module-source';
import '@endo/module-source/shim.js';

lockdown();

test('module source property/prototype graph and hardening', t => {
const AbstractModuleSource = Object.getPrototypeOf(ModuleSource);
t.is(
Object.getPrototypeOf(ModuleSource.prototype),
AbstractModuleSource.prototype,
);

t.truthy(Object.isFrozen(ModuleSource));
t.truthy(Object.isFrozen(AbstractModuleSource));
t.truthy(Object.isFrozen(ModuleSource.prototype));
t.truthy(Object.isFrozen(AbstractModuleSource.prototype));
});

test('module source constructor', t => {
const msr = new ModuleSource(`
import foo from 'import-default-export-from-me.js';
Expand Down Expand Up @@ -42,3 +57,7 @@ test('module source constructor', t => {
'ModuleSource imports should be frozen',
);
});

test('ModuleSource is a shared intrinsic', t => {
t.truthy(ModuleSource === new Compartment().globalThis.ModuleSource);
});

0 comments on commit 349f0e8

Please sign in to comment.