Overview
Dual to the notion of capability revocation is that of introduction, the convention by which a vat receives real, useful authority.
MetaMask has endowment factories which produce RPC-gated attenuations of the root MM-managed authorities (sign message, for example). The attenuations include well-known caveats, allowing the engine to restrict API invocations by both the state of a controller and the arguments of the invocation.
@endo/patterns offers an argument-only version of attenuation called narrowing via its API. Given the ocap-kernel already short-circuits cross-vat delegation chains directly to the resolving exo, this presents a unique opportunity to apply object capability principles to arbitrary kernel-managed authorities. In short, we can construct useful authority in an isolated vat, and offer a kernel.narrow operator that intersects a capability's callable interface with a new interface guard.
const readable = makeRead('~/.ocap/files/*');
async function getFile(request, path: string) {
return kernel.narrow(
'File',
read,
{ read: M.call(M.path(`~/.ocap/files/${path}`)) };
}
Leaving alone that M.path does not exist, this is otherwise a straightforward motivating example. The competing, endo-native approach for narrowing is so long that I'm posting it in a comment below.
Why Kernel Level is Right
We can reuse the same routine to introduce narrowed endowments, and still keep a record of what subpaths are available where. For a subgraph kernel-internal vats which have verifiable isolation, we can track exactly which slices of authority are available to each routine. By this method, we err on the side of caution, and push onto user-code the responsibility for registering with the kernel that an exported capability has strictly less authority than the import it narrows.
Implementation suggestion
The kernel.narrow operator produces a proxy kref that only the kernel knows about. A straightforward, if inelegant, implementation of pattern intersection is available via the M.and operator. As a compelling detail of patterns' serializability, the ocap-kernel can maintain a registry of the API interface of a kref, allowing it to reject invalid invocations without even pinging the home vat. In case of a virtual object reference to a vat which is not currently live, this allows the kernel to reject an invalid invocation without revivifying the vat (or its supporting subcluster).
Bonus Algebra
Given exported objects which are kernel narrowings of the same base object (including transitively), the kernel has all of the information necessary to construct the accessible section of the base object. If an endowment carries mutual exclusion invariants (if set('x', val) exists then set('y', val) is forbidden), the kernel is in a position to enforce such rules at the authority root instead of relying upon careful use at the call sites and the inevitable TOCTOU races between those sites.
Extension opportunities
In mirror of MetaMask's rpc-engine, we can do stateful attenuation via a DSL of well-known caveats (await E.get(cap, key) matches <pattern>). We could alternatively take a monadic approach, where the guard matches against both the invocation arguments and the state-presented-as-arguments. I don't think we benefit much from monadification, but it is an interesting option to keep in mind.
Overview
Dual to the notion of capability revocation is that of introduction, the convention by which a vat receives real, useful authority.
MetaMask has endowment factories which produce RPC-gated attenuations of the root MM-managed authorities (sign message, for example). The attenuations include well-known caveats, allowing the engine to restrict API invocations by both the state of a controller and the arguments of the invocation.
@endo/patternsoffers an argument-only version of attenuation called narrowing via its API. Given the ocap-kernel already short-circuits cross-vat delegation chains directly to the resolving exo, this presents a unique opportunity to apply object capability principles to arbitrary kernel-managed authorities. In short, we can construct useful authority in an isolated vat, and offer akernel.narrowoperator that intersects a capability's callable interface with a new interface guard.Leaving alone that
M.pathdoes not exist, this is otherwise a straightforward motivating example. The competing, endo-native approach for narrowing is so long that I'm posting it in a comment below.Why Kernel Level is Right
We can reuse the same routine to introduce narrowed endowments, and still keep a record of what subpaths are available where. For a subgraph kernel-internal vats which have verifiable isolation, we can track exactly which slices of authority are available to each routine. By this method, we err on the side of caution, and push onto user-code the responsibility for registering with the kernel that an exported capability has strictly less authority than the import it narrows.
Implementation suggestion
The
kernel.narrowoperator produces a proxy kref that only the kernel knows about. A straightforward, if inelegant, implementation of pattern intersection is available via theM.andoperator. As a compelling detail of patterns' serializability, the ocap-kernel can maintain a registry of the API interface of a kref, allowing it to reject invalid invocations without even pinging the home vat. In case of a virtual object reference to a vat which is not currently live, this allows the kernel to reject an invalid invocation without revivifying the vat (or its supporting subcluster).Bonus Algebra
Given exported objects which are kernel narrowings of the same base object (including transitively), the kernel has all of the information necessary to construct the accessible section of the base object. If an endowment carries mutual exclusion invariants (if
set('x', val)exists thenset('y', val)is forbidden), the kernel is in a position to enforce such rules at the authority root instead of relying upon careful use at the call sites and the inevitable TOCTOU races between those sites.Extension opportunities
In mirror of MetaMask's rpc-engine, we can do stateful attenuation via a DSL of well-known caveats (
await E.get(cap, key) matches <pattern>). We could alternatively take a monadic approach, where the guard matches against both the invocation arguments and the state-presented-as-arguments. I don't think we benefit much from monadification, but it is an interesting option to keep in mind.