Skip to content

Commit 8297f41

Browse files
authored
feat(harden): Introduce @endo/harden (#3008)
Closes: #2978 ## Description This change introduces an `@endo/harden` package that allows packages to be written for use in a JS or a HardenedJS environment without modification. The `@endo/harden` module provides a behavior that depends on the environment and packaging conditions. Without any packaging conditions, in a HardenedJS environment, `@endo/harden` provides the environment’s “volume freezing” `harden`, meaning that it freezes the closure over both dimensions: transitive properties and prototypes. Also without any packaging conditions, if the environment does not provide a `harden`, `@endo/harden` provides a “surface freezing” `harden`, meaning that it freezes the closure over only the one dimension: properties. This provides a modicum of immutability without interfering with shims or other mutations to shared, intrinsic prototypes. With the `hardened` condition (`node -C hardened`, `bundle-source -C hardened`), `@endo/harden` will not retain an implementation of `harden` and will assert that `harden` existed as `Object[Symbol.for('harden')]` or `globalThis.harden` in the environment and vend out that `harden`. This is useful to minimize the size of bundles that can safely presume that they will run in a HardenedJS environment. With the `noop-harden` condition (`node -C noop-harden`), `@endo/harden` will provide a version of `harden` that returns its argument unaltered. With these new modes, we expect to deprecate the `lockdown` option for `"unsafe"` `hardenTaming` which goes further and replaces `isExtensible`, `isFrozen`, and `isSealed` with versions that misreport `true` for extensible, unfrozen, or unsealed objects respectively. We hope that the new default behavior of surface hardening will suffice, but we leave the `noop-harden` condition as an option since that should have performance parity with unsafe harden taming for environments that need it. As a side-effect, every kind of `harden` will install itself on first use at `Object[Symbol.for('harden')]` as a non-configurable property such that the first `@endo/harden` implementation used wins the race to define the hardening behavior of the realm. SES will install the same property at time of `lockdown`, but if it loses the race, will throw an exception indicating that the realm cannot be locked down because of unsafe usage of `harden` before `lockdown`, and render up the stack of the first use for diagnostic purposes. ### Security Considerations The `@endo/harden` provides a new mode of usage that is less safe than `lockdown` for environments in which `lockdown` is not practical. We do not expect safety to regress in lockdown environments as a consequence. This change strengthens one safety guarantee: going forward, hardened modules using `@endo/harden` will not be vulnerable to hosts that endow a compartment with a weakened version of `harden`, because `@endo/harden` always favors the `Object[Symbol.for('harden')]` enshrined on a shared intrinsic hardened by `lockdown`. ### Scaling Considerations Adopting `@endo/harden` will increase the size of bundles, and since this change adopts `@endo/harden` throughout the Endo stack, this bundle size increase may become problematic for systems close to their bundle size limits. We provide the bundler condition `hardened` to mitigate this problem. ### Documentation Considerations - [ ] This change comes with documentation in README and NEWS for all impacted packages, including advice to adopt the `hardened` bundle condition to mitigate the bundle size increase. ### Testing Considerations This change adds configurations to `sesAvaConfigs` where adopting `@endo/harden` allows those packages to be used in more configurations. The salient configuration Endo with shims installed only, without calling Lockdown, in the cases where packages continue to rely on Assert or Eventual Send. We hope in time to test in the Base configuration, without any shims. Some packages are able to adopt the No-op mode of harden and are accordingly tested in that mode. ### Compatibility Considerations This change is additive apart from the expected increase in bundle size, for which we provide a mitigation. ### Upgrade Considerations None.
2 parents b8b52ce + 2d741ef commit 8297f41

File tree

236 files changed

+2684
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

236 files changed

+2684
-309
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@endo/harden': minor
3+
---
4+
5+
- Introduces `@endo/harden`, providing a `harden` implementation that works
6+
both inside and outside HardenedJS.
7+
- Supports the `hardened` and `harden:unsafe` build conditions to select
8+
hardened-environment and no-op behaviors.
9+
- Detects pre-lockdown use of `harden` so `lockdown()` fails with a helpful
10+
error instead of leaving modules incorrectly hardened.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'ses': minor
3+
---
4+
5+
- `lockdown` and `repairIntrinsics` now detect when code has already called a
6+
`harden` imported from `@endo/harden` before lockdown, and fail with a clear
7+
error about hardened modules executing before lockdown.
8+
- Adds `Object[Symbol.for('harden')]` as a variant of `globalThis.harden` that
9+
cannot be overridden by an endowment named `harden` in compartments.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@endo/module-source': minor
3+
---
4+
5+
- Transitively freezes the properties of `ModuleSource` constructors and
6+
instances without requiring lockdown, for greater safety against
7+
supply-chain-attack.
8+
`ModuleSource`, particularly through the `@endo/module-source/shim.js`,
9+
necessarily runs before `lockdown` is called (if ever) and cannot rely on
10+
`harden`, so must preemptively transitively freeze its properties to be
11+
a hardened module, regardless of whether `lockdown` is ever called.
12+

.changeset/pre-lockdown-harden.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@endo/bundle-source': minor
3+
'@endo/captp': minor
4+
'@endo/check-bundle': minor
5+
'@endo/common': minor
6+
'@endo/eventual-send': minor
7+
'@endo/exo': minor
8+
'@endo/import-bundle': minor
9+
'@endo/lp32': minor
10+
'@endo/marshal': minor
11+
'@endo/memoize': minor
12+
'@endo/nat': minor
13+
'@endo/netstring': minor
14+
'@endo/pass-style': minor
15+
'@endo/patterns': minor
16+
'@endo/promise-kit': minor
17+
'@endo/stream-node': minor
18+
'@endo/stream': minor
19+
'@endo/zip': minor
20+
---
21+
22+
- Relaxes dependence on a global, post-lockdown `harden` function by taking a
23+
dependency on the new `@endo/harden` package.
24+
Consequently, bundles will now entrain a `harden` implementation that is
25+
superfluous if the bundled program is guaranteed to run in a post-lockdown
26+
HardenedJS environment.
27+
To compensate, use `bundle-source` with `-C hardened` or the analogous feature
28+
for packaging conditions with your preferred bundler tool.
29+
This will hollow out `@endo/harden` and defer exclusively to the global
30+
`harden`.

CONTRIBUTING.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,25 @@ If no version comment exists, it infers the latest tag for that action.
2626
CI enforces pinning with `node scripts/update-action-pins.mjs --check-pins`.
2727
If this check fails, run the updater and commit the resulting changes.
2828

29-
### Creating a new package
29+
## Validation
30+
31+
Continuous Integration is comprehensive.
32+
Many issues can be anticipated locally by running:
33+
34+
- `yarn`
35+
- `yarn format` for Prettier code formatting
36+
- `yarn docs`, because Typedoc has a holistic view of TypeScript definitions
37+
that lint in individual packages may not catch.
38+
- `yarn workspaces foreach --all --topological exec yarn pack`: Uncovers
39+
issues with type definition generation that only occur during a release.
40+
- `yarn lerna run --reject-cycles --concurrency 1 prepack`: Prepack (without
41+
cleanup per package) to ensure that type resolution in dependent packages
42+
continues to work when the typedefs are generated by their upstream packages.
43+
This helps avoid a situation in which the types only resolve because of the
44+
state of the local filesystem, and fails when imported in an NPM
45+
`node_modules` tree.
46+
47+
## Creating a new package
3048

3149
Run <code>[scripts/create-package.sh](./scripts/create-package.sh) $name</code>,
3250
then update the resulting README.md, package.json (specifically setting

ava-noop-harden.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
nodeArguments: ['-C', 'noop-harden'],
3+
files: ['test/**/*.test.*'],
4+
timeout: '2m',
5+
};

packages/bundle-source/cache.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @ts-check
2+
import harden from '@endo/harden';
23
import { makePromiseKit } from '@endo/promise-kit';
34
import { makeReadPowers } from '@endo/compartment-mapper/node-powers.js';
45

packages/bundle-source/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@endo/base64": "workspace:^",
2727
"@endo/compartment-mapper": "workspace:^",
2828
"@endo/evasive-transform": "workspace:^",
29+
"@endo/harden": "workspace:^",
2930
"@endo/init": "workspace:^",
3031
"@endo/promise-kit": "workspace:^",
3132
"@endo/where": "workspace:^",

packages/bundle-source/src/endo.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ const textDecoder = new TextDecoder();
2020
* @returns {BundlingKit}
2121
*/
2222
export const makeBundlingKit = (
23-
{ pathResolve, userInfo, computeSha512, platform, env },
23+
io,
2424
{ cacheSourceMaps, elideComments, noTransforms, commonDependencies },
2525
) => {
26+
const { pathResolve, userInfo, computeSha512, platform, env } = io;
2627
if (noTransforms && elideComments) {
2728
throw new Error(
2829
'bundleSource endoZipBase64 cannot elideComments with noTransforms',

packages/bundle-source/src/fs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// @ts-check
2+
import harden from '@endo/harden';
3+
24
let mutex = Promise.resolve(undefined);
35

46
/** @import {AtomicFileWriter, FileReader, FileWriter} from './types.js' */

0 commit comments

Comments
 (0)