|
1 | 1 | # harden |
2 | 2 |
|
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