Endo
    Preparing search index...

    Module @endo/harden

    harden

    Hardened modules are modules that make their interface resist tampering by other modules that import them, making them less susceptible to supply chain attack. This includes hardening the exports and also the values they return or pass. In HardenedJS, the global harden function transitively freezes an object and all of the objects that are reachable by walking chains of properties and prototypes. All the primordials like Array.prototype and Object are frozen in this environment, which gives your module a place to stand toward its own defense. Then, with LavaMoat, each package is credibly isolated and only receives the subset of globals and host modules it needs to function. That is, we can enforce Principle of Least Authority. But, that leaves the module to use harden to freeze all its exports and anything it returns that might be shared by other packages that use it.

    In order to provide type information about the global harden in locked-down HardenedJS, and also to make it possible for hardened modules to be used outside HardenedJS, the @endo/harden package exports a harden function that can be used either way.

    import { harden } from '@endo/harden';

    export const myFunction = () => {};
    harden(myFunction);

    By avoiding the export of hoisted function and var declarations and by immediately calling harden on any exposed function (or prototype thereof!) we leave no window of opportunity for another module to alter our exports. If a function's return value is meant to be shared by multiple parties (such as memoized objects), a hardened module author should harden the value before the function returns it (return harden(value);).

    With HardenedJS

    The package @endo/harden reexports Object[Symbol.for('harden')] or globalThis.harden in its execution environment, in order of preference. It is suitable regardless of whether a module is used with or without HardenedJS.

    When using SES, lockdown creates globalThis.harden in the Realm's intrinsic globalThis and also automatically endows globalThis.harden to any Compartment. It is possible to delete globalThis.harden on new compartments. However, every version of SES published since the introduction of @endo/harden also provides Object[Symbol.for('harden')], which is a property of one of the hardened shared intrinsics and cannot be subverted in a compartment.

    The harden in @endo/harden prefers Object[Symbol.for('harden')] because endowments cannot override that intrinsic. Any multi-tenant Compartment should freeze its own globalThis, including making harden non-configurable and non-writable, so there is no risk of tampering.

    When creating a bundle for an application that can safely assume it will run in a HardenedJS environment, consider passing the build condition -C hardened. This will provide the smallest version of @endo/harden, one which will throw an exception if harden is not present.

    bundle-source -C hardened entry.js > entry.json
    

    Without HardenedJS

    Libraries that use @endo/harden can be used without HardenedJS and the exported harden freezes the object itself and the transitive own properties of the object, and does not traverse prototype chains.

    Consequently, the surface of an object is immutable. However, if any fields of an object are optional, an attacker can subvert them by altering their prototype. This provides a degree of immutability that is useful for partial safety and does not interfere with uncoordinated alteration of the realm intrinsics, on which some testing and frontend user interface frameworks rely.

    To opt out of any safety guarantees and to avoid the computation cost of transitively hardening own properties, use the -C harden:unsafe build condition with tools like node and Endo's bundle-source.

    Multiple instances

    The first call to harden from any instance of @endo/harden determines the behavior of any subsequent instance of @endo/harden that initializes later, regardless of differences in behavior. In a mutable, pre-lockdown JavaScript environment, it does this by behaving somewhat like a shim. A side-effect of that first call is that it installs its flavor of harden at Object[Symbol.for('harden')] and all subsequent initializations just adopt that behavior. This property is how lockdown senses that it should fail.

    With or Without not Both

    Hardened modules calling harden should be fine at any time in an application that never calls lockdown or repairIntrinsics.

    However, initializing a hardened module before setting up a HardenedJS environment (before calling lockdown) and then proceeding on the assumption that it's hardened after lockdown would leave the apparently-hardened module vulnerable.

    So, @endo/harden arranges for lockdown() and repairIntrinsics() to throw an exception with a helpful stack if harden gets called before either one. The stack points to the module that was initialized before lockdown and which should be moved after lockdown. The lockdown call often occurs as a side-effect of initializing @endo/lockdown, @endo/init, or by convention, modules with names like prepare-*.

    Configurability of Compartment harden

    The harden exported by @endo/harden prefers Object[Symbol.for('harden')] over globalThis.harden since the former is an intrinsic that cannot be overridden by an endowment. Any code that relies on globalThis.harden being endowed with a different behavior than Object[Symbol.for('harden')] should use that endowed globalThis.harden directly instead.

    isFake (deprecated)

    Using lockdown with the deprecated "unsafe" hardenTaming option creates an environment where Object.isFrozen, Object.isExtensible, Reflect.isExtensible, and isSealed all misreport that any object is frozen, non-extensible, and sealed. To indicate this, harden.isFake is true.

    We regret this misfeature. The @endo/harden does not provide harden.isFake. Code, especially tests, migrating to use @endo/harden should refactor harden.isFake to use a more legible indicator of the misbehavior of isFrozen and its compatriots, which may not be indicated by empirical behavior of harden.

    For example, Object.isFrozen({}) when harden.isFake and more clearly conveys the reason a test might be invalidated by unsafe hardenTaming. Testing that the outcome of Object.isFrozen({}) is the same as the outcome of Object.isFrozen(object) for an object that should not be frozen makes a test work just as well between safe and unsafe hardenTaming.

    The module @endo/harden/is-noop.js provides hardenIsNoop(harden) to detect whether harden is a no-op, regardless of hardenTaming. Do not rely on Object.isFrozen({}) to imply that harden is a no-op.

    import harden from '@endo/harden';
    import hardenIsNoop from '@endo/harden/is-noop.js';

    if (hardenIsNoop(harden)) {
    // ...
    }

    Modules

    is-noop.js