Skip to content

Commit

Permalink
docs(ses): Finish embracing permits terminology (#2619)
Browse files Browse the repository at this point in the history
## Description

Internally, SES uses the term “whitelist” and “permits” interchangeably.
This change attempts to regularize this terminology, favoring “permit”,
“permits”, “permitted”, and “unpermitted” throughout.
The only user-visible effects appear in error messages. 

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

Addressed.

### Testing Considerations

None.

### Compatibility Considerations

There is some risk that this will frustrate tests that over-constrain on
error message patterns.

### Upgrade Considerations

None.
  • Loading branch information
kriskowal authored Oct 28, 2024
2 parents b7c9f16 + c1f40be commit 08d3945
Show file tree
Hide file tree
Showing 15 changed files with 51 additions and 52 deletions.
2 changes: 1 addition & 1 deletion packages/ses/docs/draft-standalone-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ the default configuration of SES are
* Omit all support for sloppy mode
* Aside from `BigInt`, omit everything else outside the EcmaScript 2018 spec.
* In particular, omit the `import()` and `import.meta` expressions.
* Omit annex B (except those our whitelist allows)
* Omit annex B (except those `ses` explicitly permits)
* In particular, omit the `RegExp` static properties that provide a global
communications channel.
* On the `Math` namespace object shared by constructed compartments:
Expand Down
2 changes: 1 addition & 1 deletion packages/ses/docs/lockdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ we now step into every access to an enabled property. Every read steps into
the enabling getter. This adds yet more noise to the debugging experience.

The file [src/enablements.js](../src/enablements.js) exports three different
whitelists definining which data properties to convert to enable override by
lists definining which data properties to convert to enable override by
assignment, `minEnablements`, `moderateEnablements`, and `severeEnablements`.

```js
Expand Down
2 changes: 1 addition & 1 deletion packages/ses/src/enable-property-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
/** @import {Reporter} from './reporting-types.js' */

/**
* For a special set of properties defined in the `enablement` whitelist,
* For a special set of properties defined in the `enablement` list,
* `enablePropertyOverrides` ensures that the effect of freezing does not
* suppress the ability to override these properties on derived objects by
* simple assignment.
Expand Down
4 changes: 2 additions & 2 deletions packages/ses/src/error/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Before repair or `lockdown`, we assume there is some prior "system" console boun

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).

SES considers both `assert` and `console` to be powerful objects, appearing initially in the start compartment, and not whitelisted for implicit propagation to created compartments. Rather, we recommend an endowment pattern where the global `assert` is passed forward as is, but only filtered forms of the `console` are. As compartments create each other in a tree, they create a corresponding filtering tree of consoles. Information sent to any compartment's console is then sent up the filtering tree. Only information that survives all the filters in its path arrive at the root console, producing log output. The others have no effect. Given the expected pattern of a compartment per package, the per-compartment console filter is effectly a topic filter, treating the package identity as a topic. We plan to also support coordinated stack-frame filters, as explained at [Need source-prefix-based stackframe filter #488](https://github.com/Agoric/SES-shim/issues/488).
SES considers both `assert` and `console` to be powerful objects, appearing initially in the start compartment, and not permitted for implicit propagation to created compartments. Rather, we recommend an endowment pattern where the global `assert` is passed forward as-is, but only filtered forms of the `console` are. As compartments create each other in a tree, they create a corresponding filtering tree of consoles. Information sent to any compartment's console is then sent up the filtering tree. Only information that survives all the filters in its path arrive at the root console, producing log output. The others have no effect. Given the expected pattern of a compartment per package, the per-compartment console filter is effectly a topic filter, treating the package identity as a topic. We plan to also support coordinated stack-frame filters, as explained at [Need source-prefix-based stackframe filter #488](https://github.com/Agoric/SES-shim/issues/488).

For security and determinism, we normally reason from the *in-band frame of reference* where the console logging output does not exist, is not an effect, and `console` operations are write-only. Within this frame of reference, the `assert` and `console` powers are not very powerful. They are almost as safe as the whitelisted powerless shared primordials, which is why we're willing to recommend this endowment pattern be habitual.
For security and determinism, we normally reason from the *in-band frame of reference* where the console logging output does not exist, is not an effect, and `console` operations are write-only. Within this frame of reference, the `assert` and `console` powers are not very powerful. They are almost as safe as the permitted, powerless, shared primordials, which is why we're willing to recommend this endowment pattern be habitual.

## Hiding and Revealing Distributed Diagnostic Information

Expand Down
16 changes: 8 additions & 8 deletions packages/ses/src/error/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
// For our internal debugging purposes, uncomment
// const internalDebugConsole = console;

// The whitelists of console methods, from:
// The permitted console methods, from:
// Whatwg "living standard" https://console.spec.whatwg.org/
// Node https://nodejs.org/dist/latest-v14.x/docs/api/console.html
// MDN https://developer.mozilla.org/en-US/docs/Web/API/Console_API
Expand Down Expand Up @@ -110,16 +110,16 @@ export const consoleOtherMethods = freeze([
]);

/** @type {readonly [ConsoleProps, LogSeverity | undefined][]} */
const consoleWhitelist = freeze([
const consoleMethodPermits = freeze([
...consoleLevelMethods,
...consoleOtherMethods,
]);

/**
* consoleOmittedProperties is currently unused. I record and maintain it here
* with the intention that it be treated like the `false` entries in the main
* SES whitelist: that seeing these on the original console is expected, but
* seeing anything else that's outside the whitelist is surprising and should
* SES permits: that seeing these on the original console is expected, but
* seeing anything else that's outside the permits is surprising and should
* provide a diagnostic.
*
* const consoleOmittedProperties = freeze([
Expand Down Expand Up @@ -158,7 +158,7 @@ export const makeLoggingConsoleKit = (
let logArray = [];

const loggingConsole = fromEntries(
arrayMap(consoleWhitelist, ([name, _]) => {
arrayMap(consoleMethodPermits, ([name, _]) => {
// Use an arrow function so that it doesn't come with its own name in
// its printed form. Instead, we're hoping that tooling uses only
// the `.name` property set below.
Expand Down Expand Up @@ -508,11 +508,11 @@ freeze(defineCausalConsoleFromLogger);
/** @type {FilterConsole} */
export const filterConsole = (baseConsole, filter, _topic = undefined) => {
// TODO do something with optional topic string
const whitelist = arrayFilter(
consoleWhitelist,
const methodPermits = arrayFilter(
consoleMethodPermits,
([name, _]) => name in baseConsole,
);
const methods = arrayMap(whitelist, ([name, severity]) => {
const methods = arrayMap(methodPermits, ([name, severity]) => {
/**
* @param {...any} args
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/ses/src/error/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
/**
* @callback MakeLoggingConsoleKit
*
* A logging console just accumulates the contents of all whitelisted calls,
* A logging console just accumulates the contents of all permitted calls,
* making them available to callers of `takeLog()`. Calling `takeLog()`
* consumes these, so later calls to `takeLog()` will only provide a log of
* calls that have happened since then.
Expand Down
6 changes: 3 additions & 3 deletions packages/ses/src/error/tame-v8-error-constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
TypeError,
} from '../commons.js';

// Whitelist names from https://v8.dev/docs/stack-trace-api
// Whitelisting only the names used by error-stack-shim/src/v8StackFrames
// Permit names from https://v8.dev/docs/stack-trace-api
// Permiting only the names used by error-stack-shim/src/v8StackFrames
// callSiteToFrame to shim the error stack proposal.
const safeV8CallSiteMethodNames = [
// suppress 'getThis' definitely
Expand All @@ -45,7 +45,7 @@ const safeV8CallSiteMethodNames = [
'getPosition',
'getScriptNameOrSourceURL',

'toString', // TODO replace to use only whitelisted info
'toString', // TODO replace to use only permitted info
];

// TODO this is a ridiculously expensive way to attenuate callsites.
Expand Down
2 changes: 1 addition & 1 deletion packages/ses/src/global-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const setGlobalObjectMutableProperties = (
),
);

// TODO These should still be tamed according to the whitelist before
// TODO These should still be tamed according to the permits before
// being made available.
for (const [name, value] of entries(perCompartmentGlobals)) {
defineProperty(globalObject, name, {
Expand Down
12 changes: 6 additions & 6 deletions packages/ses/src/intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ 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 whitelists might
// 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) {
Expand Down Expand Up @@ -82,7 +82,7 @@ export const makeIntrinsicsCollector = () => {
freeze(addIntrinsics);

// For each intrinsic, if it has a `.prototype` property, use the
// whitelist to find out the intrinsic name for that prototype and add it
// 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)) {
Expand All @@ -96,17 +96,17 @@ export const makeIntrinsicsCollector = () => {
}
const permit = permitted[name];
if (typeof permit !== 'object') {
throw TypeError(`Expected permit object at whitelist.${name}`);
throw TypeError(`Expected permit object at permits.${name}`);
}
const namePrototype = permit.prototype;
if (!namePrototype) {
throw TypeError(`${name}.prototype property not whitelisted`);
throw TypeError(`${name}.prototype property not permitted`);
}
if (
typeof namePrototype !== 'string' ||
!objectHasOwnProperty(permitted, namePrototype)
) {
throw TypeError(`Unrecognized ${name}.prototype whitelist entry`);
throw TypeError(`Unrecognized ${name}.prototype permits entry`);
}
const intrinsicPrototype = intrinsic.prototype;
if (objectHasOwnProperty(intrinsics, namePrototype)) {
Expand Down Expand Up @@ -155,7 +155,7 @@ export const makeIntrinsicsCollector = () => {
/**
* getGlobalIntrinsics()
* Doesn't tame, delete, or modify anything. Samples globalObject to create an
* intrinsics record containing only the whitelisted global variables, listed
* 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.
*
Expand Down
8 changes: 4 additions & 4 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from './commons.js';
import { makeHardener } from './make-hardener.js';
import { makeIntrinsicsCollector } from './intrinsics.js';
import whitelistIntrinsics from './permits-intrinsics.js';
import removeUnpermittedIntrinsics from './permits-intrinsics.js';
import tameFunctionConstructors from './tame-function-constructors.js';
import tameDateConstructor from './tame-date-constructor.js';
import tameMathObject from './tame-math-object.js';
Expand Down Expand Up @@ -365,17 +365,17 @@ export const repairIntrinsics = (options = {}) => {
tameFauxDataProperties(intrinsics);

/**
* 2. WHITELIST to standardize the environment.
* 2. Enforce PERMITS on shared intrinsics
*/

// Remove non-standard properties.
// All remaining function encountered during whitelisting are
// All remaining functions encountered during whitelisting are
// branded as honorary native functions.
reportInGroup(
'SES Removing unpermitted intrinsics',
reporter,
groupReporter =>
whitelistIntrinsics(
removeUnpermittedIntrinsics(
intrinsics,
markVirtualizedNativeFunction,
groupReporter,
Expand Down
19 changes: 9 additions & 10 deletions packages/ses/src/permits-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// Typically, this module will not be used directly, but via the
// [lockdown-shim] which handles all necessary repairs and taming in SES.
//
// In the whitelist, the `prototype`, `__proto__`, and `constructor` must be
// In the permits, the `prototype`, `__proto__`, and `constructor` must be
// specified and point to top level entries in the map. For example,
// `Object.__proto__` leads to `FunctionPrototype` which is a top level entry
// in the map.
Expand All @@ -34,14 +34,14 @@
// `Error.stackTraceLimit` leads to 'number'),
// * the name of an intrinsic,
// * an internal constant(for example, `eval` leads to `fn` which
// is an alias for `FunctionInstance`, a record that whitelist all
// is an alias for `FunctionInstance`, a record that permits all
// properties allowed on such instance).
// * false, a property to be removed that we know about.
//
// All unlisted properties are also removed. But for the ones that are removed
// because they are unlisted, as opposed to `false`, we also print their
// name to the console as a useful diagnostic, possibly provoking an expansion
// of the whitelist.
// of the permits.

import { permitted, FunctionInstance, isAccessorPermit } from './permits.js';
import {
Expand All @@ -67,15 +67,14 @@ import {
*/

/**
* whitelistIntrinsics()
* Removes all non-allowed properties found by recursively and
* reflectively walking own property chains.
*
* @param {object} intrinsics
* @param {(object) => void} markVirtualizedNativeFunction
* @param {(virtualizedNativeFunction: object) => void} markVirtualizedNativeFunction
* @param {Reporter} reporter
*/
export default function whitelistIntrinsics(
export default function removeUnpermittedIntrinsics(
intrinsics,
markVirtualizedNativeFunction,
{ warn, error },
Expand Down Expand Up @@ -143,7 +142,7 @@ export default function whitelistIntrinsics(

// Assert: protoName, if provided, is a string.
if (protoName !== undefined && typeof protoName !== 'string') {
throw TypeError(`Malformed whitelist permit ${path}.__proto__`);
throw TypeError(`Malformed permit ${path}.__proto__`);
}

// If permit not specified, default to Object.prototype.
Expand All @@ -159,7 +158,7 @@ export default function whitelistIntrinsics(

/*
* isAllowedPropertyValue()
* Whitelist a single property value against a permit.
* enforce permit for a single property value.
*/
function isAllowedPropertyValue(path, value, prop, permit) {
if (typeof permit === 'object') {
Expand Down Expand Up @@ -187,7 +186,7 @@ export default function whitelistIntrinsics(

if (objectHasOwnProperty(intrinsics, permit)) {
if (value !== intrinsics[permit]) {
throw TypeError(`Does not match whitelist ${path}`);
throw TypeError(`Does not match permit for ${path}`);
}
return true;
}
Expand Down Expand Up @@ -314,6 +313,6 @@ export default function whitelistIntrinsics(
}

// Start path with 'intrinsics' to clarify that properties are not
// removed from the global object by the whitelisting operation.
// removed from the global object by the permitting operation.
visitProperties('intrinsics', intrinsics, permitted);
}
20 changes: 10 additions & 10 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { arrayPush } from './commons.js';
/** @import {GenericErrorConstructor} from '../types.js' */

/**
* @file Exports {@code whitelist}, a recursively defined
* @file Exports {@code permits}, a recursively defined
* JSON record enumerating all intrinsics and their properties
* according to ECMA specs.
*
Expand All @@ -32,7 +32,7 @@ export const constantProperties = {
* universalPropertyNames
* Properties of all global objects.
* Must be powerless.
* Maps from property name to the intrinsic name in the whitelist.
* Maps from property name to the intrinsic name in the permits.
*/
export const universalPropertyNames = {
// *** Function Properties of the Global Object
Expand Down Expand Up @@ -115,7 +115,7 @@ export const universalPropertyNames = {
* Those found only on the initial global, i.e., the global of the
* start compartment, as well as any compartments created before lockdown.
* These may provide much of the power provided by the original.
* Maps from property name to the intrinsic name in the whitelist.
* Maps from property name to the intrinsic name in the permits.
*/
export const initialGlobalPropertyNames = {
// *** Constructor Properties of the Global Object
Expand All @@ -125,7 +125,7 @@ export const initialGlobalPropertyNames = {
RegExp: '%InitialRegExp%',

// Omit `Symbol`, because we want the original to appear on the
// start compartment without passing through the whitelist mechanism, since
// start compartment without passing through the permits mechanism, since
// we want to preserve all its properties, even if we never heard of them.
// Symbol: '%InitialSymbol%',

Expand All @@ -149,7 +149,7 @@ export const initialGlobalPropertyNames = {
* sharedGlobalPropertyNames
* Those found only on the globals of new compartments created after lockdown,
* which must therefore be powerless.
* Maps from property name to the intrinsic name in the whitelist.
* Maps from property name to the intrinsic name in the permits.
*/
export const sharedGlobalPropertyNames = {
// *** Constructor Properties of the Global Object
Expand All @@ -168,7 +168,7 @@ export const sharedGlobalPropertyNames = {
* uniqueGlobalPropertyNames
* Those made separately for each global, including the initial global
* of the start compartment.
* Maps from property name to the intrinsic name in the whitelist
* Maps from property name to the intrinsic name in the permits
* (which is currently always the same).
*/
export const uniqueGlobalPropertyNames = {
Expand Down Expand Up @@ -228,18 +228,18 @@ export { NativeErrors };
* blacklisted and simply removed. Properties not mentioned
* are also considered blacklisted and are removed.
* <li>A string value equal to a primitive ("number", "string", etc),
* in which case the property is whitelisted if its value property
* in which case the property is permitted if its value property
* is typeof the given type. For example, {@code "Infinity"} leads to
* "number" and property values that fail {@code typeof "number"}.
* are removed.
* <li>A string value equal to an intinsic name ("ObjectPrototype",
* "Array", etc), in which case the property whitelisted if its
* "Array", etc), in which case the property permitted if its
* value property is equal to the value of the corresponfing
* intrinsics. For example, {@code Map.prototype} leads to
* "MapPrototype" and the property is removed if its value is
* not equal to %MapPrototype%
* <li>Another record, in which case this property is simply
* whitelisted and that next record represents the disposition of
* permitted and that next record represents the disposition of
* the object which is its value. For example, {@code "Object"}
* leads to another record explaining what properties {@code
* "Object"} may have and how each such property should be treated.
Expand Down Expand Up @@ -1496,7 +1496,7 @@ export const permitted = {
//
// We will likely change this to add a property to Promise called
// Promise.delegate and put static methods on it, which will necessitate
// another whitelist change to update to the current proposed standard.
// another permits change to update to the current proposed standard.
HandledPromise: {
'[[Proto]]': 'Promise',
applyFunction: fn,
Expand Down
Loading

0 comments on commit 08d3945

Please sign in to comment.