Skip to content

capability patterns and endo

Dan Connolly edited this page Jul 19, 2026 · 1 revision

Capability Patterns and Endo

Object-Capability Discipline

A capability is an unforgeable reference to an object that confers authority to invoke its methods. OCap discipline rests on two pillars:

  • Memory safety + encapsulation — no way to obtain a reference except by creation, construction, or message; no access to internal state without the object's consent.
  • Primitive effects only via references — all I/O and ambient authority are passed explicitly, never imported or globally accessible.

See OCap Discipline; Miller's thesis Robust Composition (2006); and the Security PictureBook (Stiegler, 2004).

Key Patterns

Facet

An intermediary that exposes a restricted subset of a powerful object's interface. E.g., a DepositFacet for a Purse allows deposits but not withdrawals or balance queries. See wiki/Facet.

CareTaker (Revocable Capability)

Wraps a capability such that the grantor can revoke access later. In Endo/Hardened JS: Proxy.revocable(). In E's Walnut: a forwarder that nulls its target on revoke(). The CareTaker pattern is essential for governance — stewards' authority can be revoked when roles change. See wiki/CareTaker.

Sealer/Unsealer

Like a public-key encryption pair for capabilities: seal creates an inert box; only the matching unsealer can retrieve the original. Used to share identification of a powerful object without sharing authority over it. See wiki/SealerUnsealer and vattr97's src/sealer.ts.

ClaimCheck

A lightweight token representing the right to retrieve a larger payload later. Analogous to a coat-check ticket. See wiki/ClaimCheck.

NotaryInspector

A notary attests to facts about an object without granting authority over it; an inspector reads those attestations. Separates certification from access.

PetNames

Names that are local, personal, and user-chosen rather than global and authoritative. Avoids the confused-deputy problem of global names. See wiki/PetNames.

Bearer Token

A token (string, URL, or other datum) that is forgeable in theory but practically unguessable due to high entropy. The token is the capability — whoever holds it exercises the authority.

Examples from the wiki:

  • Pre‑Signed URLs — "The URL is the capability." A signed URL embeds resource identity, expiry, and a cryptographic signature. Possession grants the authorized operation (GET, PUT) until expiry. ⚠️ Bearer semantics — must protect against link leakage.
  • Magic Link — A short-lived, single-use URL embedding a cryptographically random token tied to a specific user and purpose (login, password reset). The link is the capability. ⚠️ Bearer semantics — must protect against leakage and reuse. A CareTaker pattern: single-use, time-bound.
  • API keys, session tokens — ambient in practice: presented at a higher layer (HTTP headers) and scoped to the transport, not to individual objects.

Bearer tokens are a pragmatic concession to environments that lack object-capability memory safety (e.g., HTTP APIs, email links). They sacrifice the "unforgeable by construction" guarantee of ocap references for deployability. The Synergy pattern describes one way to manage them: rights amplification via opaque tokens that only have meaning when presented to the service that issued them.

Hardened JavaScript — The Foundation

Hardened JavaScript (aka SES) is what makes the ocap discipline enforceable at runtime. Three primitives do the work:

  • lockdown() — freezes all built-in prototypes, removes ambient authority
  • harden() — deep-freezes an object graph so its interfaces are immutable
  • Compartment — a sandbox for evaluating code with controlled endowments

The Maker Pattern: makeCounter

The canonical example starts with a simple counter:

const makeCounter = () => {
  let count = 0;
  return harden({
    incr: () => (count += 1),
    decr: () => (count -= 1),
  });
};

const counter = makeCounter();
counter.incr();

Each call creates a new encapsulated count variable and a frozen object with method-only access. There is no class, no this, no prototype mutation — just closure encapsulation + harden().

Separation of Duties (POLA)

Because incr and decr are separate references, they can be handed out independently:

entryGuard.use(counter.incr);   // can only count up
exitGuard.use(counter.decr);    // can only count down

This is the core ocap insight: an object reference is a permission. Give each party only the references it needs.

The Mint Pattern — from Counter to Purse

The same discipline scales to electronic rights. The makeMint pattern (~30 lines) builds on the same closure + harden() technique:

const makeMint = () => {
  const ledger = new WeakMap();

  const issuer = harden({
    makeEmptyPurse: () => mint.makePurse(0),
  });

  const mint = harden({
    makePurse: initialBalance => {
      const purse = harden({
        getIssuer: () => issuer,
        getBalance: () => ledger.get(purse),
        deposit: (amount, src) => {
          Nat(ledger.get(purse) + Nat(amount));
          ledger.set(src, Nat(ledger.get(src) - amount));
          ledger.set(purse, ledger.get(purse) + amount);
        },
        withdraw: amount => {
          const newPurse = issuer.makeEmptyPurse();
          newPurse.deposit(amount, purse);
          return newPurse;
        },
      });
      ledger.set(purse, initialBalance);
      return purse;
    },
  });

  return mint;
};

The progression is:

makeCounter → makeMint (mint + purse + issuer) → ERTP (brands, payments, deposit facets)

The mint creates purses, purses hold balances, deposit/withdraw move value, and the issuer is a separate facet for creating empty purses. All of ERTP's security properties — payments consumed on deposit, no forgery, no double-spending — emerge naturally from the ocap discipline: harden() prevents interface tampering, closure encapsulation protects balances, and reference-based access replaces identity-based authorization.

This is covered in 8 min in The Mint Pattern (starting at 25:25).

Widely Shared vs. Closely Held

The mint pattern introduces a key ocap design distinction:

Category Examples Authority
Closely held Purse, Mint, Unsealer Full: withdraw, mint, unseal
Widely shared DepositFacet, Brand, Sealed token Narrow: deposit only, inert

A purse is closely held — only its owner can withdraw. But its DepositFacet is widely shared — anyone can deposit into it. This asymmetry enables safe cooperation: you can receive payments without risking your balance.

Endo

Endo is a distributed secure computing platform that implements object-capability security in JavaScript. Built on TC39 standards.

Hardened JavaScript (SES)

Three primitives enforce the ocap discipline at runtime:

  • lockdown() — freezes all built-in prototypes, removes ambient authority (e.g., Date.now() is available but the constructor is frozen; no global fetch, no require).
  • harden() — deep-freezes an object graph so its interfaces are immutable and cannot be tampered with.
  • Compartment — a sandbox for evaluating code with a controlled set of endowments. Each compartment has its own global scope and can only reach what is explicitly injected.
import 'ses';
lockdown();
const c = new Compartment({ console, request: makeSafeRequest() });
c.evaluate(untrustedCode);

Vats

A vat is a unit of synchrony: one event loop, one message queue, one heap. Synchronous calls only work within a vat; cross-vat communication uses eventual send (E()). Vats may live on different machines or blockchains.

Remotables and Far()

Objects shared across vats are remotables, created with Far() or makeExo(). They get automatic serialization (marshaling) so they can be passed by reference via E().

Eventual Send (E())

const result = E(counter).incr();  // returns a promise

E(obj).method(args) sends a message asynchronously to obj, which may be local or in another vat. Messages are delivered in FIFO order per-object. On a promise, .then() is pipelined — the message is forwarded when the promise resolves.

OCapN

The OCapN protocol extends capabilities over the network. It provides secure, promise-pipelined RPC between vats on different machines, using CapTP as the transport.

Why Endo for Governance

Capability Governance application
Unforgeable references Steward roles are just references — no certificates, no keys, no PKI
Composable delegation Lead Steward issues domain steward capabilities; stewards delegate sub-roles
Revocation CareTaker pattern: revoke a steward's capability when they leave the role
Attenuation Facet pattern: issue a "read-only governance log" facet to auditors
No ambient authority Every governance action requires explicit presentation of a capability
Substrate independence Same capabilities work locally, over a network, or on a blockchain

Clone this wiki locally