Capabilities (Capability-Based Security)
A capability is an unforgeable reference that conveys the right to perform a specific action on a specific resource. Code that holds a capability can use it; code that has not been granted the capability cannot use it, and has no way to manufacture a grant. The capability itself names the granted privilege, so authority travels through a program as a value rather than something inferred from the identity of the requestor.
Capability-based security is the mechanism that makes the Principle of Least Authority enforceable in practice.
Capabilities in everyday computing
Most people have used a capability system without knowing it. Open a "share" or "attach file" dialog on your phone: the app you're using does not hold permission to read your filesystem. The OS opens the picker. You choose one file. The picker hands back a reference to that single file (i.e., a capability) and the app reads it through that reference. The app never gains access to any file you did not select, and it has no way to construct a reference to one you did not give it.
The same pattern shows up across modern device platforms: the iOS document picker, Web Bluetooth (the browser asks you to pick a device, then hands the site a reference to that one device), browser file uploads. The user mediates each grant. The recipient holds an unforgeable reference, scoped to exactly what was given.
From access control to capabilities
Despite the ubiquity of capability-oriented security in everyday devices, most application workloads operate under traditional architectures of ambient authority. Conventional security models check the principal at access time. Under those models, if we want to evaluate whether user alice can write to a given file, the file system looks up alice against an ACL, or against a role table (RBAC), and decides. Authorization is ambient: every operation the process performs runs under alice's identity, and any library or function it calls inherits those permissions transitively.
Capability systems flip this around. Authorization travels with the reference, not the principal. A handle to a file does not check who is calling: it just works. The handle itself encodes the right. Code that doesn't have the handle has no way to manufacture one, and code that does have it can pass it along, in part or in whole, to other code that needs it.
This distinction is important because of the Confused Deputy problem (Hardy, 1988): a trusted intermediary, running with full ambient authority, gets tricked by an untrusted caller into using its authority on the caller's behalf. A compiler running with permission to write its log file is asked by a user to write the output somewhere else; because the compiler holds the authority and the OS only checks who's running, the request succeeds. With capabilities, the same flow can't happen. The compiler only holds the capability for its log file; nothing it can do can convert that into a write to an arbitrary path.
The first formal treatment of capabilities was Dennis and Van Horn's Programming Semantics for Multiprogrammed Computations (CACM, 1966). The lineage runs through KeyKOS, EROS, Mark Miller's E language, and into the modern Component Model.
Capabilities in the Component Model
WebAssembly components hold no ambient authority. A component's WIT world declares every external thing it needs as an import, and the host provides those imports at link time. Each import is a capability: a typed handle to a kind of resource, supplied by something outside the component.
- A component that imports
wasi:http/outgoing-handlerholds a capability to issue HTTP requests. The host's implementation of that interface decides which hosts it can actually contact. - A component that imports
wasi:config/storeholds a capability to read configuration values. The host decides which keys exist. - A component that imports
wasi:keyvalue/storeholds a capability to a key-value namespace. The host decides which buckets it can open.
Because every capability is named in the WIT world, the surface is enumerable: read the component's WIT file and you know every kind of resource it could touch. The order-processor used in the example below imports three:
package example:order-processor@0.1.0;
world order-processor {
import wasi:http/outgoing-handler@0.2.0;
import wasi:config/store@0.2.0-draft;
import wasi:keyvalue/store@0.2.0-draft;
}The world block enumerates every kind of resource the component can touch from outside its sandbox. Granting fewer than the imports means link-time failure. Granting more isn't possible; there's no way for the host to wire a capability into a component that didn't ask for it. Because the implementation is host-controlled, the scope of each granted capability is also enumerable: the platform team sets the bounds at the manifest layer.
See the Component Model Book for the type-level mechanics of how WIT imports become capabilities at link time.
Capabilities in Cosmonic Control
In a Cosmonic Control workload manifest, capabilities are declared in two places: each component's localResources block (config keys, outbound HTTP, mounted volumes) and the workload-level hostInterfaces block (keyvalue, blobstore, messaging, and other host-mediated interfaces). Each capability is scoped independently:
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
name: order-processor
spec:
replicas: 3
template:
spec:
hostSelector:
hostgroup: default
components:
- name: handler
image: ghcr.io/example/order-processor:0.2.0
localResources:
environment:
config:
LOG_LEVEL: info
CACHE_TTL: "300"
allowedHosts:
- https://api.payments.example.com
hostInterfaces:
- namespace: wasi
package: keyvalue
version: 0.2.0-draft
interfaces:
- store
config:
bucket: orders-cacheThree capabilities are granted to the handler component:
wasi:config/store, narrowed to two specific keys (LOG_LEVELandCACHE_TTL). The component cannot read any other configuration; the other keys aren't even visible.wasi:http/outgoing-handler, narrowed to one specific host (api.payments.example.com). The component can issue HTTPS requests to that host and no other.wasi:keyvalue/store, narrowed to a single bucket (orders-cache). The component can read and write within that bucket; other buckets backed by the same keyvalue store are not visible.
The three capabilities are declared in three different shapes because each is scoped through the manifest primitive most natural to it: an inline map for config values, an allowlist for outbound HTTP, a host-interface block for keyvalue and other host-mediated resources. The full map of which capability lives in which block is in Component Configuration.
Each scoping is independent. Narrowing allowedHosts to a single domain does not give the component access to additional config keys; narrowing the keyvalue bucket does not loosen the egress allowlist; expanding the config block does not give the component access to other keyvalue buckets. The manifest is the capability list: what's there is granted, what's not isn't.
This makes the maximum reach of the component statically inspectable. A reviewer can read the manifest and know exactly which external resources the component can touch, without reading or trusting the component's source code or container image.
Related
- Principle of Least Authority — the principle that capabilities enforce.
- Security and Non-Deterministic I/O — why capabilities are load-bearing when an LLM is in the loop.
- Component Configuration — full reference for declaring
localResourcesin workload manifests. - The Confused Deputy (Hardy, 1988) — the foundational paper on why capabilities solve a problem ACLs do not.
- The Component Model Book — type-level mechanics of WIT capabilities.