CONCEPT Cited by 1 source
Object-capability RPC¶
What it is¶
Object-capability RPC is an RPC model in which references to remote objects and functions are first-class values on the wire. Holding a reference (a "stub") is the authority to invoke the underlying object — there is no out-of-band ACL check on every call, no global function namespace anyone can name, and (critically) no way for a peer to forge a reference it wasn't granted. Method invocations on a stub are proxied back to the location where the object lives.
The security model comes straight from Dennis & Van Horn's 1966 capability-based access control: what you can do is determined by what references you possess, and references are unforgeable by construction. Applied to RPC, this means:
- Functions pass by reference. Pass a function over RPC and the recipient gets a stub; invoking the stub calls the function back at its origin.
- Objects pass by reference. An object extending a marker type
(e.g.
RpcTargetin Cap'n Web) is transmitted by reference; method calls route back to the origin. - Stubs are unforgeable. The only way to obtain one is to be handed one by a peer that already has it. This lets API authors encode authorization in the type system rather than re-checking on every method.
The "Cap'n" in Cap'n Proto and Cap'n Web is short for "capabilities and …" — the naming flags this as the load-bearing design decision.
Why it matters¶
- Authorization-by-possession, not authorization-by-identity.
A caller that holds a stub for an
AuthenticatedSessioncan invoke protected methods without re-presenting credentials — the session object is the credential. See patterns/capability-returning-authenticate. - Session state without special cases. Most RPC systems handle auth via a connection-state flag mutated by a "login" message; object-capability RPC replaces that with a typed stub return value, so auth fits the normal API abstraction. Particularly valuable over WebSocket where headers can't be added after the initial handshake.
- Least privilege falls out for free. Hand out a narrow stub
(e.g.
ReadOnlyBucket) and the recipient structurally cannot call the wider API — there's no stub to invoke. Contrast a REST API where all URLs are guessable and the authorization check lives in the handler. - Bidirectional / symmetric calling. Passing a callback by reference is just another instance of the same mechanism, so bidirectional RPC comes free with no special protocol for it.
Trade-offs vs traditional RPC¶
- Lifetime / GC is harder. A stub held by a remote peer pins the underlying object. Implementations need an export/import table, reference counting (or equivalent), and a disposal discipline. Cap'n Web uses signed integer IDs never reused over a connection and implicit / explicit release.
- Debuggability shifts. Instead of "look at the URL and the bearer token," you look at the export table: who is holding which stubs? Traces need to correlate stub IDs.
- Transport must be bidirectional. Object-capability RPC needs callbacks to work, which rules out naïve request/reply transports unless stubs are limited to the request lifetime (Cap'n Web's HTTP batch mode does exactly this — references become invalid once the batch completes).
- Not naturally stateless. Standard REST / gRPC services scale horizontally by being stateless; object-capability RPC is connection-oriented (each connection has its own export table), which affects sharding / load-balancing design.
Capability-as-authorization: the canonical pattern¶
The idiomatic idiom in object-capability RPC is:
// Top-level interface (anyone can call):
class ApiServer extends RpcTarget {
authenticate(apiKey: string): Session {
if (!checkApiKey(apiKey)) throw new Error("bad key")
return new AuthenticatedSession(this.lookupUser(apiKey))
}
}
// Returned by authenticate(); only callers who successfully
// authenticated hold references:
class AuthenticatedSession extends RpcTarget {
whoami(): string { return this.username }
// ... other protected methods
}
Clients do const session = await api.authenticate(key); const who =
await session.whoami(). The server side cannot be called by a
client that never obtained a session stub. There is no
if (req.user) check on every method — the type system enforces it.
See patterns/capability-returning-authenticate.
Seen in¶
- sources/2025-09-22-cloudflare-capn-web-rpc-for-browsers-and-web-servers
— Kenton Varda's announcement of Cap'n Web
explicitly names object-capability RPC as the inherited design
choice from Cap'n Proto and walks the
authenticate()-returning-a-stub pattern in detail. "It is impossible for the client to 'forge' a session object."
Related¶
- concepts/promise-pipelining — companion capability-RPC innovation; Cap'n Proto and Cap'n Web ship both together.
- concepts/bidirectional-rpc — a consequence of making function references first-class.
- concepts/capability-based-sandbox — same underlying philosophy applied to runtime sandboxing (Cloudflare Dynamic Workers) rather than RPC.
- patterns/capability-returning-authenticate — the canonical auth pattern enabled by object-capability RPC.
- systems/capnweb / systems/capnproto