Proposal: Userspace Authority Broker and Init-Owned Shutdown
Problem
The current shell authentication path uses a kernel AuthorityBroker
capability. The shell starts with anonymous authority, calls the broker for an
anonymous bundle, then calls it again after password login for an operator
bundle. That works, but it places session policy, launcher policy, and shell
bundle construction inside the kernel.
That is the wrong long-term boundary. The kernel should provide primitive mechanisms: process creation, capability transfer, endpoint rendezvous, memory, terminal I/O, and process lifecycle. Login policy, operator profiles, service allowlists, and shell bundles are userspace policy and should be owned by init or an init-managed service.
Shutdown exposes the same issue. A shutdown command should not be a raw kernel poweroff capability passed to the shell. The natural capOS behavior is that the kernel halts when init and all remaining processes are gone. Shutdown policy should therefore be implemented as init-owned lifecycle orchestration: stop services, wait for them, release authorities, and then let init exit.
Current State
Implemented pieces today:
- The kernel starts one init process from the boot manifest.
- Init reads
BootPackage, validates the init-owned service graph, spawns services, records exported capabilities, and waits for children. - The shell receives a terminal and anonymous authority, then upgrades after password login.
AuthorityBrokeris a kernel capability implemented inkernel/src/cap/authority_broker.rs.ProcessHandlesupportswait, but not termination.- There is no init-owned lifecycle control capability yet.
The consequence is a mixed trust boundary: init owns service graph execution, but the kernel still owns shell session bundle policy.
Goals
- Move authority-broker policy out of the kernel.
- Make init, or an init-managed broker service, responsible for authenticated shell bundles.
- Keep shell unauthenticated authority minimal.
- Make shutdown an init-owned control operation, not a direct kernel shutdown cap.
- Preserve the kernel rule that the system halts naturally when the last process exits.
- Keep all authority transfer explicit and inspectable through capabilities.
Non-Goals
- Do not add ambient service names or a global service registry.
- Do not give shell raw
ProcessSpawnerbefore authentication. - Do not add a kernel “kill everything” syscall.
- Do not introduce restart policy, persistence, or crash recovery in this proposal.
- Do not solve multi-user policy; this proposal only moves the current local operator/anonymous policy out of the kernel.
Proposed Architecture
Init starts two policy-facing services:
authority-broker: userspace service that owns shell bundle policy.shell: interactive shell, initially anonymous.
Init also keeps a private lifecycle table for services it spawned. That table contains process handles, service names, restart policy state, and shutdown ordering metadata. Init does not expose the raw table. It exposes attenuated control capabilities.
Capability Graph
flowchart TD
Kernel[Kernel primitives] --> Init[init]
Kernel --> Terminal[TerminalSession]
Kernel --> Spawner[ProcessSpawner]
Kernel --> Sessions[SessionManager]
Kernel --> Audit[AuditLog]
Kernel --> Creds[CredentialStore]
Init --> Broker[authority-broker service]
Init --> Shell[shell]
Init --> Services[managed services]
Broker --> ShellAnon[anonymous shell bundle]
Broker --> ShellOp[operator shell bundle after login]
Init --> Shutdown[init-owned ShutdownControl]
Broker --> ShellOp
Shutdown --> ShellOp
The shell talks to the broker over an endpoint. Before login, the broker returns an anonymous bundle with no service-management authority. After login, the broker returns an operator bundle that includes a restricted launcher and, if policy allows, an init-owned shutdown control capability.
Interfaces
The exact schema can evolve, but the minimum shape should separate broker policy from init lifecycle control.
interface AuthorityBroker {
shellBundle @0 (sessionCapId :UInt32, profile :Text)
-> (launcherIndex :UInt16,
sessionIndex :UInt16,
hasShutdownControl :Bool,
shutdownControlIndex :UInt16);
}
interface ShutdownControl {
shutdown @0 () -> ();
}
interface ProcessHandle {
wait @0 () -> (exitCode :Int64);
terminate @1 (reason :Text) -> ();
}
AuthorityBroker can be implemented as a userspace service using endpoint IPC
instead of a kernel cap. ShutdownControl is produced by init, not by the
kernel. ProcessHandle.terminate is a primitive lifecycle operation, but the
kernel only targets one process handle; init owns the policy that decides which
handles to terminate and in what order.
Shutdown Flow
- Shell starts anonymous and does not hold
ShutdownControl. - User runs
login. - Shell obtains an operator bundle from the userspace broker.
- If policy allows, the bundle includes
ShutdownControl. - User runs
shutdown. - Shell invokes
ShutdownControl.shutdown. - Init stops accepting new service operations.
- Init asks managed services to terminate in dependency order.
- Init waits for all service handles to exit.
- Init releases remaining capabilities and exits.
- The kernel observes no remaining runnable user processes and halts through the existing last-process-exited path.
This keeps final machine shutdown in the kernel, but keeps shutdown authority and orchestration in userspace.
Broker Migration Plan
Phase 1: Define Userspace Interfaces
- Add schema for endpoint-served
AuthorityBrokerandShutdownControl. - Keep the kernel broker temporarily for compatibility.
- Add runtime clients for both interfaces.
- Add QEMU proof that an anonymous shell cannot call shutdown.
Phase 2: Init-Owned Shutdown
- Extend init with a lifecycle table for spawned services.
- Add a private init service endpoint for shutdown requests.
- Add
ProcessHandle.terminateor equivalent single-process lifecycle primitive. - Make init terminate and wait for managed services before exiting.
- Add QEMU proof that
shutdownafter login exits QEMU cleanly.
Phase 3: Userspace Authority Broker
- Implement
authority-brokeras a userspace service. - Grant it only the policy inputs and capabilities needed to mint shell bundles.
- Have shell obtain anonymous and operator bundles from that service.
- Keep shell without raw
ProcessSpawner; it should receive only restricted launch authority. - Add QEMU proof that pre-login shell cannot spawn privileged services and post-login shell can run the expected demo commands.
Phase 4: Retire Kernel Broker
- Remove
kernel/src/cap/authority_broker.rs. - Remove
KernelCapSource::AuthorityBroker. - Remove kernel-side broker bundle construction and tests.
- Update docs so the kernel boundary is again primitive-only.
Security Properties
- Shell starts without shutdown authority.
- Shutdown authority is granted only after an authenticated session is proven.
- The broker cannot invent kernel powers; it can only delegate capabilities it received from init.
- Init remains the root of service lifecycle policy.
- Kernel process termination remains per-handle, not global.
- Service shutdown is auditable because it flows through init and named process handles.
Open Questions
- Should
ShutdownControl.shutdownbe one-way, or should it return staged progress events before init exits? - Should services receive a graceful
StdIOclose, a typed lifecycle signal, or onlyProcessHandle.terminate? - Should the broker be a separate process, or should init directly expose the broker endpoint until service supervision is stronger?
- How should restart policies interact with shutdown mode?
- Should shutdown require a fresh authentication event, or is the current operator session sufficient?
Verification
Required QEMU proofs:
- Anonymous shell:
shutdownis denied or unavailable. - Operator shell: login returns shutdown authority.
- Shutdown command causes init to terminate managed services and exit.
- QEMU exits through the existing last-process halt path.
- Existing adventure/chat demo still works before shutdown.
Host tests should cover:
- Broker policy decisions for anonymous vs operator profiles.
- Init shutdown ordering over a synthetic lifecycle table.
- Manifest validation rejecting direct shell access to privileged lifecycle primitives before login.