Socket
Socket
Sign inDemoInstall

ses

Package Overview
Dependencies
Maintainers
6
Versions
103
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ses - npm Package Compare versions

Comparing version 1.5.0 to 1.6.0

65

NEWS.md

@@ -1,3 +0,64 @@

User-visible changes in SES:
User-visible changes in `ses`:
# v1.6.0 (2024-07-30)
- *NOTICE*: This version introduces multiple features to converge upon a
more common standard for [Hardened JavaScript](https://hardenedjs.org).
All code should begin migrating to these usage patterns as the older
patterns are now deprecated and will not be supported in a future major
version of SES.
- To converge on a portable pattern for using `Compartment`, introduces an
`__options__` property for the first argument of the `Compartment`
constructor that must be `true` if present and indicates the object is the
options bag and not the global endowments. All code going forward should
include this flag until the next major version of SES, when we plan for it to
become vesgitial and drop support for three-argument `Compartment`
construction.
In the unlikely event that existing code names an endowment `__options__`,
that code will break and need to be adjusted to adopt this version.
Because we rate this unlikely, we have elected not to mark this with
a major version bump.
- Adds a `__noNamespaceBox__` option that aligns the behavior of the `import`
method on SES `Compartment` with the behavior of XS and the behavior we will
champion for compartment standards.
All use of `Compartment` should migrate to use this option as the standard
behavior will be enabled by default with the next major version of SES.
- Adds support for module descriptors better aligned with XS.
Compartments use module desriptors to load and link modules.
The importHook, importNowHook, and moduleMapHook all return module descriptors
(sometimes promises for module descriptors).
The modules option or argument to the Compatment constructor has module
descriptors for all its values.
- `{record, specifier, compartment}` should become `{source: record,
specifier, compartment}`.
- `{specifier, compartment}` should become `{source: specifier,
compartment}`.
- `{record: compartment.module(specifier)}` should become `{namespace:
specifier, compartment}`.
- When running transpiled code on Node, the SES error taming
gives line-numbers into the generated JavaScript, which often don't match the
the original lines. This happens even with the normal development-time
lockdown options setting,
```js
errorTaming: 'unsafe'
```
or setting the environment variable
```sh
$ export LOCKDOWN_ERROR_TAMING=unsafe
```
To get the original line numbers, this release
adds `'unsafe-debug'`. This `errorTaming: 'unsafe-debug'` setting
should be used ***during development only*** when you can
sacrifice more security for a better debugging experience, as explained at
[`errorTaming` Options](https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md#errortaming-options).
With this setting, when running transpiled code on Node (e.g. tests written
in TypeScript),
the stacktrace line-numbers point back into the original
source, as they do on Node without SES.
# v1.5.0 (2024-05-06)

@@ -395,3 +456,3 @@

The purpose of the `details` template literal tag (often spelled `X` or `d`) together with the `quote` function (often spelled `q`) is to redact data from the error messages carried by error instances. With this release, the same `{errorTaming: 'unsafe'}` would suppress that redaction as well, so that all substitution values would act like they've been quoted. IOW, with this setting
The purpose of the `details` template literal tag (often spelled `X`) together with the `quote` function (often spelled `q`) is to redact data from the error messages carried by error instances. With this release, the same `{errorTaming: 'unsafe'}` would suppress that redaction as well, so that all substitution values would act like they've been quoted. IOW, with this setting

@@ -398,0 +459,0 @@ ```js

25

package.json
{
"name": "ses",
"version": "1.5.0",
"version": "1.6.0",
"description": "Hardened JavaScript for Fearless Cooperation",

@@ -79,10 +79,10 @@ "keywords": [

"dependencies": {
"@endo/env-options": "^1.1.4"
"@endo/env-options": "^1.1.5"
},
"devDependencies": {
"@endo/compartment-mapper": "^1.1.5",
"@endo/static-module-record": "^1.1.2",
"@endo/test262-runner": "^0.1.37",
"ava": "^6.1.2",
"babel-eslint": "^10.0.3",
"@endo/compartment-mapper": "^1.2.0",
"@endo/module-source": "^1.0.0",
"@endo/test262-runner": "^0.1.38",
"ava": "^6.1.3",
"babel-eslint": "^10.1.0",
"c8": "^7.14.0",

@@ -93,4 +93,4 @@ "core-js": "^3.31.0",

"eslint-config-prettier": "^9.1.0",
"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.29.1",
"prettier": "^3.2.5",

@@ -100,3 +100,3 @@ "sinon": "^15.1.0",

"tsd": "^0.30.7",
"typescript": "~5.5.0-dev.20240327"
"typescript": "5.5.2"
},

@@ -197,3 +197,4 @@ "files": [

"files": [
"test/**/test-*.js"
"test/**/test-*.*",
"test/**/*.test.*"
],

@@ -205,3 +206,3 @@ "timeout": "2m"

},
"gitHead": "08e59bc0d262565165636c2e3875bbe3dcb91cf8"
"gitHead": "681b813ccb1fa177905dabf2ed3f5f248cb33ce7"
}
# SES
SES is *hardened JavaScript*. SES stands for *fearless cooperation*.
This package is a SES [shim][define shim] for JavaScript features
[proposed][SES proposal] to ECMA TC39.
SES is a [shim][define shim] for [Hardened JavaScript][] as [proposed][SES
proposal] to ECMA TC39.
SES stands for *fearless cooperation*.
Hardened JavaScript is highly compatible with ordinary JavaScript.
Most existing JavaScript libraries can run on hardened JavaScript.
Most existing JavaScript libraries can run on Hardened JavaScript.

@@ -27,2 +27,10 @@ * **Compartments** Compartments are separate execution contexts: each one has

Agoric and MetaMask rely on Hardened JavaScript and this SES shim as part of
systems that sandbox third-party plugins or smart contracts and mitigate supply
chain attacks for production web applications, web extensions, and build
systems.
[![Agoric Logo](docs/agoric-x100.png)](https://agoric.com/)
[![MetaMask Logo](docs/metamask-x100.png)](https://metamask.io/)
See https://github.com/Agoric/Jessie to see how SES fits into the various

@@ -165,3 +173,6 @@ flavors of confined JavaScript execution. And visit

const c = new Compartment({
print: harden(console.log),
globals: {
print: harden(console.log),
},
__options__: true, // temporary migration affordance
});

@@ -210,3 +221,6 @@

```js
const powerfulCompartment = new Compartment({ Math });
const powerfulCompartment = new Compartment({
globals: { Math },
__options__: true, // temporary migration affordance
});
powerfulCompartment.globalThis.Date = Date;

@@ -260,3 +274,2 @@ ```

### Modules

@@ -266,13 +279,23 @@

For modules to work within a compartment, the creator must provide
a `resolveHook` and an `importHook`.
module descriptors.
A compartment can be configured with module descriptors, from highest to lowest
precedence:
- the `modules` map provided to the `Compartment` constructor,
- returned by a `moduleMapHook(specifier)` passed as an option to the
`Compartment` constructor.
- returned by either the `importHook(specifier)` or `importNowHook(specifier)`
option passed to the `Compartment` constructor. Calling
`compartment.import(specifier)` falls through to the `importHook` which may
return a promises, whereas `compartment.importNow(specifier)` falls through
to the synchronous `importNowHook`.
The `resolveHook` determines how the compartment will infer the full module
specifier for another module from a referrer module and the import specifier.
The `importHook` accepts a full specifier and asynchronously returns a
`StaticModuleRecord` for that module.
```js
import 'ses';
import { StaticModuleRecord } from '@endo/static-module-record';
import { ModuleSource } from '@endo/module-source';
const c1 = new Compartment({}, {}, {
const c1 = new Compartment({
name: "first compartment",

@@ -285,34 +308,82 @@ resolveHook: (moduleSpecifier, moduleReferrer) => {

const moduleText = await retrieve(moduleLocation);
return new StaticModuleRecord(moduleText, moduleLocation);
return {
source: new ModuleSource(moduleText, moduleLocation);
};
},
__options__: true, // temporary migration affordance
});
```
> The SES language specifies a global `StaticModuleRecord`, but this is not
> provided by the shim because it entrains a full JavaScript parser that is an
> unnecessary performance penalty for the SES runtime.
> Instead, the SES shim accepts a compiled static module record duck-type that
> The Hardened JavaScript language specifies a global `ModuleSource`, but this
> is not provided by the shim because it entrains a full JavaScript parser that
> is an unnecessary performance penalty for the SES runtime.
> Instead, the SES shim accepts a pre-compiled module source duck-type that
> is tightly coupled to the shim implementation.
> Third party modules can provide suitable implementations and even move the
> compile step to build time instead of runtime.
A compartment can also link a module in another compartment.
Each compartment has a `module` function that accepts a module specifier
and returns the module exports namespace for that module.
The module exports namespace is not useful for inspecting the exports of the
module until that module has been imported, but it can be passed into the
module map of another Compartment, creating a link.
```js
const c2 = new Compartment({}, {
'c1': c1.module('./main.js'),
}, {
const c2 = new Compartment({
name: "second compartment",
modules: {
'c1': {
source: './main.js',
compartment: c1,
},
},
resolveHook,
importHook,
__options__: true, // temporary migration affordance
});
```
### importHook aliases
#### Module Descriptors
Comparments can load and initialize module namespaces from module descriptors.
Like property descriptors, module descriptors are ordinary objects with various
forms.
##### Descriptors with `source` property
- If fhe value of the `source` property is a string, the parent compartment
loads the module but the compartment itself initializes the module.
- Otherwise, if the value of the `source` property is the module source, the
module is initialized from the module source.
- Otherwise, the value of the `source` property must be an object. The module
is loaded and initialized from the object according to the [virtual module
source](#VirtualModuleSource) pattern.
If the `importMeta` property is present, its value must be an object. The
default `importMeta` object is an empty object.
Compartments copy the `importMeta` object properties into the module
`import.meta` object like `Object.assign`.
If the `specifier` property is present, its value is coerced into a string and
becomes the referrer specifier of the module, on which all import specifiers
are resolved using the `resolveHook`.
##### Descriptors with `namespace` property
- If fhe value of the `namespace` property is a string, the descriptor shares a
module to be loaded and initialized by the compartment referred by the
`compartment` property.
- If the `compartment` property is present, its value must be a
compartment.
- If absent, the `compartment` property defaults to the compartment being
constructed in the `modules` option, or being hooked in the `loadHook`
and `loadNowHook` options.
- Otherwise, if the value of the `namespace` property is a module namepace, the
descriptor shares a module that is already available.
- Otherwise, the value of `namespace` property must be an object. The module is
loaded and initialized from the object according to the [virtual module
namespace](#VirtualModuleNamespace) pattern.
#### Redirects
If a compartment imports a module specified as `"./utility"` but actually

@@ -323,9 +394,9 @@ implemented by an alias like `"./utility/index.js"`, the `importHook` may

"request specifier".
The `importHook` may return an "alias" object with `record`, `compartment`,
and `module` properties.
The `importHook` may return an "alias" object with `source`, `compartment`,
and `specifier` properties.
- `record` must be a static module record, either a third-party module record
or a compiled static module record.
- `source` must be a module source, either a virtual module source
or a compiled module source.
- `compartment` is optional, to be specified if the alias transits to a
different compartment, and
the specified different compartment, and
- `specifier` is the full module specifier of the module in its compartment.

@@ -342,5 +413,9 @@ This defaults to the request specifier, which is only useful if the

for (const candidate of candidates) {
const record = await wrappedImportHook(candidate).catch(_ => undefined);
if (record !== undefined) {
return { record, specifier };
const source = await maybeImportSource(candidate);
if (source !== undefined) {
return {
source,
specifier: candidate,
compartment,
};
}

@@ -351,9 +426,10 @@ }

const compartment = new Compartment({}, {}, {
const compartment = new Compartment({
resolveHook,
importHook,
__options__: true, // temporary migration affordance
});
```
### moduleMapHook
#### moduleMapHook

@@ -377,34 +453,51 @@ The module map above allows modules to be introduced to a compartment up-front.

if (moduleSpecifier === 'even') {
return even.module('./index.js');
return {
source: './index.js',
compartment: even,
};
} else if (moduleSpecifier === 'odd') {
return odd.module('./index.js');
return {
source: './index.js',
compartment: odd,
};
}
};
const even = new Compartment({}, {}, {
const even = new Compartment({
resolveHook: nodeResolveHook,
importHook: makeImportHook('https://example.com/even'),
moduleMapHook,
__options__: true, // temporary migration affordance
});
const odd = new Compartment({}, {}, {
const odd = new Compartment({
resolveHook: nodeResolveHook,
importHook: makeImportHook('https://example.com/odd'),
moduleMapHook,
__options__: true, // temporary migration affordance
});
```
### importNowHook
#### importNowHook
Additionally, an `importNowHook` may be provided that the compartment will use as means to synchronously load modules not seen before in situations where calling out to asynchronous `importHook` is not possible.
Specifically, when `compartmentInstance.importNow('specifier')` is called, the compartment will first look up module records it's already aware of and call `moduleMapHook` and if none of that is successful in finding a module record matching the specifier, it will call `importNowHook` expecting to synchronously receive the same record type as from `importHook` or throw if it cannot.
Additionally, an `importNowHook` may be provided that the compartment will use
as means to synchronously load modules not seen before in situations where
calling out to asynchronous `importHook` is not possible.
Specifically, when `compartmentInstance.importNow('specifier')` is called, the
compartment will first look up module records it's already aware of and call
`moduleMapHook` and if none of that is successful in finding a module record
matching the specifier, it will call `importNowHook` expecting to synchronously
receive the same record type as from `importHook` or throw if it cannot.
```js
import 'ses';
import { StaticModuleRecord } from '@endo/static-module-record';
import { ModuleSource } from '@endo/module-source';
const c1 = new Compartment({}, {
'c2': c2.module('./main.js'),
}, {
const compartment = new Compartment({
name: "first compartment",
modules: {
c: {
source: new ModuleSource(''),
},
},
resolveHook: (moduleSpecifier, moduleReferrer) => {

@@ -416,23 +509,28 @@ return resolve(moduleSpecifier, moduleReferrer);

const moduleText = await retrieve(moduleLocation);
return new StaticModuleRecord(moduleText, moduleLocation);
return {
source: new ModuleSource(moduleText, moduleLocation),
};
},
importNowHook: moduleSpecifier => {
const moduleLocation = locate(moduleSpecifier);
// Platform specific synchronous read API can be used
// Platform-specific synchronous read API can be used
const moduleText = fs.readFileSync(moduleLocation);
return new StaticModuleRecord(moduleText, moduleLocation);
return {
source: new ModuleSource(moduleText, moduleLocation),
};
},
__options__: true, // temporary migration affordance
});
//... | importHook | importNowHook
await c1.import('a'); //| called | not called
c1.importNow('b'); //| not called | called
c1.importNow('a'); //| not called | not called
c1.importNow('c2'); //| not called | not called
await compartment.import('a'); //| called | not called
compartment.importNow('b'); //| not called | called
compartment.importNow('a'); //| not called | not called
compartment.importNow('c'); //| not called | not called
```
### Third-party modules
### <a name="VirtualModuleSource"></a> Virtual Module Source
To incorporate modules not implemented as JavaScript modules, third-parties may
implement a `StaticModuleRecord` interface.
The record must have an `imports` array and an `execute` method.
implement a `VirtualModuleSource` interface.
The object must have an `imports` array and an `execute` method.
The compartment will call `execute` with:

@@ -448,14 +546,18 @@

:warning: A future breaking version may allow the `importNow` and the `execute`
method of third-party static module records to return promises, to support
method of virtual module sources to return promises, to support
top-level await.
:warning: The virtual module source interface does not yet agree with the
[XS](https://www.moddable.com/hardening-xs) implementation of [Hardened
JavaScript](https://hardenedjs.org/).
### Compiled modules
Instead of the `StaticModuleRecord` constructor specified for the SES language,
the SES shim uses compiled static module records as a stand-in.
These can be created with a `StaticModuleRecord` constructor from a package
like `@endo/static-module-record`.
We omitted `StaticModuleRecord` from the SES shim because it entrains a heavy
Instead of the `ModuleSource` constructor specified for the SES language,
the SES shim uses compiled module source records as a stand-in.
These can be created with a `ModuleSource` constructor from a package
like `@endo/module-source`.
We omitted `ModuleSource` from the SES shim because it entrains a heavy
dependency on a JavaScript parser.
The shim depends upon a `StaticModuleRecord` constructor to analyze and
The shim depends upon a `ModuleSource` constructor to analyze and
transform the source of a JavaScript module (known as an ESM or a `.mjs` file)

@@ -465,3 +567,3 @@ into a JavaScript program suitable for evaluation with `compartment.evaluate`

A compiled static module record has the following shape:
A compiled module source record has the following shape:

@@ -498,3 +600,3 @@ - `imports` is a record that maps partial module specifiers to a list of

used in environments where eval is not available. Sandboxing of the functor is
the responsibility of the author of the StaticModuleRecord.
the responsibility of the author of the ModuleSource.
- `__liveExportsMap__` is a record that maps import names or names in the lexical

@@ -524,3 +626,7 @@ scope of the module to export names, for variables that may change after

const transforms = [addCodeCoverageInstrumentation];
const c = new Compartment({ console, coverage }, null, { transforms });
const c = new Compartment({
globals: { console, coverage },
transforms,
__options__: true, // temporary migration affordance
});
c.evaluate('console.log("Hello");');

@@ -542,3 +648,3 @@ ```

intercept the source and transform it before passing it to the
`StaticModuleRecord` constructor.
`ModuleSource` constructor.
These are distinct because programs and modules have distinct grammar

@@ -564,4 +670,6 @@ productions.

const __shimTransforms__ = [addCoverage];
const c = new Compartment({ console, coverage }, null, {
const c = new Compartment({
globals: { console, coverage },
__shimTransforms__,
__options__: true, // temporary migration affordance
});

@@ -673,7 +781,13 @@ c.evaluate('console.log("Hello");');

const promise = new Promise(resolve => {
const compartmentA = new Compartment(harden({ resolve }));
const compartmentA = new Compartment({
globals: harden({ resolve }),
__options__: true, // temporary migration affordance
});
compartmentA.evaluate(programA);
});
const compartmentB = new Compartment(harden({ promise }));
const compartmentB = new Compartment({
globals: harden({ promise }),
__options__: true, // temporary migration affordance
});
compartmentB.evaluate(programB);

@@ -838,2 +952,3 @@ ```

[Hardened JavaScript]: https://hardenedjs.org/
[define shim]: https://en.wikipedia.org/wiki/Shim_(computing)

@@ -840,0 +955,0 @@ [Endo Matrix]: https://matrix.to/#/#endojs:matrix.org

@@ -7,3 +7,2 @@ // @ts-check

Map,
ReferenceError,
TypeError,

@@ -13,3 +12,2 @@ WeakMap,

defineProperties,
entries,
promiseThen,

@@ -26,2 +24,3 @@ toStringTagSymbol,

} from './global-object.js';
import { assertEqual } from './error/assert.js';
import { sharedGlobalPropertyNames } from './permits.js';

@@ -31,7 +30,6 @@ import { load, loadNow } from './module-load.js';

import { getDeferredExports } from './module-proxy.js';
import { assert } from './error/assert.js';
import { compartmentEvaluate } from './compartment-evaluate.js';
import { makeSafeEvaluator } from './make-safe-evaluator.js';
const { quote: q } = assert;
/** @import {ModuleDescriptor} from '../types.js' */

@@ -49,15 +47,2 @@ // moduleAliases associates every public module exports namespace with its

// Compartments do not need an importHook or resolveHook to be useful
// as a vessel for evaluating programs.
// However, any method that operates the module system will throw an exception
// if these hooks are not available.
const assertModuleHooks = compartment => {
const { importHook, resolveHook } = weakmapGet(privateFields, compartment);
if (typeof importHook !== 'function' || typeof resolveHook !== 'function') {
throw TypeError(
'Compartment must be constructed with an importHook and a resolveHook for it to be able to load modules',
);
}
};
export const InertCompartment = function Compartment(

@@ -119,4 +104,2 @@ _endowments = {},

assertModuleHooks(this);
const { exportsProxy } = getDeferredExports(

@@ -133,2 +116,4 @@ this,

async import(specifier) {
const { noNamespaceBox } = weakmapGet(privateFields, this);
if (typeof specifier !== 'string') {

@@ -138,4 +123,2 @@ throw TypeError('first argument of import() must be a string');

assertModuleHooks(this);
return promiseThen(

@@ -150,2 +133,7 @@ load(privateFields, moduleAliases, this, specifier),

);
if (noNamespaceBox) {
return namespace;
}
// Legacy behavior: box the namespace object so that thenable modules
// do not get coerced into a promise accidentally.
return { namespace };

@@ -161,4 +149,2 @@ },

assertModuleHooks(this);
return load(privateFields, moduleAliases, this, specifier);

@@ -172,3 +158,2 @@ },

assertModuleHooks(this);
loadNow(privateFields, moduleAliases, this, specifier);

@@ -200,5 +185,55 @@ return compartmentImportNow(/** @type {Compartment} */ (this), specifier);

* @param {(object: object) => void} markVirtualizedNativeFunction
* @param {Compartment} [parentCompartment]
* @returns {Compartment['constructor']}
*/
// In order to facilitate migration from the deprecated signature
// of the compartment constructor,
// new Compartent(globals?, modules?, options?)
// to the new signature:
// new Compartment(options?)
// where globals and modules are expressed in the options bag instead of
// positional arguments, this function detects the temporary sigil __options__
// on the first argument and coerces compartments arguments into a single
// compartments object.
const compartmentOptions = (...args) => {
if (args.length === 0) {
return {};
}
if (
args.length === 1 &&
typeof args[0] === 'object' &&
args[0] !== null &&
'__options__' in args[0]
) {
const { __options__, ...options } = args[0];
assert(
__options__ === true,
`Compartment constructor only supports true __options__ sigil, got ${__options__}`,
);
return options;
} else {
const [
globals = /** @type {Map<string, any>} */ ({}),
modules = /** @type {Map<string, ModuleDescriptor>} */ ({}),
options = {},
] = args;
assertEqual(
options.modules,
undefined,
`Compartment constructor must receive either a module map argument or modules option, not both`,
);
assertEqual(
options.globals,
undefined,
`Compartment constructor must receive either globals argument or option, not both`,
);
return {
...options,
globals,
modules,
};
}
};
/** @type {MakeCompartmentConstructor} */

@@ -209,4 +244,5 @@ export const makeCompartmentConstructor = (

markVirtualizedNativeFunction,
parentCompartment = undefined,
) => {
function Compartment(endowments = {}, moduleMap = {}, options = {}) {
function Compartment(...args) {
if (new.target === undefined) {

@@ -223,2 +259,4 @@ throw TypeError(

__shimTransforms__ = [],
globals: endowmentsOption = {},
modules: moduleMapOption = {},
resolveHook,

@@ -229,4 +267,7 @@ importHook,

importMetaHook,
} = options;
__noNamespaceBox__: noNamespaceBox = false,
} = compartmentOptions(...args);
const globalTransforms = [...transforms, ...__shimTransforms__];
const endowments = { __proto__: null, ...endowmentsOption };
const moduleMap = { __proto__: null, ...moduleMapOption };

@@ -240,26 +281,2 @@ // Map<FullSpecifier, ModuleCompartmentRecord>

// Validate given moduleMap.
// The module map gets translated on-demand in module-load.js and the
// moduleMap can be invalid in ways that cannot be detected in the
// constructor, but these checks allow us to throw early for a better
// developer experience.
for (const [specifier, aliasNamespace] of entries(moduleMap || {})) {
if (typeof aliasNamespace === 'string') {
// TODO implement parent module record retrieval.
throw TypeError(
`Cannot map module ${q(specifier)} to ${q(
aliasNamespace,
)} in parent compartment`,
);
} else if (weakmapGet(moduleAliases, aliasNamespace) === undefined) {
// TODO create and link a synthetic module instance from the given
// namespace object.
throw ReferenceError(
`Cannot map module ${q(
specifier,
)} because it has no known compartment in this realm`,
);
}
}
const globalObject = {};

@@ -286,2 +303,3 @@

makeCompartmentConstructor: targetMakeCompartmentConstructor,
parentCompartment: this,
markVirtualizedNativeFunction,

@@ -314,2 +332,4 @@ });

instances,
parentCompartment,
noNamespaceBox,
});

@@ -316,0 +336,0 @@ }

@@ -153,4 +153,5 @@ // Copyright (C) 2019 Agoric, under Apache License 2.0

* Normally this is the function exported as `assert.details` and often
* spelled `d`. However, if the `{errorTaming: 'unsafe'}` option is given to
* `lockdown`, then `unredactedDetails` is used instead.
* spelled `X`. However, if the `{errorTaming: 'unsafe'}` or
* `{errorTaming: 'unsafe-debug'}` option is
* given to `lockdown`, then `unredactedDetails` is used instead.
*

@@ -178,3 +179,4 @@ * There are some unconditional uses of `redactedDetails` in this module. All

* were wrapped with the `quote` function above (the function normally
* spelled `q`). If the `{errorTaming: 'unsafe'}` option is given to
* spelled `q`). If the `{errorTaming: 'unsafe'}`
* or `{errorTaming: 'unsafe-debug'}` option is given to
* `lockdown`, then the lockdown-shim arranges for the global `assert` to be

@@ -569,1 +571,15 @@ * one whose `details` property is `unredactedDetails`.

export { assert };
// Internal, to obviate polymorphic dispatch, but may become rigorously
// consistent with @endo/error:
/** @type {AssertionFunctions['equal']} */
const assertEqual = assert.equal;
export {
assertEqual,
makeError,
note as annotateError,
redactedDetails as X,
quote as q,
};

@@ -61,3 +61,3 @@ # Logging Errors

Before repair or `lockdown`, we assume there is some prior "system" console bound to the global `console` in the start compartment. `{ consoleTaming: 'unsafe' }` leaves this unsafe system console in place. This system console is completely ignorant of the extra information in these side tables, which is therefore never seen. This includes the hidden data in the detailed messages. Instead, the system console shows the abbreviated `message` text produced by the `details` template literal that omits censored data. When combined with the default `{ errorTaming: 'safe' }`, the system console may not see error stack information. The `{ errorTaming: 'unsafe' }` setting does not remove error stacks from error objects, in which case the system console will find it as usual.
Before repair or `lockdown`, we assume there is some prior "system" console bound to the global `console` in the start compartment. `{ consoleTaming: 'unsafe' }` leaves this unsafe system console in place. This system console is completely ignorant of the extra information in these side tables, which is therefore never seen. This includes the hidden data in the detailed messages. Instead, the system console shows the abbreviated `message` text produced by the `details` template literal that omits censored data. When combined with the default `{ errorTaming: 'safe' }`, the system console may not see error stack information. The `{ errorTaming: 'unsafe' }` or `{ errorTaming: 'unsafe-details' }` setting does not remove error stacks from error objects, in which case the system console will find it as usual.

@@ -64,0 +64,0 @@ The default `{ consoleTaming: 'safe' }` setting replaces the system console with a root console that does use all these side tables to generate a more informative log. This root console wraps the prior system console. This root console outputs its log information only by invoking this wrapped system console, which therefore determines how this log information is made available to the external world. To support determinism, we will also need to support a no-op console setting, as explained at [deterministic handling of adversarial code calling console.log with a Proxy #1852](https://github.com/Agoric/agoric-sdk/issues/1852) and [Need no-op console setting for determinism #487](https://github.com/Agoric/SES-shim/issues/487).

@@ -10,2 +10,3 @@ import {

defineProperty,
getOwnPropertyDescriptors,
} from '../commons.js';

@@ -33,2 +34,3 @@ import { NativeErrors } from '../permits.js';

};
let initialGetStackString = tamedMethods.getStackString;

@@ -39,3 +41,7 @@ export default function tameErrorConstructor(

) {
if (errorTaming !== 'safe' && errorTaming !== 'unsafe') {
if (
errorTaming !== 'safe' &&
errorTaming !== 'unsafe' &&
errorTaming !== 'unsafe-debug'
) {
throw TypeError(`unrecognized errorTaming ${errorTaming}`);

@@ -48,5 +54,5 @@ }

const { captureStackTrace: originalCaptureStackTrace } = FERAL_ERROR;
const platform =
typeof FERAL_ERROR.captureStackTrace === 'function' ? 'v8' : 'unknown';
const { captureStackTrace: originalCaptureStackTrace } = FERAL_ERROR;
typeof originalCaptureStackTrace === 'function' ? 'v8' : 'unknown';

@@ -129,2 +135,41 @@ const makeErrorConstructor = (_ = {}) => {

if (errorTaming === 'unsafe-debug' && platform === 'v8') {
// This case is a kludge to work around
// https://github.com/endojs/endo/issues/1798
// https://github.com/endojs/endo/issues/2348
// https://github.com/Agoric/agoric-sdk/issues/8662
defineProperties(InitialError, {
prepareStackTrace: {
get() {
return FERAL_ERROR.prepareStackTrace;
},
set(newPrepareStackTrace) {
FERAL_ERROR.prepareStackTrace = newPrepareStackTrace;
},
enumerable: false,
configurable: true,
},
captureStackTrace: {
value: FERAL_ERROR.captureStackTrace,
writable: true,
enumerable: false,
configurable: true,
},
});
const descs = getOwnPropertyDescriptors(InitialError);
defineProperties(SharedError, {
stackTraceLimit: descs.stackTraceLimit,
prepareStackTrace: descs.prepareStackTrace,
captureStackTrace: descs.captureStackTrace,
});
return {
'%InitialGetStackString%': initialGetStackString,
'%InitialError%': InitialError,
'%SharedError%': SharedError,
};
}
// The default SharedError much be completely powerless even on v8,

@@ -179,3 +224,2 @@ // so the lenient `stackTraceLimit` accessor does nothing on all

let initialGetStackString = tamedMethods.getStackString;
if (platform === 'v8') {

@@ -188,3 +232,3 @@ initialGetStackString = tameV8ErrorConstructor(

);
} else if (errorTaming === 'unsafe') {
} else if (errorTaming === 'unsafe' || errorTaming === 'unsafe-debug') {
// v8 has too much magic around their 'stack' own property for it to

@@ -191,0 +235,0 @@ // coexist cleanly with this accessor. So only install it on non-v8

@@ -19,2 +19,3 @@ import {

weaksetHas,
TypeError,
} from '../commons.js';

@@ -168,2 +169,7 @@

) => {
if (errorTaming === 'unsafe-debug') {
throw TypeError(
'internal: v8+unsafe-debug special case should already be done',
);
}
// TODO: Proper CallSite types

@@ -170,0 +176,0 @@ /** @typedef {{}} CallSite */

@@ -72,7 +72,8 @@ import {

* @param {object} globalObject
* @param {object} param1
* @param {object} param1.intrinsics
* @param {object} param1.newGlobalPropertyNames
* @param {Function} param1.makeCompartmentConstructor
* @param {(object) => void} param1.markVirtualizedNativeFunction
* @param {object} args
* @param {object} args.intrinsics
* @param {object} args.newGlobalPropertyNames
* @param {Function} args.makeCompartmentConstructor
* @param {(object) => void} args.markVirtualizedNativeFunction
* @param {Compartment} [args.parentCompartment]
*/

@@ -86,2 +87,3 @@ export const setGlobalObjectMutableProperties = (

markVirtualizedNativeFunction,
parentCompartment,
},

@@ -120,2 +122,3 @@ ) => {

markVirtualizedNativeFunction,
parentCompartment,
),

@@ -122,0 +125,0 @@ );

@@ -61,3 +61,3 @@ // Copyright (C) 2018 Agoric

const { Fail, details: d, quote: q } = assert;
const { Fail, details: X, quote: q } = assert;

@@ -204,3 +204,3 @@ /** @type {Error=} */

assert.fail(
d`Already locked down at ${priorRepairIntrinsics} (SES_ALREADY_LOCKED_DOWN)`,
X`Already locked down at ${priorRepairIntrinsics} (SES_ALREADY_LOCKED_DOWN)`,
TypeError,

@@ -303,3 +303,3 @@ );

let optGetStackString;
if (errorTaming !== 'unsafe') {
if (errorTaming === 'safe') {
optGetStackString = intrinsics['%InitialGetStackString%'];

@@ -329,4 +329,8 @@ }

// @ts-ignore assert is absent on globalThis type def.
if (errorTaming === 'unsafe' && globalThis.assert === assert) {
// If errorTaming is 'unsafe' we replace the global assert with
if (
(errorTaming === 'unsafe' || errorTaming === 'unsafe-debug') &&
globalThis.assert === assert
) {
// If errorTaming is 'unsafe' or 'unsafe-debug' we replace the
// global assert with
// one whose `details` template literal tag does not redact

@@ -398,3 +402,3 @@ // unmarked substitution values. IOW, it blabs information that

assert.fail(
d`Already locked down at ${priorHardenIntrinsics} (SES_ALREADY_LOCKED_DOWN)`,
X`Already locked down at ${priorHardenIntrinsics} (SES_ALREADY_LOCKED_DOWN)`,
TypeError,

@@ -401,0 +405,0 @@ );

@@ -27,5 +27,5 @@ import { assert } from './error/assert.js';

export const makeThirdPartyModuleInstance = (
export const makeVirtualModuleInstance = (
compartmentPrivateFields,
staticModuleRecord,
moduleSource,
compartment,

@@ -45,12 +45,12 @@ moduleAliases,

if (staticModuleRecord.exports) {
if (moduleSource.exports) {
if (
!isArray(staticModuleRecord.exports) ||
arraySome(staticModuleRecord.exports, name => typeof name !== 'string')
!isArray(moduleSource.exports) ||
arraySome(moduleSource.exports, name => typeof name !== 'string')
) {
throw TypeError(
`SES third-party static module record "exports" property must be an array of strings for module ${moduleSpecifier}`,
`SES virtual module source "exports" property must be an array of strings for module ${moduleSpecifier}`,
);
}
arrayForEach(staticModuleRecord.exports, name => {
arrayForEach(moduleSource.exports, name => {
let value = exportsTarget[name];

@@ -101,7 +101,3 @@ const updaters = [];

// eslint-disable-next-line @endo/no-polymorphic-call
staticModuleRecord.execute(
exportsTarget,
compartment,
resolvedImports,
);
moduleSource.execute(exportsTarget, compartment, resolvedImports);
} catch (err) {

@@ -132,3 +128,3 @@ localState.errorFromExecute = err;

moduleSpecifier,
staticModuleRecord,
moduleSource,
importMeta: moduleRecordMeta,

@@ -144,3 +140,3 @@ } = moduleRecord;

__syncModuleFunctor__,
} = staticModuleRecord;
} = moduleSource;

@@ -147,0 +143,0 @@ const compartmentFields = weakmapGet(privateFields, compartment);

/* eslint-disable no-underscore-dangle */
// For brevity, in this file, as in module-load.js, the term "moduleRecord"
// without qualification means "module compartment record".
// This is a super-set of the "static module record", that is reusable between
// compartments with different hooks.
// The "module compartment record" captures the compartment and overlays the
// module's "imports" with the more specific "resolvedImports" as inferred from
// the particular compartment's "resolveHook".
import { assert } from './error/assert.js';
import {
makeModuleInstance,
makeThirdPartyModuleInstance,
makeVirtualModuleInstance,
} from './module-instance.js';

@@ -64,17 +56,14 @@ import {

function isPrecompiled(staticModuleRecord) {
return typeof staticModuleRecord.__syncModuleProgram__ === 'string';
function mayBePrecompiledModuleSource(moduleSource) {
return typeof moduleSource.__syncModuleProgram__ === 'string';
}
function validatePrecompiledStaticModuleRecord(
staticModuleRecord,
moduleSpecifier,
) {
const { __fixedExportMap__, __liveExportMap__ } = staticModuleRecord;
function validatePrecompiledModuleSource(moduleSource, moduleSpecifier) {
const { __fixedExportMap__, __liveExportMap__ } = moduleSource;
isObject(__fixedExportMap__) ||
Fail`Property '__fixedExportMap__' of a precompiled module record must be an object, got ${q(
Fail`Property '__fixedExportMap__' of a precompiled module source must be an object, got ${q(
__fixedExportMap__,
)}, for module ${q(moduleSpecifier)}`;
isObject(__liveExportMap__) ||
Fail`Property '__liveExportMap__' of a precompiled module record must be an object, got ${q(
Fail`Property '__liveExportMap__' of a precompiled module source must be an object, got ${q(
__liveExportMap__,

@@ -84,13 +73,10 @@ )}, for module ${q(moduleSpecifier)}`;

function isThirdParty(staticModuleRecord) {
return typeof staticModuleRecord.execute === 'function';
function mayBeVirtualModuleSource(moduleSource) {
return typeof moduleSource.execute === 'function';
}
function validateThirdPartyStaticModuleRecord(
staticModuleRecord,
moduleSpecifier,
) {
const { exports } = staticModuleRecord;
function validateVirtualModuleSource(moduleSource, moduleSpecifier) {
const { exports } = moduleSource;
isArray(exports) ||
Fail`Property 'exports' of a third-party static module record must be an array, got ${q(
Fail`Property 'exports' of a third-party module source must be an array, got ${q(
exports,

@@ -100,18 +86,18 @@ )}, for module ${q(moduleSpecifier)}`;

function validateStaticModuleRecord(staticModuleRecord, moduleSpecifier) {
isObject(staticModuleRecord) ||
Fail`Static module records must be of type object, got ${q(
staticModuleRecord,
function validateModuleSource(moduleSource, moduleSpecifier) {
isObject(moduleSource) ||
Fail`Module sources must be of type object, got ${q(
moduleSource,
)}, for module ${q(moduleSpecifier)}`;
const { imports, exports, reexports = [] } = staticModuleRecord;
const { imports, exports, reexports = [] } = moduleSource;
isArray(imports) ||
Fail`Property 'imports' of a static module record must be an array, got ${q(
Fail`Property 'imports' of a module source must be an array, got ${q(
imports,
)}, for module ${q(moduleSpecifier)}`;
isArray(exports) ||
Fail`Property 'exports' of a precompiled module record must be an array, got ${q(
Fail`Property 'exports' of a precompiled module source must be an array, got ${q(
exports,
)}, for module ${q(moduleSpecifier)}`;
isArray(reexports) ||
Fail`Property 'reexports' of a precompiled module record must be an array if present, got ${q(
Fail`Property 'reexports' of a precompiled module source must be an array if present, got ${q(
reexports,

@@ -126,3 +112,3 @@ )}, for module ${q(moduleSpecifier)}`;

) => {
const { compartment, moduleSpecifier, resolvedImports, staticModuleRecord } =
const { compartment, moduleSpecifier, resolvedImports, moduleSource } =
moduleRecord;

@@ -136,8 +122,8 @@ const { instances } = weakmapGet(compartmentPrivateFields, compartment);

validateStaticModuleRecord(staticModuleRecord, moduleSpecifier);
validateModuleSource(moduleSource, moduleSpecifier);
const importedInstances = new Map();
let moduleInstance;
if (isPrecompiled(staticModuleRecord)) {
validatePrecompiledStaticModuleRecord(staticModuleRecord, moduleSpecifier);
if (mayBePrecompiledModuleSource(moduleSource)) {
validatePrecompiledModuleSource(moduleSource, moduleSpecifier);
moduleInstance = makeModuleInstance(

@@ -149,7 +135,7 @@ compartmentPrivateFields,

);
} else if (isThirdParty(staticModuleRecord)) {
validateThirdPartyStaticModuleRecord(staticModuleRecord, moduleSpecifier);
moduleInstance = makeThirdPartyModuleInstance(
} else if (mayBeVirtualModuleSource(moduleSource)) {
validateVirtualModuleSource(moduleSource, moduleSpecifier);
moduleInstance = makeVirtualModuleInstance(
compartmentPrivateFields,
staticModuleRecord,
moduleSource,
compartment,

@@ -162,5 +148,3 @@ moduleAliases,

throw TypeError(
`importHook must return a static module record, got ${q(
staticModuleRecord,
)}`,
`importHook must provide a module source, got ${q(moduleSource)}`,
);

@@ -167,0 +151,0 @@ }

@@ -1,15 +0,6 @@

// For brevity, in this file, as in module-link.js, the term "moduleRecord"
// without qualification means "module compartment record".
// This is a super-set of the "static module record", that is reusable between
// compartments with different hooks.
// The "module compartment record" captures the compartment and overlays the
// module's "imports" with the more specific "resolvedImports" as inferred from
// the particular compartment's "resolveHook".
import { getEnvironmentOption as getenv } from '@endo/env-options';
import {
ReferenceError,
TypeError,
Map,
Set,
TypeError,
arrayJoin,

@@ -20,19 +11,21 @@ arrayMap,

freeze,
generatorNext,
generatorThrow,
getOwnPropertyNames,
isObject,
mapGet,
mapHas,
mapSet,
promiseThen,
setAdd,
promiseThen,
values,
weakmapGet,
generatorNext,
generatorThrow,
weakmapHas,
} from './commons.js';
import { assert } from './error/assert.js';
import { makeError, annotateError, q, X } from './error/assert.js';
const { Fail, details: d, quote: q } = assert;
const noop = () => {};
async function asyncTrampoline(generatorFunc, args, errorWrapper) {
const asyncTrampoline = async (generatorFunc, args, errorWrapper) => {
await null;
const iterator = generatorFunc(...args);

@@ -50,5 +43,5 @@ let result = generatorNext(iterator);

return result.value;
}
};
function syncTrampoline(generatorFunc, args) {
const syncTrampoline = (generatorFunc, args) => {
const iterator = generatorFunc(...args);

@@ -64,3 +57,4 @@ let result = generatorNext(iterator);

return result.value;
}
};
// `makeAlias` constructs compartment specifier tuples for the `aliases`

@@ -73,6 +67,3 @@ // private field of compartments.

export const makeAlias = (compartment, specifier) =>
freeze({
compartment,
specifier,
});
freeze({ compartment, specifier });

@@ -90,3 +81,3 @@ // `resolveAll` pre-computes resolutions of all imports within the compartment

const loadRecord = (
const loadModuleSource = (
compartmentPrivateFields,

@@ -96,3 +87,3 @@ moduleAliases,

moduleSpecifier,
staticModuleRecord,
moduleSource,
enqueueJob,

@@ -103,10 +94,7 @@ selectImplementation,

) => {
const { resolveHook, moduleRecords } = weakmapGet(
compartmentPrivateFields,
compartment,
);
const { resolveHook } = weakmapGet(compartmentPrivateFields, compartment);
// resolve all imports relative to this referrer module.
const resolvedImports = resolveAll(
staticModuleRecord.imports,
moduleSource.imports,
resolveHook,

@@ -117,3 +105,3 @@ moduleSpecifier,

compartment,
staticModuleRecord,
moduleSource,
moduleSpecifier,

@@ -139,4 +127,2 @@ resolvedImports,

// Memoize.
mapSet(moduleRecords, moduleSpecifier, moduleRecord);
return moduleRecord;

@@ -154,77 +140,246 @@ };

) {
const { importHook, importNowHook, moduleMap, moduleMapHook, moduleRecords } =
weakmapGet(compartmentPrivateFields, compartment);
const {
importHook,
importNowHook,
moduleMap,
moduleMapHook,
moduleRecords,
parentCompartment,
} = weakmapGet(compartmentPrivateFields, compartment);
if (mapHas(moduleRecords, moduleSpecifier)) {
return mapGet(moduleRecords, moduleSpecifier);
}
// Follow moduleMap, or moduleMapHook if present.
let aliasNamespace = moduleMap[moduleSpecifier];
if (aliasNamespace === undefined && moduleMapHook !== undefined) {
aliasNamespace = moduleMapHook(moduleSpecifier);
let moduleDescriptor = moduleMap[moduleSpecifier];
if (moduleDescriptor === undefined && moduleMapHook !== undefined) {
moduleDescriptor = moduleMapHook(moduleSpecifier);
}
if (typeof aliasNamespace === 'string') {
// eslint-disable-next-line @endo/no-polymorphic-call
assert.fail(
d`Cannot map module ${q(moduleSpecifier)} to ${q(
aliasNamespace,
)} in parent compartment, not yet implemented`,
TypeError,
);
} else if (aliasNamespace !== undefined) {
const alias = weakmapGet(moduleAliases, aliasNamespace);
if (alias === undefined) {
// eslint-disable-next-line @endo/no-polymorphic-call
assert.fail(
d`Cannot map module ${q(
if (moduleDescriptor === undefined) {
const moduleHook = selectImplementation(importHook, importNowHook);
if (moduleHook === undefined) {
const moduleHookName = selectImplementation(
'importHook',
'importNowHook',
);
throw makeError(
X`${moduleHookName} needed to load module ${q(
moduleSpecifier,
)} because the value is not a module exports namespace, or is from another realm`,
ReferenceError,
)} in compartment ${q(compartment.name)}`,
);
}
// Behold: recursion.
// eslint-disable-next-line no-use-before-define
const aliasRecord = yield memoizedLoadWithErrorAnnotation(
compartmentPrivateFields,
moduleAliases,
alias.compartment,
alias.specifier,
enqueueJob,
selectImplementation,
moduleLoads,
);
mapSet(moduleRecords, moduleSpecifier, aliasRecord);
return aliasRecord;
moduleDescriptor = moduleHook(moduleSpecifier);
// Uninitialized module namespaces throw if we attempt to coerce them into
// promises.
if (!weakmapHas(moduleAliases, moduleDescriptor)) {
moduleDescriptor = yield moduleDescriptor;
}
}
if (mapHas(moduleRecords, moduleSpecifier)) {
return mapGet(moduleRecords, moduleSpecifier);
}
if (typeof moduleDescriptor === 'string') {
// eslint-disable-next-line @endo/no-polymorphic-call
throw makeError(
X`Cannot map module ${q(moduleSpecifier)} to ${q(
moduleDescriptor,
)} in parent compartment, use {source} module descriptor`,
TypeError,
);
} else if (isObject(moduleDescriptor)) {
// In this shim (and not in XS, and not in the standard we imagine), we
// allow a module namespace object to stand in for a module descriptor that
// describes its original {compartment, specifier} so that it can be used
// to create a link.
let aliasDescriptor = weakmapGet(moduleAliases, moduleDescriptor);
if (aliasDescriptor !== undefined) {
moduleDescriptor = aliasDescriptor;
}
const staticModuleRecord = yield selectImplementation(
importHook,
importNowHook,
)(moduleSpecifier);
if (moduleDescriptor.namespace !== undefined) {
// { namespace: string, compartment?: Compartment }
// Namespace module descriptors link to a module instance.
if (staticModuleRecord === null || typeof staticModuleRecord !== 'object') {
Fail`importHook must return a promise for an object, for module ${q(
moduleSpecifier,
)} in compartment ${q(compartment.name)}`;
}
if (typeof moduleDescriptor.namespace === 'string') {
// The default compartment is the *parent*, not this child compartment.
// This is a difference from the legacy {specifier, compartment} module
// descriptor.
const {
compartment: aliasCompartment = parentCompartment,
namespace: aliasSpecifier,
} = moduleDescriptor;
if (
!isObject(aliasCompartment) ||
!weakmapHas(compartmentPrivateFields, aliasCompartment)
) {
throw makeError(
X`Invalid compartment in module descriptor for specifier ${q(moduleSpecifier)} in compartment ${q(compartment.name)}`,
);
}
// Behold: recursion.
// eslint-disable-next-line no-use-before-define
const aliasRecord = yield memoizedLoadWithErrorAnnotation(
compartmentPrivateFields,
moduleAliases,
aliasCompartment,
aliasSpecifier,
enqueueJob,
selectImplementation,
moduleLoads,
);
mapSet(moduleRecords, moduleSpecifier, aliasRecord);
return aliasRecord;
}
// check if record is a RedirectStaticModuleInterface
if (staticModuleRecord.specifier !== undefined) {
// check if this redirect with an explicit record
if (staticModuleRecord.record !== undefined) {
// ensure expected record shape
if (staticModuleRecord.compartment !== undefined) {
throw TypeError(
'Cannot redirect to an explicit record with a specified compartment',
// All remaining objects must either be a module namespace, or be
// promoted into a module namespace with a virtual module source.
if (isObject(moduleDescriptor.namespace)) {
const { namespace } = moduleDescriptor;
// Brand-check SES shim module exports namespaces:
aliasDescriptor = weakmapGet(moduleAliases, namespace);
if (aliasDescriptor !== undefined) {
moduleDescriptor = aliasDescriptor;
// Fall through to processing the resulting {compartment, specifier}
// alias.
} else {
// Promote an arbitrary object to a module namespace with a virtual
// module source.
// { namespace: Object }
const exports = getOwnPropertyNames(namespace);
/** @type {import('../types.js').VirtualModuleSource} */
const moduleSource = {
imports: [],
exports,
execute(env) {
for (const name of exports) {
env[name] = namespace[name];
}
},
};
const importMeta = undefined;
const moduleRecord = loadModuleSource(
compartmentPrivateFields,
moduleAliases,
compartment,
moduleSpecifier,
moduleSource,
enqueueJob,
selectImplementation,
moduleLoads,
importMeta,
);
mapSet(moduleRecords, moduleSpecifier, moduleRecord);
return moduleRecord;
}
} else {
throw makeError(
X`Invalid compartment in module descriptor for specifier ${q(moduleSpecifier)} in compartment ${q(compartment.name)}`,
);
}
}
if (moduleDescriptor.source !== undefined) {
// Module source descriptors create an instance from a module source.
// The descriptor may contain the module source, or refer to a source
// loaded in a particular compartment.
if (typeof moduleDescriptor.source === 'string') {
// { source: string, importMeta?, specifier?: string, compartment? }
// A string source is the specifier for a different module source.
// That source may come from this compartment's parent (default), or
// from a specified compartment, and the specified compartment may be
// this compartment to make a duplicate.
const {
source: loaderSpecifier,
specifier: instanceSpecifier = moduleSpecifier,
compartment: loaderCompartment = parentCompartment,
importMeta = undefined,
} = moduleDescriptor;
// Induce the compartment, possibly a different compartment
// to load a module source.
// Behold: recursion.
// eslint-disable-next-line no-use-before-define
const loaderRecord = yield memoizedLoadWithErrorAnnotation(
compartmentPrivateFields,
moduleAliases,
loaderCompartment,
loaderSpecifier,
enqueueJob,
selectImplementation,
moduleLoads,
);
// Extract the source of the module from the loader compartment's
// record.
const { moduleSource } = loaderRecord;
// Instantiate that source in our own compartment, possibly with a
// different specifier for resolving its own imports.
const moduleRecord = loadModuleSource(
compartmentPrivateFields,
moduleAliases,
compartment,
instanceSpecifier,
moduleSource,
enqueueJob,
selectImplementation,
moduleLoads,
importMeta,
);
mapSet(moduleRecords, moduleSpecifier, moduleRecord);
return moduleRecord;
} else {
// { source: ModuleSource, importMeta?, specifier?: string }
// We assume all non-string module sources are any of the supported
// kinds of module source: PrecompiledModuleSource,
// VirtualModuleSource, or a native ModuleSource.
const {
source: moduleSource,
specifier: aliasSpecifier = moduleSpecifier,
importMeta,
} = moduleDescriptor;
const aliasRecord = loadModuleSource(
compartmentPrivateFields,
moduleAliases,
compartment,
aliasSpecifier,
moduleSource,
enqueueJob,
selectImplementation,
moduleLoads,
importMeta,
);
mapSet(moduleRecords, moduleSpecifier, aliasRecord);
return aliasRecord;
}
}
if (moduleDescriptor.archive !== undefined) {
// { archive: Archive, path: string }
// We do not support this XS-native module descriptor.
throw makeError(
X`Unsupported archive module descriptor for specifier ${q(moduleSpecifier)} in compartment ${q(compartment.name)}`,
);
}
// { record, specifier?, compartment?, importMeta? }
// A (legacy) module descriptor for when we find the module source (record)
// but at a different specifier than requested.
// Providing this {specifier, record} descriptor serves as an ergonomic
// short-hand for stashing the record, returning a {compartment, specifier}
// reference, bouncing the module hook, then producing the source (record)
// when module hook receives the response specifier.
if (moduleDescriptor.record !== undefined) {
const {
compartment: aliasCompartment = compartment,
specifier: aliasSpecifier = moduleSpecifier,
record: aliasModuleRecord,
record: moduleSource,
importMeta,
} = staticModuleRecord;
} = moduleDescriptor;
const aliasRecord = loadRecord(
const aliasRecord = loadModuleSource(
compartmentPrivateFields,

@@ -234,3 +389,3 @@ moduleAliases,

aliasSpecifier,
aliasModuleRecord,
moduleSource,
enqueueJob,

@@ -242,11 +397,20 @@ selectImplementation,

mapSet(moduleRecords, moduleSpecifier, aliasRecord);
mapSet(moduleRecords, aliasSpecifier, aliasRecord);
return aliasRecord;
}
// check if this redirect with an explicit compartment
if (staticModuleRecord.compartment !== undefined) {
// ensure expected record shape
if (staticModuleRecord.importMeta !== undefined) {
throw TypeError(
'Cannot redirect to an implicit record with a specified importMeta',
// { specifier: string, compartment: Compartment }
// A (legacy) module descriptor that describes a link to a module instance
// in a specified compartment.
if (
moduleDescriptor.compartment !== undefined &&
moduleDescriptor.specifier !== undefined
) {
if (
!isObject(moduleDescriptor.compartment) ||
!weakmapHas(compartmentPrivateFields, moduleDescriptor.compartment) ||
typeof moduleDescriptor.specifier !== 'string'
) {
throw makeError(
X`Invalid compartment in module descriptor for specifier ${q(moduleSpecifier)} in compartment ${q(compartment.name)}`,
);

@@ -259,4 +423,4 @@ }

moduleAliases,
staticModuleRecord.compartment,
staticModuleRecord.specifier,
moduleDescriptor.compartment,
moduleDescriptor.specifier,
enqueueJob,

@@ -270,15 +434,25 @@ selectImplementation,

throw TypeError('Unnexpected RedirectStaticModuleInterface record shape');
// A (legacy) behavior: If we do not recognize the module descriptor as a
// module descriptor, we assume that it is a module source (record):
const moduleSource = moduleDescriptor;
const moduleRecord = loadModuleSource(
compartmentPrivateFields,
moduleAliases,
compartment,
moduleSpecifier,
moduleSource,
enqueueJob,
selectImplementation,
moduleLoads,
);
// Memoize.
mapSet(moduleRecords, moduleSpecifier, moduleRecord);
return moduleRecord;
} else {
throw makeError(
X`module descriptor must be a string or object for specifier ${q(
moduleSpecifier,
)} in compartment ${q(compartment.name)}`,
);
}
return loadRecord(
compartmentPrivateFields,
moduleAliases,
compartment,
moduleSpecifier,
staticModuleRecord,
enqueueJob,
selectImplementation,
moduleLoads,
);
}

@@ -324,5 +498,5 @@

// eslint-disable-next-line @endo/no-polymorphic-call
assert.note(
annotateError(
error,
d`${error.message}, loading ${q(moduleSpecifier)} in compartment ${q(
X`${error.message}, loading ${q(moduleSpecifier)} in compartment ${q(
compartmentName,

@@ -340,3 +514,3 @@ )}`,

function asyncJobQueue() {
const asyncJobQueue = () => {
/** @type {Set<Promise<undefined>>} */

@@ -368,2 +542,3 @@ const pendingJobs = new Set();

const drainQueue = async () => {
await null;
for (const job of pendingJobs) {

@@ -376,3 +551,3 @@ // eslint-disable-next-line no-await-in-loop

return { enqueueJob, drainQueue };
}
};

@@ -384,3 +559,3 @@ /**

*/
function throwAggregateError({ errors, errorPrefix }) {
const throwAggregateError = ({ errors, errorPrefix }) => {
// Throw an aggregate error if there were any errors.

@@ -397,3 +572,3 @@ if (errors.length > 0) {

}
}
};

@@ -404,3 +579,3 @@ const preferSync = (_asyncImpl, syncImpl) => syncImpl;

/*
* `load` asynchronously gathers the `StaticModuleRecord`s for a module and its
* `load` asynchronously gathers the module records for a module and its
* transitive dependencies.

@@ -449,4 +624,4 @@ * The module records refer to each other by a reference to the dependency's

/*
* `loadNow` synchronously gathers the `StaticModuleRecord`s for a module and its
* transitive dependencies.
* `loadNow` synchronously gathers the module records for a specified module
* and its transitive dependencies.
* The module records refer to each other by a reference to the dependency's

@@ -453,0 +628,0 @@ * compartment and the specifier of the module within its own compartment.

@@ -1116,4 +1116,10 @@ /* eslint-disable no-restricted-globals */

Uint32Array: TypedArray('%Uint32ArrayPrototype%'),
Uint8Array: TypedArray('%Uint8ArrayPrototype%'),
Uint8ClampedArray: TypedArray('%Uint8ClampedArrayPrototype%'),
Uint8Array: {
...TypedArray('%Uint8ArrayPrototype%'),
// https://github.com/tc39/proposal-arraybuffer-base64
fromBase64: fn,
// https://github.com/tc39/proposal-arraybuffer-base64
fromHex: fn,
},

@@ -1131,4 +1137,14 @@ '%BigInt64ArrayPrototype%': TypedArrayPrototype('BigInt64Array'),

'%Uint32ArrayPrototype%': TypedArrayPrototype('Uint32Array'),
'%Uint8ArrayPrototype%': TypedArrayPrototype('Uint8Array'),
'%Uint8ClampedArrayPrototype%': TypedArrayPrototype('Uint8ClampedArray'),
'%Uint8ArrayPrototype%': {
...TypedArrayPrototype('Uint8Array'),
// https://github.com/tc39/proposal-arraybuffer-base64
setFromBase64: fn,
// https://github.com/tc39/proposal-arraybuffer-base64
setFromHex: fn,
// https://github.com/tc39/proposal-arraybuffer-base64
toBase64: fn,
// https://github.com/tc39/proposal-arraybuffer-base64
toHex: fn,
},

@@ -1135,0 +1151,0 @@ // *** Keyed Collections

@@ -28,3 +28,3 @@ /**

unhandledRejectionTrapping?: 'report' | 'none';
errorTaming?: 'safe' | 'unsafe';
errorTaming?: 'safe' | 'unsafe' | 'unsafe-debug';
dateTaming?: 'safe' | 'unsafe'; // deprecated

@@ -47,6 +47,9 @@ mathTaming?: 'safe' | 'unsafe'; // deprecated

export type ModuleExportsNamespace = Record<string, any>;
export type __LiveExportMap__ = Record<string, [string, boolean]>;
export type __FixedExportMap__ = Record<string, [string]>;
export type __ReexportMap__ = Record<string, Array<[string, string]>>;
export interface PrecompiledStaticModuleInterface {
export interface PrecompiledModuleSource {
imports: Array<string>;

@@ -58,5 +61,6 @@ exports: Array<string>;

__fixedExportMap__: __FixedExportMap__;
__reexportMap__: __ReexportMap__;
}
export interface ThirdPartyStaticModuleInterface {
export interface VirtualModuleSource {
imports: Array<string>;

@@ -74,19 +78,42 @@ exports: Array<string>;

export type FinalStaticModuleType =
| PrecompiledStaticModuleInterface
| ThirdPartyStaticModuleInterface;
export type ModuleSource = PrecompiledModuleSource | VirtualModuleSource;
export interface RedirectStaticModuleInterface {
specifier: string;
record?: FinalStaticModuleType;
export interface SourceModuleDescriptor {
source: string | ModuleSource;
specifier?: string;
importMeta?: any;
compartment?: Compartment; // defaults to parent
}
export interface NamespaceModuleDescriptor {
namespace: string | ModuleExportsNamespace;
compartment?: Compartment;
}
export type StaticModuleType =
| RedirectStaticModuleInterface
| FinalStaticModuleType;
// Deprecated in favor of SourceModuleDescriptor,
// but beware the change in default compartment.
export interface RecordModuleDescriptor {
specifier: string;
record?: ModuleSource;
importMeta?: any;
compartment?: Compartment; // defaults to self
}
export type ModuleExportsNamespace = Record<string, any>;
export type ModuleDescriptor =
| SourceModuleDescriptor
| NamespaceModuleDescriptor
// To be deprecated:
| RecordModuleDescriptor
| ModuleExportsNamespace
| VirtualModuleSource
| PrecompiledModuleSource
| string;
// Deprecated type aliases:
export type PrecompiledStaticModuleInterface = PrecompiledModuleSource;
export type ThirdPartyStaticModuleInterface = VirtualModuleSource;
export type RedirectStaticModuleInterface = RecordModuleDescriptor;
export type FinalStaticModuleType = ModuleSource;
export type StaticModuleType = RedirectStaticModuleInterface | ModuleSource;
export type Transform = (source: string) => string;

@@ -97,8 +124,12 @@ export type ResolveHook = (

) => string;
export type ModuleMap = Record<string, string | ModuleExportsNamespace>;
export type ModuleMap = Record<string, string | ModuleDescriptor>;
export type ModuleMapHook = (
moduleSpecifier: string,
) => string | ModuleExportsNamespace | void;
export type ImportHook = (moduleSpecifier: string) => Promise<StaticModuleType>;
export type ImportNowHook = (moduleSpecifier: string) => StaticModuleType;
) => ModuleDescriptor | undefined;
export type ImportHook = (moduleSpecifier: string) => Promise<ModuleDescriptor>;
export type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor;
export type ImportMetaHook = (
moduleSpecifier: string,
importMeta: Object,
) => void;

@@ -111,4 +142,8 @@ export interface CompartmentOptions {

importNowHook?: ImportNowHook;
importMetaHook?: ImportMetaHook;
resolveHook?: ResolveHook;
globals?: Map<string, any>;
modules?: Map<string, ModuleDescriptor>;
__shimTransforms__?: Array<Transform>;
__noNamespaceBox__?: boolean;
}

@@ -280,7 +315,7 @@

*/
equal(
equal<T>(
/** What we received */
actual: any,
actual: unknown,
/** What we wanted */
expected: any,
expected: T,
/** The details of what was asserted */

@@ -291,3 +326,3 @@ details?: Details,

options?: AssertMakeErrorOptions,
): void;
): asserts actual is T;

@@ -357,8 +392,6 @@ /**

* ```
* // TODO Update SES-shim to new convention, where `details` is
* // renamed to `X` rather than `d`.
* or following the normal convention to locally rename `details` to `d`
* and `quote` to `q` like `const { details: d, quote: q } = assert;`:
* or following the normal convention to locally rename `details` to `X`
* and `quote` to `q` like `const { details: X, quote: q } = assert;`:
* ```js
* assert(sky.isBlue(), d`${sky.color} should be "blue"`);
* assert(sky.isBlue(), X`${sky.color} should be "blue"`);
* ```

@@ -439,6 +472,4 @@ * However, note that in most cases it is preferable to instead use the `Fail`

*
* // TODO Update SES-shim to new convention, where `details` is
* // renamed to `X` rather than `d`.
* The normal convention is to locally rename `details` to `d` and `quote` to `q`
* like `const { details: d, quote: q } = assert;`, so the above example would then be
* The normal convention is to locally rename `details` to `X` and `quote` to `q`
* like `const { details: X, quote: q } = assert;`, so the above example would then be
* ```js

@@ -523,5 +554,8 @@ * sky.color === expectedColor || Fail`${sky.color} should be ${q(expectedColor)}`;

export class Compartment {
constructor(options?: CompartmentOptions & { __options__: true });
// Deprecated:
constructor(
globals?: Object,
moduleMap?: ModuleMap,
globals?: Record<string, any> | undefined,
modules?: Record<string, ModuleDescriptor>,
options?: CompartmentOptions,

@@ -528,0 +562,0 @@ );

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc