UNPKG

ses

Version:

Hardened JavaScript for Fearless Cooperation

581 lines (523 loc) 20 kB
/** * @module Types of the SES environment */ /* eslint-disable no-restricted-globals, vars-on-top, no-var */ // It's academically tempting to define a hardened type, but TypeScript doesn't // strike a good balance in distinguishing "readonly" in the sense that you // promise not to change vs "readonly" in the sense that you depend on a thing // not changing. // type Hardened<T> = // T extends number | bigint | string | null | undefined | Function ? T : // { readonly [P in keyof T]: Hardened<T[P]> }; // So Harden just passes the type through without modification. // This will occasionally conflict with the type of Object.freeze. // In those cases, we recommend casting the result of Object.freeze to the // original thawn type, as if the signature of freeze were identical to this // version of harden. export type Harden = <T>(value: T) => T; // not Hardened<T>; export interface RepairOptions { regExpTaming?: 'safe' | 'unsafe'; localeTaming?: 'safe' | 'unsafe'; consoleTaming?: 'safe' | 'unsafe'; errorTrapping?: 'platform' | 'exit' | 'abort' | 'report' | 'none'; reporting?: 'platform' | 'console' | 'none'; unhandledRejectionTrapping?: 'report' | 'none'; errorTaming?: 'safe' | 'unsafe' | 'unsafe-debug'; /** * @deprecated Deprecated and does nothing. In the future specifying it will be an error. */ dateTaming?: 'safe' | 'unsafe'; /** * @deprecated Deprecated and does nothing. In the future specifying it will be an error. */ mathTaming?: 'safe' | 'unsafe'; evalTaming?: 'safeEval' | 'unsafeEval' | 'noEval'; stackFiltering?: 'concise' | 'verbose'; overrideTaming?: 'moderate' | 'min' | 'severe'; overrideDebug?: Array<string>; domainTaming?: 'safe' | 'unsafe'; /** * safe (default): do nothing. * * unsafe-ignore: make %IteratorPrototype%[@@iterator] to a funky accessor which ignores all assignments. */ legacyRegeneratorRuntimeTaming?: 'safe' | 'unsafe-ignore'; __hardenTaming__?: 'safe' | 'unsafe'; } // Deprecated in favor of the more specific RepairOptions export type LockdownOptions = RepairOptions; export type RepairIntrinsics = (options?: LockdownOptions) => void; export type HardenIntrinsics = () => void; export type Lockdown = (options?: LockdownOptions) => void; 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 PrecompiledModuleSource { imports: Array<string>; exports: Array<string>; reexports: Array<string>; __syncModuleProgram__: string; __liveExportMap__: __LiveExportMap__; __fixedExportMap__: __FixedExportMap__; __reexportMap__: __ReexportMap__; } export interface VirtualModuleSource { imports: Array<string>; exports: Array<string>; /** * Note that this value does _not_ contain any numeric or symbol property keys, which can theoretically be members of `exports` in a CommonJS module. */ execute( exportsTarget: Record<string, any>, compartment: Compartment, resolvedImports: Record<string, string>, ): void; } export type ModuleSource = PrecompiledModuleSource | VirtualModuleSource; export interface SourceModuleDescriptor { source: string | ModuleSource; specifier?: string; importMeta?: any; compartment?: Compartment; // defaults to parent } export interface NamespaceModuleDescriptor { namespace: string | ModuleExportsNamespace; compartment?: Compartment; } // 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 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; export type ResolveHook = ( importSpecifier: string, referrerSpecifier: string, ) => string; export type ModuleMap = Record<string, string | ModuleDescriptor>; export type ModuleMapHook = ( moduleSpecifier: string, ) => ModuleDescriptor | undefined; export type ImportHook = (moduleSpecifier: string) => Promise<ModuleDescriptor>; export type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor; export type ImportMetaHook = ( moduleSpecifier: string, importMeta: Object, ) => void; export interface CompartmentOptions { name?: string; transforms?: Array<Transform>; moduleMapHook?: ModuleMapHook; importHook?: ImportHook; importNowHook?: ImportNowHook; importMetaHook?: ImportMetaHook; resolveHook?: ResolveHook; globals?: Map<string, any>; modules?: Map<string, ModuleDescriptor>; __shimTransforms__?: Array<Transform>; __noNamespaceBox__?: boolean; } export interface EvaluateOptions { transforms?: Array<Transform>; sloppyGlobalsMode?: boolean; __moduleShimLexicals__?: Record<string, any>; __evadeHtmlCommentTest__?: boolean; __rejectSomeDirectEvalExpressions__?: boolean; } /** * A call to the `details` template literal makes and returns a fresh details * token, which is a frozen empty object associated with the arguments of that * `details` template literal expression. */ export type DetailsToken = Record<any, never>; /** Either a plain string, or made by the `details` template literal tag. */ export type Details = string | DetailsToken; export interface AssertMakeErrorOptions { /** * Does not affect the error.name property. That remains determined by * the constructor. Rather, the `errorName` determines how this error is * identified in the causal console log's output. */ errorName?: string; /** * Discloses the error that caused this one, typically from a lower * layer of abstraction. This is represented by a public `cause` data property * on the error, not a hidden annotation. */ cause?: Error; /** * Normally only used when the ErrorConstuctor is `AggregateError`, to * represent the set of prior errors aggregated together in this error, * typically by `Promise.any`. But `makeError` allows it on any error. * This is represented by a public `errors` data property on the error, * not a hidden annotation. */ errors?: Error[]; /** * Defaults to true. If true, `makeError` will apply `sanitizeError` * to the error before returning it. See the comments on * {@link sanitizeError}. */ sanitize?: boolean; } // TODO inline overloading type AssertTypeofBigint = ( specimen: any, typeName: 'bigint', details?: Details, ) => asserts specimen is bigint; type AssertTypeofBoolean = ( specimen: any, typeName: 'boolean', details?: Details, ) => asserts specimen is boolean; type AssertTypeofFunction = ( specimen: any, typeName: 'function', details?: Details, ) => asserts specimen is Function; type AssertTypeofNumber = ( specimen: any, typeName: 'number', details?: Details, ) => asserts specimen is number; type AssertTypeofObject = ( specimen: any, typeName: 'object', details?: Details, ) => asserts specimen is Record<any, any> | null; type AssertTypeofString = ( specimen: any, typeName: 'string', details?: Details, ) => asserts specimen is string; type AssertTypeofSymbol = ( specimen: any, typeName: 'symbol', details?: Details, ) => asserts specimen is symbol; type AssertTypeofUndefined = ( specimen: any, typeName: 'undefined', details?: Details, ) => asserts specimen is undefined; export type AssertTypeof = AssertTypeofBigint & AssertTypeofBoolean & AssertTypeofFunction & AssertTypeofNumber & AssertTypeofObject & AssertTypeofString & AssertTypeofSymbol & AssertTypeofUndefined; interface StringablePayload { toString(): string; } /** * TypeScript does not treat `AggregateErrorConstructor` as a subtype of * `ErrorConstructor`, which makes sense because their constructors * have incompatible signatures. However, we want to parameterize some * operations by any error constructor, including possible `AggregateError`. * So we introduce `GenericErrorConstructor` as a common supertype. Any call * to it to make an instance must therefore first case split on whether the * constructor is an AggregateErrorConstructor or a normal ErrorConstructor. */ export type GenericErrorConstructor = | ErrorConstructor | AggregateErrorConstructor; /** * To make an `assert` which terminates some larger unit of computation * like a transaction, vat, or process, call `makeAssert` with a `Raise` * callback, where that callback actually performs that larger termination. * If possible, the callback should also report its `reason` parameter as * the alleged reason for the termination. */ export type Raise = (reason: Error) => void; /** * Makes and returns an `assert` function object that shares the bookkeeping * state defined by this module with other `assert` function objects made by * `makeAssert`. This state is per-module-instance and is exposed by the * `loggedErrorHandler` above. We refer to `assert` as a "function object" * because it can be called directly as a function, but also has methods that * can be called. * * If `optRaise` is provided, the returned `assert` function object will call * `optRaise(reason)` before throwing the error. This enables `optRaise` to * engage in even more violent termination behavior, like terminating the vat, * that prevents execution from reaching the following throw. However, if * `optRaise` returns normally, which would be unusual, the throw following * `optRaise(reason)` would still happen. */ // Behold: recursion. // eslint-disable-next-line no-use-before-define export type MakeAssert = (raise?: Raise, unredacted?: boolean) => Assert; export type BaseAssert = ( /** The truthy/falsy value we're testing */ flag: any, /** The details of what was asserted */ details?: Details, /** An optional alternate error constructor to use */ errConstructor?: GenericErrorConstructor, options?: AssertMakeErrorOptions, ) => asserts flag; export interface AssertionFunctions extends BaseAssert { typeof: AssertTypeof; /** * The `assert.equal` method * * Assert that two values must be `Object.is`. */ equal<T>( /** What we received */ actual: unknown, /** What we wanted */ expected: T, /** The details of what was asserted */ details?: Details, /** An optional alternate error constructor to use */ errConstructor?: GenericErrorConstructor, options?: AssertMakeErrorOptions, ): asserts actual is T; /** * The `assert.string` method. * * `assert.string(v)` is equivalent to `assert.typeof(v, 'string')`. We * special case this one because it is the most frequently used. * * Assert an expected typeof result. */ string( specimen: any, /** The details of what was asserted */ details?: Details, ): asserts specimen is string; /** * The `assert.fail` method. * * Fail an assertion, recording full details to the console and * raising an exception with a message in which `details` substitution values * have been redacted. * * The optional `optDetails` can be a string for backwards compatibility * with the nodejs assertion library. */ fail( /** The details of what was asserted */ details?: Details, /** An optional alternate error constructor to use */ errConstructor?: GenericErrorConstructor, options?: AssertMakeErrorOptions, ): never; } export interface AssertionUtilities { /** * Aka the `makeError` function as imported from `@endo/errors` * * Recording unredacted details for the console. */ error( /** The details of what was asserted */ details?: Details, /** An optional alternate error constructor to use */ errConstructor?: GenericErrorConstructor, options?: AssertMakeErrorOptions, ): Error; /** * Aka the `annotateError` function as imported from `@endo/errors` * * Annotate an error with details, potentially to be used by an * augmented console such as the causal console of `console.js`, to * provide extra information associated with logged errors. */ note(error: Error, details: Details): void; /** * Use the `details` function as a template literal tag to create * informative error messages. The assertion functions take such messages * as optional arguments: * ```js * assert(sky.isBlue(), details`${sky.color} should be "blue"`); * ``` * 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(), X`${sky.color} should be "blue"`); * ``` * However, note that in most cases it is preferable to instead use the `Fail` * template literal tag (which has the same input signature as `details` * but automatically creates and throws an error): * ```js * sky.isBlue() || Fail`${sky.color} should be "blue"`; * ``` * * The details template tag returns a `DetailsToken` object that can print * itself with the formatted message in two ways. * It will report full details to the console, but * mask embedded substitution values with their typeof information in the thrown error * to prevent revealing secrets up the exceptional path. In the example * above, the thrown error may reveal only that `sky.color` is a string, * whereas the same diagnostic printed to the console reveals that the * sky was green. This masking can be disabled for an individual substitution value * using `quote`. * * The `raw` property of an input template array is ignored, so a simple * array of strings may be provided directly. */ details( template: TemplateStringsArray | string[], ...args: any ): DetailsToken; /** * Use the `Fail` function as a template literal tag to efficiently * create and throw a `details`-style error only when a condition is not satisfied. * ```js * condition || Fail`...complaint...`; * ``` * This avoids the overhead of creating usually-unnecessary errors like * ```js * assert(condition, details`...complaint...`); * ``` * while improving readability over alternatives like * ```js * condition || assert.fail(details`...complaint...`); * ``` * * However, due to current weakness in TypeScript, static reasoning * is less powerful with the `||` patterns than with an `assert` call. * Until/unless https://github.com/microsoft/TypeScript/issues/51426 is fixed, * for `||`-style assertions where this loss of static reasoning is a problem, * instead express the assertion as * ```js * if (!condition) { * Fail`...complaint...`; * } * ``` * or, if needed, * ```js * if (!condition) { * // `throw` is noop since `Fail` throws, but it improves static analysis * throw Fail`...complaint...`; * } * ``` */ Fail(template: TemplateStringsArray | string[], ...args: any): never; /** * To "declassify" and quote a substitution value used in a * ``` details`...` ``` template literal, enclose that substitution expression * in a call to `quote`. This makes the value appear quoted * (as if with `JSON.stringify`) in the message of the thrown error. The * payload itself is still passed unquoted to the console as it would be * without `quote`. * * For example, the following will reveal the expected sky color, but not the * actual incorrect sky color, in the thrown error's message: * ```js * sky.color === expectedColor || Fail`${sky.color} should be ${quote(expectedColor)}`; * ``` * * 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 * sky.color === expectedColor || Fail`${sky.color} should be ${q(expectedColor)}`; * ``` */ quote( /** What to declassify */ payload: any, spaces?: string | number, ): /** The declassified and quoted payload */ StringablePayload; /** * Embed a string directly into error details without wrapping punctuation. * To avoid injection attacks that exploit quoting confusion, this must NEVER * be used with data that is possibly attacker-controlled. * As a further safeguard, we fall back to quoting any input that is not a * string of sufficiently word-like parts separated by isolated spaces (rather * than throwing an exception, which could hide the original problem for which * explanatory details are being constructed---i.e., ``` assert.details`...` ``` * should never be the source of a new exception, nor should an attempt to * render its output, although we _could_ instead decide to handle the latter * by inline replacement similar to that of `bestEffortStringify` for producing * rendered messages like `(an object) was tagged "[Unsafe bare string]"`). */ bare( /** What to declassify */ payload: any, spaces?: string | number, ): /** The declassified payload without quotes (beware confusion hazard) */ StringablePayload; } export interface DeprecatedAssertionUtilities { makeAssert: MakeAssert; } /** * assert that expr is truthy, with an optional details to describe * the assertion. It is a tagged template literal like * ```js * assert(expr, details`....`);` * ``` * * The literal portions of the template are assumed non-sensitive, as * are the `typeof` types of the substitution values. These are * assembled into the thrown error message. The actual contents of the * substitution values are assumed sensitive, to be revealed to * the console only. We assume only the virtual platform's owner can read * what is written to the console, where the owner is in a privileged * position over computation running on that platform. * * The optional `optDetails` can be a string for backwards compatibility * with the nodejs assertion library. */ export type Assert = AssertionFunctions & AssertionUtilities & DeprecatedAssertionUtilities; interface CompartmentEvaluateOptions { sloppyGlobalsMode?: boolean; __moduleShimLexicals__?: Object; __evadeHtmlCommentTest__?: boolean; __evadeImportExpressionTest__?: boolean; __rejectSomeDirectEvalExpressions__?: boolean; } declare global { var harden: Harden; var repairIntrinsics: RepairIntrinsics; var hardenIntrinsics: HardenIntrinsics; var lockdown: Lockdown; var assert: Assert; /** * Each Compartment constructor is a global. A host that wants to execute * code in a context bound to a new global creates a new compartment. */ export class Compartment { constructor(options?: CompartmentOptions & { __options__: true }); // Deprecated: constructor( globals?: Record<PropertyKey, any> | undefined, modules?: Record<string, ModuleDescriptor>, options?: CompartmentOptions, ); get globalThis(): Record<PropertyKey, any>; get name(): string; evaluate(code: string, options?: CompartmentEvaluateOptions): any; import(specifier: string): Promise<{ namespace: ModuleExportsNamespace }>; load(specifier: string): Promise<void>; importNow(specifier: string): ModuleExportsNamespace; module(specifier: string): ModuleExportsNamespace; } }