The Authority Stack: From Sandboxed Code to Human Approval

Astrid’s authority stack answers a precise question: when a capsule asks to do something outside its WebAssembly memory, which boundary decides whether that effect may occur? The answer is not one master security switch. This chapter follows the request from a sandbox with no ambient access through manifest checks, network safety, signed capabilities, human policy, resource budgets, and audit. The goal is to make the division of labor clear enough that a reader can tell which protection is doing real work—and which claim would be too broad.

The first gate is a world with no ambient authority

A conventional process begins with whatever the operating system has already made available to it. It may inherit a working directory, open file descriptors, environment variables, network access, and the credentials of its parent. The program can then call into a large host interface and rely on the host to remember which assumptions were intended.

Astrid starts the capsule from the opposite position. WebAssembly code runs without direct syscalls, file descriptors, or host memory. The guest cannot reach a file or socket merely because the host process can. It must cross an Astrid host interface. These interfaces are typed and versioned, so the runtime can identify the operation rather than interpreting arbitrary guest behavior as a privileged request.

That does not mean every host call is automatically safe. It means every external effect has a place where policy can be applied. A capsule that wants to read a file uses a filesystem operation. A capsule that wants to connect to a service uses the network interface. A capsule that wants to start a native command uses the process path. The sandbox removes the unbounded starting authority; the later gates decide which bounded powers are available.

The manifest gate is the first of those later checks. It compares a sensitive operation with the capability declarations attached to the capsule. Filesystem paths are resolved according to Astrid’s virtual filesystem schemes, and parent-directory components are rejected before a simple prefix comparison can be tricked by a path such as a workspace followed by ... A wildcard read or write does not mean the entire host filesystem; it is confined to the canonical workspace root. The gate also handles declared network destinations, host processes, socket binds, and identity operations. An empty allow-list is a denial, not an invitation to guess.

This is why a capsule manifest should be read as a request and a review surface, not as an authority token. The declaration gives the runtime a stable description of intended access. The host call still has to match it at the moment the operation is attempted. That distinction matters for paths whose meaning can change, for principal-specific home mounts, and for any operation where a broad declaration would otherwise become an accidental escape hatch.

It also explains why Astrid treats different capabilities as different kinds of power. Reading a file, writing outside a sandbox, spawning a process, connecting to a network host, publishing on behalf of a principal, and changing an identity link are not interchangeable just because they all appear under one [capabilities] table. The table organizes requests; the effect-specific implementation decides how each request is checked.

Network access requires permission and destination safety

Network authorization illustrates the stack especially well because “this capsule may use the network” is not a sufficient security statement. Astrid separates the question of whether the capsule may name a destination from the question of whether the destination is safe to contact.

For outbound TCP, the manifest gate first matches the literal host and port against the capsule’s net_connect allow-list. An entry can name one host and one port or a constrained port wildcard. If there is no matching declaration, the request stops before a connection is attempted. This is a policy check about the capsule’s intended scope.

Only after that check does the host own DNS resolution and inspect the resulting addresses. Astrid’s HTTP and network paths reject private, loopback, link-local, multicast, and unspecified addresses through an SSRF airlock. The capsule cannot supply its own resolver, replace a hostname with a pre-resolved address, choose a proxy, or relax certificate verification. For HTTP redirects, every hop goes through the airlock again, and sensitive headers are stripped when a redirect crosses origins.

The order closes a common ambiguity. A public-looking hostname can resolve to an internal address, and an initially safe URL can redirect to a private service. Conversely, an operator may intentionally run a local model provider and need to permit a specific local endpoint. Astrid handles that as an operator-side exemption for named host and port pairs; the capsule does not get to widen the exception from inside its own manifest. Permission and destination safety remain two separate decisions.

The same pattern appears in inbound networking. A capsule-hosted TCP listener is subject to a declared bind capability and is confined to loopback in the current host path. This is a narrower posture than exposing arbitrary application servers to a public interface. It keeps the first application-facing listener behind an explicit operator decision instead of allowing any capsule that can describe a server to become a network service.

flowchart TB Action["Capsule requests an effect"] --> Sandbox["WebAssembly sandbox"] Sandbox --> Manifest["Manifest resource gate"] Manifest --> IPC["IPC publish and subscribe ACL"] IPC --> Token["Principal scoped signed token"] Token --> Approval["Approval allowance and budget"] Approval --> OS["Host OS sandbox"] OS --> Effect["External effect"] Effect --> Audit["Signed audit record"] Sandbox -.-> Stop["No effect on denial"] Manifest -.-> Stop IPC -.-> Stop Token -.-> Stop Approval -.-> Stop OS -.-> Stop

The stack should not be read as a fixed universal sequence for every operation. A filesystem read may need the sandbox, manifest, principal, and audit boundaries without requiring a human prompt each time. A sensitive capability grant may involve approval and signed-token creation. A native subprocess additionally needs an operating-system sandbox. The diagram shows independent responsibilities, not a claim that every request traverses every box in exactly the same way.

A token is not a consent dialog

Astrid’s capability tokens provide machine-checkable authority after a decision has been made. A token is signed with the runtime’s Ed25519 key, bound to a principal, associated with an audit entry, and scoped to a resource pattern and permission. It may be limited to a session or stored persistently, and it may carry an expiration. The agent cannot mint one by constructing a similar-looking object or by asking the model to claim that it exists.

The resource pattern is part of the boundary. It can identify one MCP tool, a constrained group of tools, or a directory-shaped file scope. Path traversal is rejected when a pattern is created and again when it is matched. A token that authorizes one resource pattern is not a general credential that can be repurposed for another. In Astrid’s vocabulary, possession of a signed proof is meaningful only within the scope that the proof names.

The store reinforces that distinction over time. Persistent tokens are checked for expiration and signature validity when they are read or used. Revocation is written to durable storage before the in-memory view is updated, so a crash between the two does not resurrect a revoked token. Single-use consumption is marked atomically under a write lock and persists across restart. These details address different failure modes: tampering, stale authority, replay, and inconsistent recovery.

Human approval is a neighboring layer, not a synonym for the token. The approval system classifies sensitive actions such as file deletion, command execution, network requests, capability grants, and MCP calls. An operator can choose Allow Once, Allow Session, Allow Workspace, Allow Always, or Deny. Scoped allowances reduce repeated prompts, but they remain tied to a principal and, where applicable, a workspace. A workspace allowance granted in one project cannot silently apply in another.

“Allow Always” makes the distinction visible. The approval component records the decision, but the kernel owns the signing key and capability store. The kernel is the component that mints the persistent capability token. Consent is a policy input; the token is the runtime’s signed authorization result. If no human handler is available, the action becomes a deferred resolution and enters a queue. It is not silently treated as approved because the system would prefer progress.

The current approval design also states what it does not yet provide. General cryptographic delegation and attenuated per-action child tokens are planned, not shipped. That matters for systems in which one principal wants to give another a narrower authority. The presence of token types or approval abstractions does not prove that a complete delegation tree already exists.

Budgets and audit answer different questions

A capability can be valid and still be too expensive or too risky to exercise without a resource limit. Astrid’s approval layer therefore includes spending budgets at both the action and session/workspace levels. The check and reservation happen atomically, so two concurrent callers cannot both observe unused budget and then overspend it. If a cancelled operation has reserved funds, a drop guard can return them.

Budgets answer, “May this operation consume this much of a constrained resource?” They do not answer whether the caller is authorized to touch a particular file or host. A request must satisfy both kinds of condition. The same separation appears in the runtime’s execution controls: a capsule can be permitted to call an interface while still being bounded by memory, concurrency, throughput, or deadline policy.

Audit answers a third question: “What did Astrid record about the decision and result?” The current audit subsystem stores signed entries linked by BLAKE3 hashes. Each entry carries the action, authorization proof, and outcome. Tool arguments are represented by hashes rather than raw content, allowing the log to prove that a particular argument set was involved without copying secrets or user data into the chain. Capability issuance, revocation, approvals, denials, tool calls, file operations, and lifecycle events can therefore be connected to an accountable history.

The audit path is not merely an after-the-fact debug log. Where the security path requires an audit write, it refuses to execute the action if that write fails. Host implementations also report allowed, failed, and denied filesystem or network operations to the signed sink when it is installed. That makes the record part of the effect boundary. Yet the record still does not grant authority. A signed entry saying that an action was approved is evidence of a transition, not a reusable permission to repeat an unrelated effect.

This division prevents another common mistake. Logging every request does not secure an unbounded request path, and a valid token does not tell an operator whether a model’s choice was wise. Authorization, resource accounting, and evidence reinforce one another without becoming one object.

Why Astrid avoids a master gate

A single interceptor sounds simpler: send every action through one policy engine and let it decide. Astrid’s source rejects that shape because it would create one large, hard-to-audit mechanism responsible for sandboxing, path resolution, network safety, IPC routing, credentials, approvals, budgets, and logging. Separating the gates allows each one to be enforced near the effect it controls. A path traversal check belongs beside path resolution; an SSRF check belongs beside DNS and connection setup; principal routing belongs in event delivery; signing belongs where the runtime key is held.

The cost is real. Operators and reviewers must understand several boundaries and their interactions. A capability declaration may be necessary for admission while not being the final grant. A network allow-list may pass while the resolved address is still rejected. A contract may define a typed operation whose host implementation is not complete. The current ABI is incremental: basic filesystem operations are live while several handle, rename, link, and recursive operations remain stubbed; richer HTTP upload, trailer, stream-half, and readiness paths are incomplete; and UDP is denied in the current network host. A typed unsupported result is an honest boundary, not evidence that the whole contract is available.

That honesty is part of the security model. Astrid’s goal is not to make every capsule automatically trustworthy or every action frictionless. It is to make the authority question visible at the point where an effect would otherwise inherit the host’s power. The next test is therefore not another list of gates. It is a complete request path: whether identity, authority, and evidence remain aligned as one action crosses uplinks, IPC routes, shared capsule instances, and principal-specific state.

References