Skip to content

fix(ses): enablements grow monotonically#3129

Merged
erights merged 4 commits intomasterfrom
markm-deep-enablements
Mar 16, 2026
Merged

fix(ses): enablements grow monotonically#3129
erights merged 4 commits intomasterfrom
markm-deep-enablements

Conversation

@erights
Copy link
Contributor

@erights erights commented Mar 16, 2026

Closes: #XXXX
Refs: #XXXX

Description

Problem originally reported by @boneskull .

overrideTaming: 'moderate' includes overrideTaming: 'min'.

Previously overrideTaming: 'min' correctly enabled Iterator.prototype.constructor to be overridden by assignment, but due to an oversight, overrideTaming: 'moderate' did not. Now it does.

To make such mistakes less likely, this PR also adopts a style where all records within larger enablements triple-dot the corresponding record from a smaller enablement, if present.

Security Considerations

Beyond fixing an observable bug, none. Everything is equally safe before and after this PR.

Scaling Considerations

none

Documentation Considerations

none

Testing Considerations

Tested.
Tests also modified to follow enablements being tested.
New test that the larger enablements are relaxations (as defined in packages/ses/test/enable-property-overrides-relaxation.test.js) of smaller enablements.

Compatibility Considerations

Iterator appears starting in Node 22. In my first attempt, tests broke in Node 22 because of this.

Upgrade Considerations

none.

@erights erights self-assigned this Mar 16, 2026
@changeset-bot
Copy link

changeset-bot bot commented Mar 16, 2026

🦋 Changeset detected

Latest commit: 48016c4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
ses Minor
@endo/bundle-source Patch
@endo/captp Patch
@endo/check-bundle Patch
@endo/cli Patch
@endo/common Patch
@endo/compartment-mapper Patch
@endo/daemon Patch
@endo/errors Patch
@endo/exo Patch
@endo/far Patch
@endo/import-bundle Patch
@endo/lockdown Patch
@endo/lp32 Patch
@endo/marshal Patch
@endo/module-source Patch
@endo/netstring Patch
@endo/ocapn Patch
@endo/pass-style Patch
@endo/patterns Patch
@endo/ses-ava Patch
@endo/stream-node Patch
@endo/stream-types-test Patch
@endo/stream Patch
@endo/test262-runner Patch
@endo/init Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@erights erights requested a review from boneskull March 16, 2026 04:41
@erights erights force-pushed the markm-deep-enablements branch from 335c632 to bb86fc3 Compare March 16, 2026 05:23
@erights erights force-pushed the markm-deep-enablements branch from a8685aa to 38ea294 Compare March 16, 2026 05:34
@erights erights marked this pull request as ready for review March 16, 2026 05:38
Copy link
Member

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also cover monotonicity via test assertions, e.g.

/**
 * Matches any non-empty string that consists exclusively of ASCII
 * letter/digit/underscore/percent sign and does not start with a digit.
 * Percent sign is allowed for unnamed primordials such as `%ObjectPrototype%`.
 */
const identifierLikePatt = /^[a-zA-Z_%][a-zA-Z_%0-9]*$/i;

/** @type {Map<symbol, string>} */
const builtinSymbols = new Map(
  Reflect.ownKeys(Symbol).flatMap(key => {
    const value = Symbol[key];
    return typeof value === 'symbol' ? [[value, key]] : [];
  }),
);

/** @type {(symbol: symbol) => string} */
const stringifySymbol = symbol => {
  const builtinSymbolName = builtinSymbols.get(symbol);
  if (builtinSymbolName) {
    return builtinSymbolName.match(identifierLikePatt)
      ? `Symbol.${builtinSymbolName}`
      : `Symbol[${JSON.stringify(builtinSymbolName)}]`;
  }
  const key = Symbol.keyFor(symbol);
  return key !== undefined
    ? `Symbol.for(${JSON.stringify(key)})`
    : `Symbol(${JSON.stringify(symbol.description)})`;
};

/**
 * Assert that some enablement value is a valid relaxation of a base enablement.
 * `true` may be relaxed only to `true`, "*" may be relaxed to `true` or "*",
 * and a base record may be relaxed to `true`, "*", or a record that includes a
 * superset of the base properties in which each property of the relaxation is
 * either absent from the base element or is (recursively) a valid
 * relaxation of the corresponding base enablement.
 */
const assertEnablementsRelaxation = (t, base, relaxation, path = '') => {
  // Relaxing to `true` is always acceptable.
  if (relaxation === true) return;

  // Otherwise, relaxation must either preserve `true`/"*" or be recursively
  // acceptable.
  if (base === true || base === '*') {
    t.is(
      relaxation,
      base,
      `relaxation must preserve ${JSON.stringify(base)} at ${path || 'top-level'}`,
    );
    return;
  }

  t.is(
    base === null ? 'null' : typeof base,
    'object',
    `base enablement at ${path || 'top-level'} must be \`true\`, "*", or a record`,
  );
  if (relaxation === '*') return;
  t.is(
    relaxation === null ? 'null' : typeof relaxation,
    'object',
    `relaxed enablement at ${path || 'top-level'} must be \`true\`, "*", or a record`,
  );

  const baseKeys = Reflect.ownKeys(base);
  const relaxationKeys = Reflect.ownKeys(relaxation);
  const missingKeys = baseKeys.filter(k => !relaxationKeys.includes(k));
  t.deepEqual(
    missingKeys,
    [],
    `relaxation must not omit base properties at ${path || 'top-level'}`,
  );
  for (const key of baseKeys) {
    const pathSuffix =
      typeof key === 'symbol'
        ? `[${stringifySymbol(key)}]`
        : key.match(identifierLikePatt)
          ? `.${key}`
          : `[${JSON.stringify(key)}]`;
    const subPath = `${path}${pathSuffix}`.replace(/^[.]/, '');
    const relaxationEnablement = Object.hasOwn(relaxation, key)
      ? relaxation[key]
      : undefined;
    assertEnablementsRelaxation(t, base[key], relaxationEnablement, subPath);
  }
};

test('moderateEnablements relaxes minEnablements', t => {
  assertEnablementsRelaxation(t, minEnablements, moderateEnablements);
});

test('severeEnablements relaxes moderateEnablements', t => {
  assertEnablementsRelaxation(t, moderateEnablements, severeEnablements);
});

@erights
Copy link
Contributor Author

erights commented Mar 16, 2026

@gibson042 , #3129 (review) done. Thanks!

@erights erights enabled auto-merge (squash) March 16, 2026 23:46
@erights erights merged commit a675d8e into master Mar 16, 2026
21 checks passed
@erights erights deleted the markm-deep-enablements branch March 16, 2026 23:49
@github-actions github-actions bot mentioned this pull request Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants