Skip to content

Commit e5182ab

Browse files
committed
doc(harden): README
1 parent 3f0f347 commit e5182ab

File tree

1 file changed

+120
-1
lines changed

1 file changed

+120
-1
lines changed

packages/harden/README.md

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,122 @@
11
# harden
22

3-
This `@endo/harden` package is a skeleton package.
3+
Hardened modules are modules that make their exports resist tampering by other
4+
modules that import them, making them less suceptible to supply chain attack.
5+
In [HardenedJS](https://hardenedjs.org), the global `harden` function
6+
transitively freezes an object and all of the objects that are reachable by
7+
walking up chains of properties and prototypes.
8+
All the primordials like `Array.prototype` and `Object` are frozen in
9+
this environment, which gives your module a place to stand toward its own
10+
defense.
11+
Then, with [LavaMoat](https://github.com/lavamoat/lavamoat), each package
12+
is credibly isolated and only receives the subset of globals and host modules
13+
it needs to function.
14+
That is, we can enforce [Principle of Least
15+
Authority](https://en.wikipedia.org/wiki/Principle_of_least_privilege).
16+
But, that leaves the module to use `harden` to freeze all its exports and
17+
anything it returns that might be shared by other packages that use it.
18+
19+
In order to provide type information about the global `harden` in lockded-down
20+
HardenedJS, and also to make it possible for hardened modules to be used
21+
outside HardenedJS, the `@endo/harden` package exports a `harden` function that
22+
can be used either way.
23+
24+
```js
25+
import { harden } from '@endo/harden';
26+
27+
export const myFunction = () => {};
28+
harden(myFunction);
29+
```
30+
31+
By avoiding the export of hoisted `function` and `var` declarations and by
32+
immediately calling `harden` on any exposed function (or prototype thereof!) we
33+
leave no window of opportunity for another module to alter our exports.
34+
If a function's return value is meant to be shared by multiple parties (such
35+
as memoized objects), a hardened module author should harden the value before
36+
the function returns it (`return harden(value);`).
37+
38+
# With HardenedJS
39+
40+
The package `@endo/harden` reexports the `globalThis.harden` or
41+
`Object[Symbol.for('harden')]` in its execution environment, in order of
42+
preference, and is suitable regardless of whether a module is used
43+
with or without HardenedJS.
44+
45+
When using SES, `lockdown` creates `globalThis.harden` in the Realm's
46+
intrinsic `globalThis` and also automatically endows `globalThis.harden`
47+
to any `Compartment`.
48+
It is possible to delete `globalThis.harden` on new compartments.
49+
However, every version of SES published since the introduction of `@endo/harden`
50+
also provides `Object[Symbol.for('harden')]`, which is a property of one
51+
of the hardened shared intrinsics and cannot be subverted in a compartment.
52+
53+
The `harden` in `@endo/harden` prefers `globalThis.harden` because this
54+
affords the greatest degree of flexibility.
55+
Any multi-tenant `Compartment` should freeze its own `globalThis`, including
56+
making `harden` non-configurable and non-writable, so there is no risk
57+
of tampering, and endowing a `Compartment` with a different `harden`
58+
than the Realm's `Object[Symbol.for('harden')]` may be useful for some
59+
cases.
60+
61+
When creating a bundle for an application that can safely assume it will run in
62+
a HardenedJS environment, consider passing the build condition `-C hardened`.
63+
This will provide the smallest version of `@endo/harden`, one which will throw
64+
an exception if `harden` is not present.
65+
66+
```
67+
bundle-source -C hardened entry.js > entry.json
68+
```
69+
70+
# Without HardenedJS
71+
72+
Libraries that use `@endo/harden` can be used without HardenedJS and the
73+
exported `harden` only freezes the transitive owned properties of the object
74+
and does not traverse prototype chains.
75+
76+
Consequently, the surface of an object is immutable.
77+
However, if any fields of an object are optional, an attacker can subvert them
78+
by altering their prototype.
79+
This provides a degree of immutability that is useful for partial safety and
80+
does not interfere with uncoordinated alteration of the realm intrinsics, on
81+
which some testing and frontend user interface frameworks rely.
82+
83+
To opt out of any safety guarantees and to avoid the computation cost of
84+
transitively hardening own properties, use the `-C harden:unsafe` build
85+
condition with tools like `node` and Endo's `bundle-source`.
86+
87+
# Multiple instances
88+
89+
The first instance of `@endo/harden` will determine the behavior of any
90+
subsequent instance of `@endo/harden` that initializes later, regardless of
91+
differences in behavior.
92+
In a mutable, pre-lockdown JavaScript environment, it does this by behaving
93+
somewhat like a shim.
94+
A side-effect of the _first use_ of `harden` is that it installs its flavor of
95+
`harden` at `Object[Symbol.for('harden')]` and all subsequent initializations
96+
just adopt that behavior.
97+
This property is how `lockdown` senses that it should fail.
98+
99+
# With _or_ Without _not_ Both
100+
101+
Hardened modules calling `harden` should be fine at any time in an application
102+
that never uses HardenedJS, calling `lockdown`.
103+
104+
However, initializing a hardened module before setting up a HardenedJS
105+
environment (before calling `lockdown`) and then proceeding on the assumption
106+
that it's hardened after `lockdown` would leave the apparently-hardened module
107+
vulnerable.
108+
109+
So, `@endo/harden` arranges for `lockdown()` to throw an exception with
110+
a _helpful_ stack if `harden` gets called before `lockdown`.
111+
The stack points to the module that was initialized before `lockdown`
112+
and which should be moved after `lockdown`.
113+
The `lockdown` call often occurs as a side-effect of initializing
114+
`@endo/lockdown`, `@endo/init`, or by convention, modules with names like
115+
`prepare-*`.
116+
117+
# Configurability of Compartment harden
118+
119+
The `harden` exported by `@endo/harden` will defer to `globalThis.harden` if
120+
one was endowed, regardless of the presence of `Object[Symbol.for('harden')]`
121+
on the shared intrinsic.
122+

0 commit comments

Comments
 (0)