Capabilities and grant
grant is a dynamic-context operator that attenuates authority for its body by
intersection. grant [exec: [...], fs: [...], net: ...] { body } narrows the
commands, filesystem paths, and network access available to body and
everything it calls. Authority is never amplified:
- a dimension omitted from a grant inherits the ambient authority;
- a dimension present can only narrow it;
- nested grants compose by meet;
- a deny is anti-monotonic — further layers can add denies but never reopen a denied region.
The axes are independent: each is a separate field whose absence is the meet
identity (None = inherit = ⊤, `Option<T>: Meet`).
- Restricting one axis does not restrict another.
grant [fs: [...]] { … }narrows the filesystem but leavesnet,exec,editor, andshellat the caller’s authority; a grant that means to confine network access must saynet: falseitself. nethas no in-process gate. ral has no network primitives, so anet: falseis enforced only by the OS sandbox — it fails closed where no backend exists.
This is a deliberate mental-model fact, recorded so omission is never mistaken for an implicit cross-axis deny — see reduced-authority-witness §B7.
Capability checks gate four dimensions:
- exec over a three-valued lattice (Allow / Subcommands / Deny) — more expressive than orthodox object-capability, since a base profile can veto a name a restrict file never mentions;
- fs by read/write path prefix with denies;
- net as allow/deny;
- audit as a record of check events.
Filesystem checks are alias-aware and resolve symlinks, so a directory scoped by
within [dir: ...] inside a grant cannot escape its policy. The bundled
coreutils (cp, mv, rm, mkdir) route through the same check chokepoint as
the structured primitives, closing the bypass.
The grant body evaluates locally. RAL-owned filesystem effects are checked in
process by check_fs_op before the syscall; each external or bundled child the
body spawns is confined under the effective projection — per-command Seatbelt
on macOS and bwrap on Linux, the projection’s own AppContainer on Windows
(projection-keyed-appcontainer).
A .ral profile and the inline
grant surface are symmetric: both decode through the same walker into one
frozen Capabilities, so configuration is the grant value written as source,
not a second schema. That walker resolves every sigil as it decodes —
the freeze boundary — so no unresolved form ever
reaches the stack or the IPC wire.
This is the boundary exarch reuses as its sandbox: each agent turn evaluates under a profile’s capabilities pushed onto this same stack.
Concessions. Three caveats are inherent to the design, not defects:
- Bare command names are ambient. A bare exec key like
gitresolves throughPATH, so it is not a strict object-capability; the mitigation is the base profile’s deny on shells together with thefsdeny on writingxdg:bin— the names that matter are pinned, the rest rest onPATHintegrity. - TOCTOU on path resolution. The resolver-form and bind-form checks share one source, so the OS profile and the in-process check cannot disagree, but symlink races inside the admitted set are a known surface, not a closed one.
- Confused deputy at the name layer. A prefix that is both
exec-admitted andfs-writable is an escape hatch — drop a binary, the next call admits it — so the invariant no prefix is both is a candidate load-time check.
See also syscalls-are-effects (a capability is permission over the effect set), scoping, control-operators, two-enforcers, system-c (the type-based pole of this calculus), access-control-algebra (the security-literature model this lattice instantiates — composition over a Belnap bilattice).
Realised in capability-enforcement.
Cite: RATIONALE
§“Scoped execution contexts”, §“Capability-checked dispatch”; docs/SPEC.md
§11.