Skip to content

feat(ses): update lockdown init and enable Hermes VM#2723

Merged
leotm merged 6 commits into
masterfrom
ses-hermes-lockdown-taming
Apr 7, 2025
Merged

feat(ses): update lockdown init and enable Hermes VM#2723
leotm merged 6 commits into
masterfrom
ses-hermes-lockdown-taming

Conversation

@leotm

@leotm leotm commented Feb 14, 2025

Copy link
Copy Markdown
Contributor

Closes: #1891 (tracker), tested on #2334, Endo Sync: 2025-01-29

Previous iterations: #2723 (comment)

TODO

  • add new lockdown option
    • legacyHermesTaming: safe (default), unsafe
    • hostEvaluators: '_legacy' (default), 'all' (regular), 'none' (strict csp), 'no-direct' (hermes)
    • fail on safe-eval (default) and no-direct (hermes) early
      • i.e. require unsafe-eval and no-direct on hermes (since with not supported)
    • add assertions (on non _legacy options) and types
    • update ses/error-codes/SES_DIRECT_EVAL.md
    • update lockdown.md
    • refactor assertDirectEvalAvailable to probeHostEvaluators
      • return results, now used for further assertions
      • test Function() constructor and eval() separately then together
    • ensure backwards compatibility (lockdown({}) - no option provided)
      • before: worked with a strict CSP (assertDirectEvalAvailable swallows strict CSP error then skips SES_DIRECT_EVAL error)
      • after: 'all' as default (breaking change) with a strict CSP will error, 'none' is now required
      • endo sync: 4th option undefined, warn (with new error reporter) to use the new lockdown option
      • since getEnvironmentOption default must be a string ('all'), warn instead of SES_DIRECT_EVAL error with a strict CSP
      • default to '_legacy' under-the-hood if lockdown({}), warn to use new option; SES_DIRECT_EVAL error with a normal CSP, also when set to 'all'
      • document TODOs to replace '_legacy' with 'all' as default in future (the breaking change)
    • disable compartment-shim when bundling ses for hermes
    • disable: globalThis Compartment and testCompartmentHooks
    • disable compartmentInstance.evaluate instead of deleting compartment
      • makeCompartmentConstructor setGlobalObjectEvaluators safeEvaluate is the default (no evalTaming yet)
      • CompartmentPrototype.evaluate returns compartmentEvaluate safeEvaluate is the default (no evalTaming)
      • endo sync: throw error on usage, if env lacks direct eval support
  • test on hermes
    • evalTaming: no-eval/safe-eval/unsafe-eval
    • hostEvaluators: all/none/no-direct
    • post-lockdown: eval(42), eval('42'), Function('return 42')()
  • check prev test branch (below)
  • fix typos
  • update smoke test with Hermes eval assertions
  • enable Hermes VM in CI
  • fix(ses): lockdown options should be kebob-case #2739 merged 🎉 resolve conflicts
  • endo sync
    • lockdown options enum values kebab-case vs camelCase
    • could make no-direct a new evalTaming option instead, however this would mean no longer being able to choose unsafe-eval or no-eval, one would have to be the assumed default
    • could use no-eval instead of unsafe-eval (example added in docs)
    • dynamic no-direct instead of via lockdown option? no since we don't know the user intention, whether to throw SES_DIRECT_EVAL or allow SES to init
    • why not error on safe-eval? did this originally, but better to fail earlier when there are incompatible lockdown options (safe-eval requires direct eval)
    • no-compartment-eval instead of no-direct? maybe not since we also now allow SES to init w/o direct eval
    • --disallow-code-generation-from-strings useful node flag to emulate a strict csp runtime in tests
  • re-review

Follow-up: new Compartment() fails on removeUnpermittedIntrinsics at

  • Tolerating undeletable intrinsics.%CompartmentPrototype%.importNow.prototype === undefined
  • Uncaught TypeError: property is not configurable

test branch https://github.com/endojs/endo/tree/ses-hermes-p2 (from #2334)

  • yarn build:hermes bundle ses for hermes
  • yarn test:hermes run ses/test/_hermes-smoke.js

Hermes eval behaviour on bin/hermesc (standalone compiler) and bin/hermes (vm, eshost)

// Hermes direct eval cannot access or modify variables within its closure scope (sloppy and indirect)

eval = globalThis.eval; // remains direct eval, compiler: "error: invalid assignment left-hand side"

// calling globalThis.eval(...) uses indirect eval

// calling eval on Hermes VM emits: "warning: Direct call to eval(), but lexical scope is not supported."

print(eval(1 + 1));
print(eval("1 + 1"));

function a(eval, TEST) {
  // compiler: "error: cannot declare 'eval'"
  eval("TEST=true");
  return TEST;
}
print(a(eval, false)); // vm: false

function b(eval) {
  // compiler: "error: cannot declare 'eval'"
  var TEST = false;
  eval("TEST=true");
  return TEST;
}
print(b(eval)); // vm: false

function c() {
  var TEST = false;
  eval("TEST=true");
  return TEST;
}
print(c()); // vm: false

function d() {
  var TEST2 = 42;
  return eval("TEST2");
}
print(d()); // ReferenceError: Property 'TEST2' doesn't exist

// Note: eshost only runs bin/hermes (vm), not bin/hermesc (compiler)

Comment thread packages/ses/src/make-eval-function.js Outdated
Comment thread packages/ses/src/make-eval-function.js Outdated
Comment thread packages/ses/src/make-eval-function.js Outdated
@leotm leotm changed the title feat(ses): Hermes eval and compartment taming feat(ses): Hermes eval and compartment taming lockdown option Feb 17, 2025
Comment thread packages/ses/src/commons.js Outdated
Comment thread packages/ses/src/commons.js Outdated
Comment thread packages/ses/src/lockdown.js
Comment thread packages/ses/src/lockdown.js Outdated
Comment thread packages/ses/src/lockdown.js Outdated
Comment thread packages/ses/src/make-function-constructor.js
Comment thread packages/ses/src/global-object.js
@leotm leotm force-pushed the ses-hermes-lockdown-taming branch 5 times, most recently from 69a97f5 to 7e2b07a Compare February 19, 2025 17:27
@leotm leotm changed the base branch from ses-hermes-fn-caller-arguments-permits-check to master February 19, 2025 17:29
@leotm leotm changed the base branch from master to ses-hermes-fn-caller-arguments-permits-check February 19, 2025 17:30
@leotm leotm force-pushed the ses-hermes-lockdown-taming branch 3 times, most recently from fa54256 to 5793270 Compare February 19, 2025 18:13
Comment thread packages/ses/src/lockdown.js Outdated
@leotm leotm force-pushed the ses-hermes-lockdown-taming branch 3 times, most recently from 1e87397 to 063ec33 Compare February 19, 2025 19:04
Comment thread packages/ses/src/lockdown.js Outdated
@leotm leotm force-pushed the ses-hermes-lockdown-taming branch 3 times, most recently from 8cf7a5e to d0269ff Compare February 24, 2025 17:08
Comment thread packages/ses/docs/lockdown.md Outdated
Comment thread packages/ses/src/compartment.js Outdated

if (evalTaming === 'unsafe-no-direct') {
evaluator = () => {
throw TypeError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this take care of all three evaluators: eval, Function, Compartment (or just Compartment evaluate)?

'no-eval' should have the same effect on Compartments as 'unsafe-no-direct'. Why is it a separate code path? Are there any differences observable from within a Compartment?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is only covering Compartment evaluate.

We've introduced unsafe-no-direct with the intention of creating a variant of unsafe-eval where root compartment evaluators remain untouched but due to inability to use direct eval we have to throw from Compartment evaluate.

The intention is to allow the program to use Function or eval for some of the basic tasks they're sometimes used for in the ecosystem (like checking code for syntax errors), and we have some ideas for what we could do to implement evaluators in absence of direct eval. It does not undermine the value of having a locked-down environment while not bundling with inline compartments yet.

Comment thread packages/ses/src/global-object.js Outdated
Comment on lines +108 to +111
// We reach here if the Function() constructor is outright forbidden by a
// strict Content Security Policy (containing either a `default-src` or a
// `script-src` directive), not been implemented in the host, or the host
// is configured to throw an exception instead of `new Function`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't 'no-eval' also disallow these?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is where we check what the capabilities of the host are.
no-eval should have an effect after this check.

If the above is not correct, we have to look into running this earlier or changing the logic of asserting on the results of probeHostEvaluators a bit.

Comment thread packages/ses/src/lockdown.js
Comment thread packages/ses/src/lockdown.js Outdated
Comment thread packages/ses/NEWS.md Outdated
Comment thread packages/ses/NEWS.md Outdated
@leotm

leotm commented Mar 27, 2025

Copy link
Copy Markdown
Contributor Author

noting iterations for posterity

@erights

erights commented Mar 27, 2025

Copy link
Copy Markdown
Contributor

@Jack-Works , this PR is now proceeding under the assumption that it would be ok with you to change the behavior of 'unsafe-eval' slightly: On a platform (like Hermes) without any direct eval, it would be ok to move the error that compartment-evaluators-are-not-possible from initialization time to the time that someone tries to use a compartment evaluator. This would allow non-compartment use of unsafe evaluators even on those platforms. On platforms with direct eval, this change should have no observable difference.

Would that change in 'unsafe-eval' behavior be ok with you? If not, could you clarify your motivating use case for 'unsafe-eval'? Thanks.

@Jack-Works

Copy link
Copy Markdown
Contributor

@Jack-Works , this PR is now proceeding under the assumption that it would be ok with you to change the behavior of 'unsafe-eval' slightly: On a platform (like Hermes) without any direct eval, it would be ok to move the error that compartment-evaluators-are-not-possible from initialization time to the time that someone tries to use a compartment evaluator. This would allow non-compartment use of unsafe evaluators even on those platforms. On platforms with direct eval, this change should have no observable difference.

Would that change in 'unsafe-eval' behavior be ok with you? If not, could you clarify your motivating use case for 'unsafe-eval'? Thanks.

Yes, it's ok with me. I'm only using this on platforms with direct eval available

Comment thread packages/ses/NEWS.md Outdated
Comment thread packages/ses/NEWS.md Outdated
Comment thread packages/ses/src/make-eval-function.js Outdated
Comment thread packages/ses/test/_hermes-smoke.js
@leotm

This comment was marked as resolved.

@leotm

leotm commented Apr 1, 2025

Copy link
Copy Markdown
Contributor Author

@erights i've rebased the 9 commits into 6 and ready for merge, unless any further feedback

@erights erights left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks!

Comment thread packages/ses/NEWS.md Outdated
@@ -1,4 +1,12 @@
User-visible changes in `ses`:
<!-- markdownlint-disable MD041 -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does this line mean? Does the need for this have something to do with this PR, or is it a drive-by? Did you use some kind of yarn format-like or prettier-like thing for .md files? Should we bundle that in to yarn format (though not in this PR)? If so, please at least file an issue.

In any case, no change suggested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sorry i resolved #2723 (review) too early, exactly we should add a script (or include it in yarn format) then ensure it's run in CI (example)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you're right it's out of scope of this PR, filed issue here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

reverted formatting changes in the end to keep this PR relevant/minimal and not step on any toes

all .md docs in the codebase better updated when addressing the filed issue then reviewed separetely

Comment thread packages/ses/NEWS.md
or setting the environment variable

```sh
$ export LOCKDOWN_ERROR_TAMING=unsafe

@erights erights Apr 5, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Was the $ removal manual or by the .md fixer thing?

I do mildly prefer keeping the $ prefix so readers can easily see this is shell and not js.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yep .md fixer https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md014.md

Rationale: It is easier to copy/paste and less noisy if the dollar signs are omitted when they are not needed. See https://cirosantilli.com/markdown-style-guide#dollar-signs-in-shell-code for more information.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because some commands do not produce output, it is not a violation if some commands do not have output:

$ mkdir test
mkdir: created directory 'test'
$ ls test

seems a bit silly that some doesn't include one command with no output 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I do mildly prefer keeping the $ prefix so readers can easily see this is shell and not js.

agree ^ added exception <!-- markdownlint-disable MD014 -->

Comment thread packages/ses/docs/lockdown.md
@leotm leotm mentioned this pull request Apr 7, 2025
@leotm

leotm commented Apr 7, 2025

Copy link
Copy Markdown
Contributor Author

@leotm

leotm commented Apr 7, 2025

Copy link
Copy Markdown
Contributor Author

nb: next release (ses@1.13.0) ses-hermes.cjs shim will be consumed by

to plug into Metro for bundling React Native apps

@leotm

leotm commented Apr 7, 2025

Copy link
Copy Markdown
Contributor Author

RE #2334 (review) @kriskowal

Please add a draft of release notes to NEWS.md. I roll these up to a blog on hardenedjs.org now and this would likely be a headline.

and RE #2108 @erights

None. Nothing Breaking certainly. Nothing newsworthy yet. But when the ses-shim as a whole is know to support Hermes, that will be newsworthy ;)

happy to expand current NEWS.md update in follow-up a PR to be more newsworthy (;

which i imagine would land on https://github.com/endojs/hardenedjs.org/tree/main/src/content/docs/blog

i foresee the blog post could detail how SES can now be used with Metro to run Hardened JS on React Native Hermes (or JSC) apps, like we're doing in LavaMoat/LavaMoat#1438

  • SES now compatible with Hermes compiler/VM
  • SES now tolerates Hermes language features
  • SES can now initialise w/o direct eval using evalTaming
  • lockdown only, no Compartment yet
  • import/require 'ses/hermes'
  • curl/wget .../ses-hermes.cjs
  • currently supporting v0.12.0, next v0.13.0 then either
    • further hermes tags
    • subsequent supported major versions of react-native containing sdk with side-by-side hermes bins
    • (unreleased static hermes, currently too expensive to build and run in CI)

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.

SES lockdown Hermes compat tracking issue

5 participants