Chio/Docs
LOGIN · JOIN

Why Capabilities

Access lists and capabilities assign authority differently when a program acts on another party’s request.

Concept chio@5f63e6e

On this page

An agent that acts on text written by someone else serves two parties: the request comes from one and the authority from another. Capabilities keep the two apart, and Chio grants authority to agents in that form.

A review agent reads a pull request and then calls tools. The text can name any file, branch, or endpoint, and if the agent’s calls run under its operator’s credentials, every resource those credentials reach is one the text can ask the agent to change. Operating systems research met this problem long before language models. It described authority as a table of who may do what, stored the table in two different ways, and found that only one of them lets a program keep two parties’ authority apart.

Access lists and capability lists store the same matrix

In 1971 Butler Lampson gave protection an abstract model that fit most mechanisms of the time. It has a set of objects, a set of domains, and an access matrix A. Rows are labeled by domains and columns by objects, and each entry holds the access attributes one domain has to one object, such as read or write. Later work, including the paper by Miller, Yee, and Shapiro used below, calls the rows subjects and the columns resources.

A matrix with a row for every subject and a column for every resource is mostly empty, and Lampson judged a single table of its entries usually impractical. He described two ways to cut it instead. Attach each subject’s row to the subject, as pairs of a resource and the rights on it: each pair is a capability, and the subject’s set of them is its capability list, or C-list. Or attach each resource’s column to the resource, as a list of who may do what: an access control list.

The subjects and files in this matrix come from Hardy’s account of the confused deputy.

Hardy’s compiler as an access matrix, stored as access lists or as capability listsstore the matrix asaccess listscapability listsselect a column to name the output fileuserthe invokerucompilerhome files licensecx1(USER)DEBUGx2(SYSX)STATx3(SYSX)BILL{read, write}∅∅∅{write}{write}
requestRUN (SYSX)FORT debugging output to "(SYSX)BILL"a name, carrying no authoritycheckwrite∈Au,x3∪Ac,x3=∅∪{write}\text{write} \in \htmlClass{cor-parent}{A}_{u,\htmlClass{cor-child}{x_3}} \cup \htmlClass{cor-parent}{A}_{c,\htmlClass{cor-child}{x_3}} = \varnothing \cup \{\text{write}\}resultadmitted by the compiler’s home files license; (SYSX)BILL is overwritten
Hardy’s compiler as an access matrix, stored as access lists or as capability listsstore asaccess listscapability listsucx1(USER)DEBUGx2(SYSX)STATx3(SYSX)BILL{read, write}∅∅∅{write}{write}
requestRUN (SYSX)FORT debugging output to "(SYSX)BILL"a name, carrying no authoritycheckwrite∈Au,x3∪Ac,x3=∅∪{write}\text{write} \in \htmlClass{cor-parent}{A}_{u,\htmlClass{cor-child}{x_3}} \cup \htmlClass{cor-parent}{A}_{c,\htmlClass{cor-child}{x_3}} = \varnothing \cup \{\text{write}\}resultadmitted by the compiler’s home files license; (SYSX)BILL is overwritten

Access lists; the user names (SYSX)BILL for the debugging output. Admitted by the compiler’s home files license; (SYSX)BILL is overwritten.

admit(q, κ)  ⟹   q∈scope⁡(κ)∧ caller⁡(q)=subject⁡(κ)\begin{aligned} \text{admit}(\htmlClass{cor-child}{q},\ \htmlClass{cor-parent}{\kappa}) \implies\ & \htmlClass{cor-child}{q} \mathrel{\htmlClass{cor-relation}{\in}} \operatorname{scope}(\htmlClass{cor-parent}{\kappa}) \\ \land\ & \operatorname{caller}(\htmlClass{cor-child}{q}) = \operatorname{subject}(\htmlClass{cor-parent}{\kappa}) \end{aligned}
In Rust · evaluate.rs:57
pub struct EvaluateInput<'a> {
    /// Tool call request being evaluated.
    pub request: &'a PortableToolCallRequest,
    /// The capability token authorising this call.
    pub capability: &'a CapabilityToken,
    // …
}
A reconstruction of Hardy’s account, with (USER)DEBUG standing for any file of the user’s and the compiler’s rights taken from its home files license in SYSX. Both storage choices hold the same cells and differ in which cells a request can reach; the Rust is Chio’s evaluation input, where a request and its capability arrive together.Capabilities

Both layouts contain the same permissions. This has led to the belief that access lists and capabilities are equivalent. Miller, Yee, and Shapiro named the belief the Equivalence Myth. A static matrix does not say how a subject names the resource it wants, or how rights move from one subject to another, and the two designs differ in both.

Naming is the first difference. Under access lists, a subject names a resource through a shared namespace, such as file names, and the system then consults the resource’s list. The name and the permission arrive by separate routes. A capability points from the subject to the resource, so each capability can serve both to “designate which resource to access, and to provide the authority to perform that access.”

The compiler mixed two parties’ authority

Norm Hardy published the account in 1988, from his time at Tymshare, a commercial timesharing company. A compiler was installed in a directory called SYSX, and a user ran it with RUN (SYSX)FORT, optionally naming a file to receive debugging output. The compiler also recorded language usage statistics in (SYSX)STAT. To let it write that file, Tymshare gave the compiler a home files license, which allowed it to write files in its home directory, SYSX.

The billing information was stored in the same directory, in (SYSX)BILL. A user who learned that name supplied it as the file for debugging output. The compiler asked the operating system to open the file, the system saw the home files license and allowed the write, and the billing information was lost.

The compiler’s code had been correct when it was written. Hardy traced the fault to authority “stemming from two sources”: the user’s, handed over by running the compiler, and the license, granted for the statistics. “The compiler serves two masters,” he wrote, and it had no way to express which authority a given write was meant to use.

Access lists make the compiler’s authority ambient

Under access lists, a request for (SYSX)BILL carries only the file’s name. The check consults that file’s column against everything the compiler holds, the user’s row and its own, and the license supplies the right. Name (SYSX)STAT and the statistics are overwritten the same way; Hardy noted that file also needed protecting.

The first repair added a system call that let the compiler choose between its two authorities, which Hardy called switching hats. Some applications soon needed more than two. The rules for opening a file kept growing until, the last time Hardy wrote them down, they needed fourteen boolean operators. The trouble was structural: one mechanism told the compiler which file to write, and a separate one decided whether it could.

Miller, Yee, and Shapiro call this kind of authority ambient: authority “exercised, but not selected, by its user.” Their example is Unix file permissions. A caller of open() chooses no credential to present; the request succeeds or fails. Their Property D, No Ambient Authority, requires a subject to select the authority it exercises. An agent with its operator’s API keys in its environment holds ambient authority of the same kind. The text it reads is in the position of the user who supplied (SYSX)BILL: it needs only to name a resource, and the agent’s own authority does the rest.

A capability names and authorizes at once

Capabilities are older than the deputy. In 1966 Jack Dennis and Earl Van Horn described a computation as proceeding within a sphere of protection specified by a list of capabilities, a C-list. Each capability locates an object and indicates the actions the computation may perform on it. A process could create an inferior sphere and grant it some of its own capabilities, and a segment capability could be “passed down with restricted access authority.”

Hardy’s capability solution used that shape. The compiler would hold a capability to the statistics file and use it when writing statistics. For debugging output it would use a capability supplied by the user. “The capability both identifies the file and authorizes the compiler to write there.” No file name has to be resolved, and nothing has to decide which of the compiler’s authorities applies. Under capability lists, a request for (SYSX)BILL must carry a capability from the user’s row. The user holds none for the billing file, so it cannot supply one. The compiler’s license is still in the matrix, and no request from the user can reach it.

Miller, Yee, and Shapiro call this Property A, No Designation Without Authority. No access list system can have it, because an access list system names resources through a namespace kept apart from authority. A system with Property A also has Property D: a request that must carry its authority has selected that authority. The paper also states the limit. Removing ambient authority helps make it possible to avoid confused deputies; it does not guarantee that a deputy never errs.

A Chio grant is a signed capability

Chio grants authority to agents as capability tokens. Each token names the issuer that granted it, the subject that may exercise it, a scope of permitted calls, and a validity period, and the issuer signs all of it. A tool grant in the scope names a tool on a server, the operations allowed, and constraints on its parameters. Read against Lampson’s model, a grant is a row entry, and the tokens an agent holds make up its capability list.

A call presents one token with the request. The kernel’s evaluation input contains both the request and the capability token authorizing it. The kernel checks that this token covers this request for this caller and consults nothing else the agent holds. A token’s identifier alone carries none of its authority; the complete signed token arrives with the call.

In the Quickstart, you grant the fixer agent part of a fork of your project.

quickstart.tsquickstart.pysrc/main.rs · in main
const project = await Computer.open("quickstart");
const fixer = await project.agent("fixer");
const fork = await project.fork();
const authority = fork.grant({
  to: fixer,
  resources: { "/workspace": ["read", "write"], "/models/default": ["invoke"] },
  budget: USD(2),
  expiresIn: "20m",
});
console.log(authority.describe());
Output
grant      cap-01a8…31f2, signed by your kernel
to         fixer, did:chio:z6MkrT2…8wd
on         quickstart, fork 51c07ae
allows     /workspace         read, write
           /models/default    invoke
budget     USD 2.00, charged at your door
expires    in 20 minutes

The output reads as one row of the matrix. Your kernel is the issuer that signed it, fixer is the subject, allows lists the scope, and expires ends the validity period. When the fixer later tried to read /secrets/.env, its request carried this token, and the kernel refused the read because the scope does not cover that path.

A signed token is closer to a certificate than to Lampson’s protected pair, and Miller, Yee, and Shapiro consider that case too. A certificate names its resource in cleartext, so a name can be separated from the authority. They give two conditions under which deputies that cannot be confused remain feasible: certificates arrive in the context of a request, and subjects keep local identifiers for the certificates they hold. A Chio call meets the first by construction, since the token travels with the request it authorizes.

Lampson’s capabilities could not be forged because only the supervisor could create them. A Chio token is data that agents hold and present, so its issuer’s signature takes that role, and the kernel accepts only issuers it trusts. A signature prevents forgery but not copying. The token’s subject binds it to one caller: the kernel admits the token only for the caller it names, and a grant can also require a fresh proof of possession signed with that caller’s key.

Delegation narrows authority; revocation follows it

Delegation lets an application assign work to specialists with only the authority each assignment needs. Delegation follows Dennis and Van Horn’s grant with restricted access. When a grant permits delegation, its holder can pass part of that authority to another agent, and the kernel admits the child only if each of its grants is covered by a grant of the parent. Each step is recorded as a signed link in the child’s delegation chain, naming the delegator, the delegatee, and how the scope was narrowed.

The Irrevocability Myth holds that a capability, once handed out, cannot be taken back. The paper answers it with a forwarder the grantor can later disable, a method it traces to Redell in 1974. Chio keeps revocation as kernel state. Each admission checks the presented token and every link of its delegation chain against the revocation records, so once a parent is revoked, every token delegated from it is refused. Expiry needs no revocation: a token’s validity period is fixed when it is signed, and a delegated child cannot outlive its parent.

In the figure, grant p delegates to c, which passes part of its authority to d; s is a second child of p. Revoking c sets the hour from which c and d are refused, while p and s keep their windows. Each child’s window sits inside its parent’s, so none outlives the grant it came from.

Usability of four delegated grants over time, with c revoked024681012tp10iatexp
up(t)=1[0≤t<12]\htmlClass{role-call}{u_p}(t) = \htmlClass{cor-parent}{\mathbf{1}[0 \le t < 12]}
c←p10exp still 10
uc(t)=1[2≤t<10]⋅1[t<6]⏟ℓ=c\htmlClass{role-call}{u_c}(t) = \htmlClass{cor-parent}{\mathbf{1}[2 \le t < 10]} \cdot \underbrace{\htmlClass{role-deny}{\mathbf{1}[t < 6]}}_{\ell = c}
d←c10
ud(t)=1[4≤t<9]⋅1[t<6]⏟c∈chain(d)\htmlClass{role-call}{u_d}(t) = \htmlClass{cor-parent}{\mathbf{1}[4 \le t < 9]} \cdot \underbrace{\htmlClass{role-deny}{\mathbf{1}[t < 6]}}_{c \in \mathrm{chain}(d)}
s←p10
us(t)=1[3≤t<11]\htmlClass{role-call}{u_s}(t) = \htmlClass{cor-parent}{\mathbf{1}[3 \le t < 11]}
trev=6\htmlClass{role-deny}{t_{\mathrm{rev}} = 6}
ux(t)=1[iatx≤t<expx]⋅∏ℓ ∈ {x}∪chain(x)1[t<trev(ℓ)]\htmlClass{role-call}{u_x}(t) = \htmlClass{cor-parent}{\mathbf{1}[\mathrm{iat}_x \le t < \mathrm{exp}_x]} \cdot \prod_{\ell \,\in\, \{x\} \cup \mathrm{chain}(x)} \htmlClass{role-deny}{\mathbf{1}[t < t_{\mathrm{rev}}(\ell)]}
Usability of four delegated grants over time, with c revoked024681012tpiatexpc←pd←cs←p
trev=6\htmlClass{role-deny}{t_{\mathrm{rev}} = 6}

c is revoked at hour 6. c and its delegate d are unusable from then on; their signed windows are unchanged. p and s are unaffected.

ux(t)=1[iatx≤t<expx]⋅∏ℓ ∈ {x}∪chain(x)1[t<trev(ℓ)]up(t)=1[0≤t<12]uc(t)=1[2≤t<10]⋅1[t<6]⏟ℓ=cud(t)=1[4≤t<9]⋅1[t<6]⏟c∈chain(d)us(t)=1[3≤t<11]\small \begin{aligned} \htmlClass{role-call}{u_x}(t) &= \htmlClass{cor-parent}{\mathbf{1}[\mathrm{iat}_x \le t < \mathrm{exp}_x]} \\ &\quad \cdot \textstyle\prod_{\ell \,\in\, \{x\} \cup \mathrm{chain}(x)} \htmlClass{role-deny}{\mathbf{1}[t < t_{\mathrm{rev}}(\ell)]} \\[8pt] \htmlClass{role-call}{u_p}(t) &= \htmlClass{cor-parent}{\mathbf{1}[0 \le t < 12]} \\[4pt] \htmlClass{role-call}{u_c}(t) &= \htmlClass{cor-parent}{\mathbf{1}[2 \le t < 10]} \cdot \underbrace{\htmlClass{role-deny}{\mathbf{1}[t < 6]}}_{\ell = c} \\[4pt] \htmlClass{role-call}{u_d}(t) &= \htmlClass{cor-parent}{\mathbf{1}[4 \le t < 9]} \cdot \underbrace{\htmlClass{role-deny}{\mathbf{1}[t < 6]}}_{c \in \mathrm{chain}(d)} \\[4pt] \htmlClass{role-call}{u_s}(t) &= \htmlClass{cor-parent}{\mathbf{1}[3 \le t < 11]} \end{aligned}
In Rust · validation.rs:71813 lines
pub(crate) fn check_revocation(&self, cap: &CapabilityToken) -> Result<(), KernelError> {
    if self.with_revocation_store(|store| Ok(store.is_revoked(&cap.id)?))? {
        return Err(KernelError::CapabilityRevoked(cap.id.clone()));
    }
    for link in &cap.delegation_chain {
        if self.with_revocation_store(|store| Ok(store.is_revoked(&link.capability_id)?))? {
            return Err(KernelError::DelegationChainRevoked(
                link.capability_id.clone(),
            ));
        }
    }
    Ok(())
}
Illustrative hours. A grant is time-valid while issued_at ≤ now < expires_at, and the kernel rejects it when the grant or any link in its delegation chain is revoked. Revocation takes effect at a verifier that has observed it; the drawing makes no claim about propagation time.Revocation and current state

In Chio, each of these ideas has a concrete counterpart.

A capability list per subjectDennis and Van Horn, 1966; Lampson, 1971
The tokens an agent holds; each grant names a tool, its operations, and constraints on its parameters.
Capabilities cannot be forgedLampson, 1971
The issuer’s signature over the token, checked against the kernel’s trusted issuer keys.
Designation travels with authorityHardy, 1988; Property A
The request and the token it exercises are evaluated together.
Authority is selected by its userProperty D
Each call presents one token; the kernel checks that token alone.
Delegation with restricted accessDennis and Van Horn, 1966
Each child grant is covered by a parent grant, and each step is a signed link.
Access can be revokedRedell, 1974
Revocation records in the kernel, checked for the token and its whole chain.

Read more in Delegation and attenuation, which gives the full rules for narrowing and chain validation.

A grant still needs a boundary

A capability decides which authority a request exercises. It authorizes a call; it does not make the call correct, the tool’s answer true, or the task finished. Within that authority, names still designate. A grant that permits writes under repository/ admits a write to any file there, and text the agent reads can still steer it toward one of them. The kernel refuses what a grant excludes; what a grant includes is a design decision, and the principle of least privilege applies to every grant.

Three mechanisms narrow what that steering can reach. With task-scoped authority, the application mints a grant per subtask, sized to its paths, call count, and lifetime, so injected text reaches only what the current task holds. With information flow across calls, the kernel joins the label of each read into retained state and refuses a send to a destination whose clearance does not admit that state, however many permitted calls come between. Guards inspect the contents of a request, such as a path, a destination, or a command, and can refuse a call the token covers. They narrow the calls a grant permits and cannot widen the token.

Kernel admission protects a resource only when the agent has no other way to reach it. The host has to prevent access through other credentials or handles, such as an API key left in the agent’s environment. Authority the kernel never sees stays ambient.

Sources and further reading

  1. Programming Semantics for Multiprogrammed ComputationsJack B. Dennis and Earl C. Van Horn · Communications of the ACM 9(3), March 1966

    Introduces the C-list, the sphere of protection, and granting capabilities to an inferior sphere with restricted access. Presented in August 1965 at the ACM Conference on Programming Languages and Pragmatics; an open copy is the Project MAC report MAC-TR-23.

  2. ProtectionButler W. Lampson · Proceedings of the Fifth Princeton Conference on Information Sciences and Systems, 1971; reprinted in ACM Operating Systems Review 8(1), January 1974

    Defines the access matrix of domains and objects, and describes storing it as capabilities attached to domains or as access control lists attached to objects.

  3. The Confused Deputy (or why capabilities might have been invented)Norm Hardy · ACM Operating Systems Review 22(4), October 1988

    The Tymshare compiler, the billing file, the switch-hats repair, and the capability solution.

  4. Capability Myths DemolishedMark S. Miller, Ka-Ping Yee, and Jonathan Shapiro · Technical Report SRL2003-02, Systems Research Laboratory, Johns Hopkins University, 2003

    Compares access lists with three models of capabilities, defines Properties A through G, and answers the Equivalence, Confinement, and Irrevocability Myths.

  5. The Protection of Information in Computer SystemsJerome H. Saltzer and Michael D. Schroeder · 1975

    Section I sets out least privilege, complete mediation, and the other design principles for protection mechanisms.

Continue with the mechanism