Security and Threat Model
This page states what the system protects, by what mechanism, and — as plainly — which attacks are outside its scope. A security property you rely on should appear here.
In one sentence: what a build can do is decided by what its boot cart’s entry is allowed to hold. Everything below is the detail of that.
What the system defends
Authority. Reaching the outside world requires holding an endowment for it. Code that was never granted an endowment has the ordinary language available to it and nothing more. Authority arrives through actor construction or introduction, and the set an executable may hold is decided when it is built.
Entry. A shop can only reach what it has mounted. Mounting is not an endowment — the
clerk simply mounts, and what it can mount is which mount modules it carries: a directory,
its own .pit store, a git repository, an archive.
So the question to ask of a shipped artifact is “what can its clerk reach?” A clerk with a git module can pull from a repository; one with an http fetcher can pull an archive; a clerk carrying neither answers only from what it already has. That is the same rule that governs everything else — what a build can do is what it carries — rather than a second mechanism beside it. The real gating sits underneath, where the endowments already are: a directory mount claims the target’s file endowment, a git mount reaches the network through an actor that claims a socket. Deny those and the clerk cannot mount those things, with no new machinery.
Integrity. Every artifact is named by the hash of its bytes, so what arrives is verified against what was named.
Package privacy. internal/ is private to the package holding it, structurally: the
import written inside that package is bare and package-relative, so it always means the
caller’s own, and the qualified <other>::internal/x is refused. There is no spelling of
another package’s internal that could have been permitted, which is why this needs no check
at run time.
Isolation of state. Each actor owns its heap. A value sent to another actor is encoded, copied, and decoded, so both sides hold their own copy and neither can reach into the other’s memory.
What the shop’s lists are not
The shop holds a handful of lists of package names — endowments, endowments_whole,
c_native, fallback, bin — and only some of them decide anything about authority.
endowments and endowments_whole say which packages may provide endowments and which are
linked whole; c_native says which may compile C. Those are real gates. fallback and
bin are naming conveniences: they answer what an unqualified name means and where a start
looks for a tool.
c_native gates by naming when populated: a shop then compiles C from the packages it
lists and from no others. Its current empty spelling means all packages may compile C,
which is the development default; a hardened shop names its allowed packages explicitly.
fallback is not a permission boundary. A package left off it is not thereby denied to
anyone — shoplib is on no fallback list and any program may use shoplib::deflate. It
just has to write the full locator and say where it came from. Reading the fallback list as
a security control would be reading the wrong list, and the ones that do gate — endowments
and C — are named above.
What isolation means, precisely
Actor isolation is a memory and data-race property. Two actors cannot corrupt each other’s state, race on a shared value, or hold a reference into each other’s heap.
It is not a timing property. Actors share a machine, and several things are observable across that boundary by an actor that measures carefully:
- Shared read-only pages. Actors running the same code map the same images. Whether a page is already resident is observable through access time.
- A shared scheduler. Turn scheduling, queue depth, and how long another actor runs are all visible in wall-clock time.
- Per-actor collection pauses. Collection pauses the actor being collected, and its timing reflects that actor’s allocation behaviour.
- A deduplicating store. Content addressing means storing something already present is cheaper than storing something new, which is observable to whoever can measure the store.
These are accepted consequences of the design. An actor handling a secret on a machine that also runs untrusted code should be treated as sharing a timing channel with it, and any defence against that belongs in the program — constant-time comparison for secrets, and not placing an adversary’s code in the same runtime as material that must stay confidential.
The C floor
C is the trusted computing base. A native module runs with the authority of the process: it calls syscalls directly and reads memory directly, answering to the operating system alone. Confinement is a property of pit code and Mach bytecode running above that floor.
This is why C is a distinct decision rather than one endowment among many, and why a
deny_c policy checks the transitive closure: a program whose dependency graph is free of
native code has a meaningfully different exposure from one where a single deep dependency
pulls C in. Every native claim is traced back to the unit that imported it, and that path is
reported when policy is evaluated.
Native code in a shipped artifact
A player artifact omits the compiler and the general-purpose dynamic loader. It may contain one fixed native image and entry, when its sealed realization selects the native execution form and the device’s policy accepted that exact image and its target.
The distinction that matters: generating or loading native code on demand is a standing capability, while running one specific pre-authorized image is a decision already made.
The gate is compilation, not start
Policy answers one question, when a program is built: may this be built? If it compiled here, it runs. Nothing is checked when an actor starts.
That is safe because a shop can only build from what it has mounted, so the mount list is the boundary. It also means policy is not retroactive — a realization already in the cache starts under the decision it was built with, and rebuilding is what applies a new one. See Capabilities.
The boot cart is trusted by ruling. A runtime must start somewhere, and the medium it starts from cannot be authorized by code that has not run yet; the C floor checks only that the cart is interpretable on this binary.
Co-residence grants no reference. Two images sharing a mapping, a pool, a cart, or a process are merely near each other. What code can reach is decided by the realization’s binding rows, resolved during setup. Confined code cannot manufacture an image reference out of ordinary data.
An actor token cannot be forged. A token that names another actor is created only by
actor creation, by delivery, or by an $inspect snapshot. The public actor module can read a
token’s display id, but it cannot construct a token, extract its private descriptor, or
obtain the runtime’s actor-key object. So the ability to name an actor, like the ability
to name an image, comes only from having been handed the reference — not from holding data
that looks like one.
Fairness
Turn scheduling aims to keep every actor progressing: a turn that exceeds its budget is suspended and resumed rather than allowed to run indefinitely. This is a liveness property, not a security boundary — an actor that wants to consume its share of the machine can, and a program that must bound an untrusted workload should give it limits explicitly rather than rely on the scheduler to be adversarial.
Where each mechanism applies
| Concern | Mechanism |
|---|---|
| What may enter at all | Mounts — and which mount modules the clerk carries |
| Which code may act on the world | Endowments, decided per closure at build |
| Which bytes am I running | Content hashes, verified on read |
| Who published this | Signed manifests, authenticated to an issuing root |
| What may reach what at run time | Realization binding rows |
| Whether a program can start code it did not name | The guest-code flag: $start on a computed name |
| Freedom from native code | deny_c over the transitive claim closure |
| Confidentiality against timing | Out of scope — handle in the program |