ses
Version:
Hardened JavaScript for Fearless Cooperation
193 lines (175 loc) • 5.76 kB
JavaScript
import { cauterizeProperty } from './cauterize-property.js';
import {
TypeError,
WeakSet,
arrayFilter,
create,
defineProperty,
entries,
freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
globalThis,
is,
isObject,
objectHasOwnProperty,
values,
weaksetHas,
} from './commons.js';
import {
constantProperties,
sharedGlobalPropertyNames,
universalPropertyNames,
permitted,
} from './permits.js';
/**
* @import {Reporter} from './reporting-types.js'
*/
const isFunction = obj => typeof obj === 'function';
// Like defineProperty, but throws if it would modify an existing property.
// We use this to ensure that two conflicting attempts to define the same
// property throws, causing SES initialization to fail. Otherwise, a
// conflict between, for example, two of SES's internal permits might
// get masked as one overwrites the other. Accordingly, the thrown error
// complains of a "Conflicting definition".
function initProperty(obj, name, desc) {
if (objectHasOwnProperty(obj, name)) {
const preDesc = getOwnPropertyDescriptor(obj, name);
if (
!preDesc ||
!is(preDesc.value, desc.value) ||
preDesc.get !== desc.get ||
preDesc.set !== desc.set ||
preDesc.writable !== desc.writable ||
preDesc.enumerable !== desc.enumerable ||
preDesc.configurable !== desc.configurable
) {
throw TypeError(`Conflicting definitions of ${name}`);
}
}
defineProperty(obj, name, desc);
}
// Like defineProperties, but throws if it would modify an existing property.
// This ensures that the intrinsics added to the intrinsics collector object
// graph do not overlap.
function initProperties(obj, descs) {
for (const [name, desc] of entries(descs)) {
initProperty(obj, name, desc);
}
}
// sampleGlobals creates an intrinsics object, suitable for
// interinsicsCollector.addIntrinsics, from the named properties of a global
// object.
function sampleGlobals(globalObject, newPropertyNames) {
const newIntrinsics = { __proto__: null };
for (const [globalName, intrinsicName] of entries(newPropertyNames)) {
if (objectHasOwnProperty(globalObject, globalName)) {
newIntrinsics[intrinsicName] = globalObject[globalName];
}
}
return newIntrinsics;
}
/**
* @param {Reporter} reporter
*/
export const makeIntrinsicsCollector = reporter => {
/** @type {Record<any, any>} */
const intrinsics = create(null);
let pseudoNatives;
const addIntrinsics = newIntrinsics => {
initProperties(intrinsics, getOwnPropertyDescriptors(newIntrinsics));
};
freeze(addIntrinsics);
// For each intrinsic, if it has a `.prototype` property, use the
// permits to find out the intrinsic name for that prototype and add it
// to the intrinsics.
const completePrototypes = () => {
for (const [name, intrinsic] of entries(intrinsics)) {
if (!isObject(intrinsic)) {
// eslint-disable-next-line no-continue
continue;
}
if (!objectHasOwnProperty(intrinsic, 'prototype')) {
// eslint-disable-next-line no-continue
continue;
}
const permit = permitted[name];
if (typeof permit !== 'object') {
throw TypeError(`Expected permit object at permits.${name}`);
}
const namePrototype = permit.prototype;
if (!namePrototype) {
cauterizeProperty(
intrinsic,
'prototype',
false,
`${name}.prototype`,
reporter,
);
// eslint-disable-next-line no-continue
continue;
}
if (
typeof namePrototype !== 'string' ||
!objectHasOwnProperty(permitted, namePrototype)
) {
throw TypeError(`Unrecognized ${name}.prototype permits entry`);
}
const intrinsicPrototype = intrinsic.prototype;
if (objectHasOwnProperty(intrinsics, namePrototype)) {
if (intrinsics[namePrototype] !== intrinsicPrototype) {
throw TypeError(`Conflicting bindings of ${namePrototype}`);
}
// eslint-disable-next-line no-continue
continue;
}
intrinsics[namePrototype] = intrinsicPrototype;
}
};
freeze(completePrototypes);
const finalIntrinsics = () => {
freeze(intrinsics);
pseudoNatives = new WeakSet(arrayFilter(values(intrinsics), isFunction));
return intrinsics;
};
freeze(finalIntrinsics);
const isPseudoNative = obj => {
if (!pseudoNatives) {
throw TypeError(
'isPseudoNative can only be called after finalIntrinsics',
);
}
return weaksetHas(pseudoNatives, obj);
};
freeze(isPseudoNative);
const intrinsicsCollector = {
addIntrinsics,
completePrototypes,
finalIntrinsics,
isPseudoNative,
};
freeze(intrinsicsCollector);
addIntrinsics(constantProperties);
addIntrinsics(sampleGlobals(globalThis, universalPropertyNames));
return intrinsicsCollector;
};
/**
* getGlobalIntrinsics()
* Doesn't tame, delete, or modify anything. Samples globalObject to create an
* intrinsics record containing only the permitted global variables, listed
* by the intrinsic names appropriate for new globals, i.e., the globals of
* newly constructed compartments.
*
* WARNING:
* If run before lockdown, the returned intrinsics record will carry the
* *original* unsafe (feral, untamed) bindings of these global variables.
*
* @param {object} globalObject
* @param {Reporter} reporter
*/
export const getGlobalIntrinsics = (globalObject, reporter) => {
// TODO pass a proper reporter to `makeIntrinsicsCollector`
const { addIntrinsics, finalIntrinsics } = makeIntrinsicsCollector(reporter);
addIntrinsics(sampleGlobals(globalObject, sharedGlobalPropertyNames));
return finalIntrinsics();
};