Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Local Users, Storage, And Policy Backlog

Design and task decomposition for manifest-seeded and disk-backed local user management. This work belongs to the User Identity, Sessions, And Policy track and depends on capability-native storage reaching at least a RAM-backed Store/Namespace proof before durable account mutation is meaningful.

Grounding

This decomposition is grounded in the current capability, identity, manifest, storage, and authority-broker documents:

  • docs/capability-model.md
  • docs/architecture/manifest-startup.md
  • docs/proposals/user-identity-and-policy-proposal.md
  • docs/proposals/userspace-authority-broker-proposal.md
  • docs/proposals/storage-and-naming-proposal.md
  • docs/proposals/oidc-and-oauth2-proposal.md
  • docs/proposals/cryptography-and-key-management-proposal.md
  • docs/security/trust-boundaries.md
  • docs/roadmap.md
  • REVIEW_FINDINGS.md

Relevant prior-art research files checked from the actual docs/research/ tree are:

  • docs/research/eros-capros-coyotos.md
  • docs/research/genode.md
  • docs/research/plan9-inferno.md
  • docs/research/sel4.md
  • docs/research/zircon.md

Design Position

user remains a human-facing policy term, not a kernel subject. The kernel should not learn uid, role, group, tenant, or external-claim semantics. Account records, roles, attributes, labels, profiles, and federation claims decide which capabilities a trusted broker may mint or delegate; they are never independent authorization tokens.

Terms

The identity vocabulary should be precise enough that later schemas do not accidentally recreate Unix users.

  • Principal: the stable identity key used by auth, policy, audit, and ownership metadata. A principal can represent a human, service, guest, anonymous caller, deployment, or pseudonymous external subject.
  • User: a user-facing category for a principal/session that represents a human or human-adjacent actor. It is not a kernel object, not a UID, and not an authority source. In design text, prefer principal, session, or account when one of those is meant.
  • Account: a durable local record for a principal. It binds credential references, status, roles, attributes, storage roots, quotas, and default policy/resource profile names. Some principals have no account: anonymous callers, some guests, and some one-shot external sessions.
  • Profile: a named policy template selected by account data, manifest seed data, external admission rules, or service configuration. A profile contains no authority by itself. It selects bundle fragments, quotas, ABAC defaults, labels, and approval eligibility that the broker may use when minting actual capabilities. Use policy profile or resource profile when the narrower meaning is intended; use plain profile only for prose that intentionally covers both.
  • Policy profile: the authorization template: roles, ABAC defaults, allowed bundle fragments, approval paths, label defaults, and external admission constraints.
  • Resource profile: the quota and default-resource template: storage, memory, CPU share, process/thread/cap limits, IPC limits, log volume, network posture, and launcher posture.
  • Session: a live authenticated, guest, anonymous, or external context. It has freshness, expiry, source, auth strength, audit identity, and a selected policy profile plus resource profile. A session receives capabilities; an account does not run.
  • Role: an RBAC label attached to accounts or sessions. It is used by a broker to decide eligibility for bundle fragments or leased grants. It is not authority after the corresponding cap is absent.
  • Workload: a process or supervised subtree launched with a concrete CapSet. It may carry session/account metadata for audit and policy, but it runs with capabilities, not as a user.

There are three account and admission sources:

  • Manifest seed accounts: immutable or append-only bootstrap records in the boot package. These create first local operators, recovery identities, service identities, emergency guest policy, and initial policy bundles.
  • Local account store: mutable disk-backed account, credential, role, attribute, quota, and resource-profile records. This is the normal source for durable local accounts after storage is available.
  • External identity admission and bindings: OIDC, passkey, cloud, deployment, or certificate-backed principals mapped to system policy profiles or existing local accounts. External claims are inputs to ABAC and account binding; they do not grant local authority by themselves.

Manifest seed data should be sufficient to boot, recover, unlock storage, and create or repair the local account store. It should not become a permanent mutable account database. Disk state should be authoritative for ordinary accounts after the account store is initialized, with explicit versioning, rollback detection, and recovery import/export.

Account Model

The first durable data model should be small and cap-shaped:

struct AccountRecord {
  recordId @0 :Data;
  principalId @1 :Data;
  kind @2 :PrincipalKind;
  displayName @3 :Text;
  status @4 :AccountStatus;
  credentialRefs @5 :List(Data);
  roles @6 :List(Text);
  attributes @7 :List(Attribute);
  resourceProfile @8 :ProfileRef;
  policyProfile @9 :ProfileRef;
  homeRoot @10 :StorageRootRef;
  createdAtMs @11 :UInt64;
  updatedAtMs @12 :UInt64;
  schemaVersion @13 :UInt32;
  storeEpoch @14 :UInt64;
  recordVersion @15 :UInt64;
  policyEpoch @16 :UInt64;
  previousHash @17 :Data;
  contentHash @18 :Data;
}

struct ProfileRef {
  profileId @0 :Data;
  versionId @1 :Data;
  epoch @2 :UInt64;
}

struct StorageRootRef {
  storageServiceId @0 :Data;
  rootObjectId @1 :Data;
  rootKind @2 :StorageRootKind;
  schemaVersion @3 :UInt32;
  rootVersion @4 :Data;
}

enum StorageRootKind {
  namespace @0;
}

enum AccountStatus {
  active @0;
  disabled @1;
  locked @2;
  recoveryOnly @3;
}

struct ResourceProfile {
  profileId @0 :Data;
  versionId @1 :Data;
  epoch @2 :UInt64;
  homeQuotaBytes @3 :UInt64;
  tempQuotaBytes @4 :UInt64;
  processLimit @5 :UInt32;
  threadLimit @6 :UInt32;
  capLimit @7 :UInt32;
  memoryCommitLimitBytes @8 :UInt64;
  frameGrantLimitPages @9 :UInt64;
  endpointQueueLimit @10 :UInt32;
  inFlightCallLimit @11 :UInt32;
  pendingIpcSubmissionLimit @12 :UInt32;
  ringScratchLimitBytes @13 :UInt64;
  logQuotaBytesPerWindow @14 :UInt64;
  networkProfile @15 :Text;
  cpuBudgetUsPerWindow @16 :UInt64;
  cpuWindowUs @17 :UInt64;
  timerWaiterLimit @18 :UInt32;
  launcherProfile @19 :Text;
}

homeRoot is a persistent reference that the account/storage broker resolves into a live Namespace capability at session-bundle time. It is not itself a capability, not a path, and not a raw Directory. The first implementation should use capability-native Namespace as the account home source of truth; Directory is a compatibility projection returned by a filesystem or POSIX adapter when a workload needs file-like APIs. storageServiceId names the trusted storage service instance, rootObjectId names the stored namespace root within that service, rootKind keeps the record extensible while v1 only accepts namespace, and schemaVersion lets future storage-root encodings fail closed.

External identities should bind to accounts through explicit records:

struct ExternalIdentityBinding {
  bindingId @0 :Data;
  provider @1 :Text;        # oidc issuer, cloud provider, cert authority
  subjectHash @2 :Data;     # hash(provider kind, issuer, tenant, subject)
  principalId @3 :Data;     # local or pseudonymous principal
  tenant @4 :Text;
  acceptedClaims @5 :List(Text);
  expiresAtMs @6 :UInt64;
  policyProfile @7 :ProfileRef;
  resourceProfile @8 :ProfileRef;
  schemaVersion @9 :UInt32;
  storeEpoch @10 :UInt64;
  recordVersion @11 :UInt64;
  policyEpoch @12 :UInt64;
  previousHash @13 :Data;
  contentHash @14 :Data;
}

Claims such as OIDC groups, acr, amr, tenant IDs, device posture, source network, and token age are ABAC inputs. They must be normalized before use and discarded or refreshed when stale.

Gate 0 schema-plan decisions are recorded in docs/proposals/user-identity-and-policy-proposal.md: durable account records belong in a separate account-store schema/service slice, while UserSession keeps only session/profile summaries and opaque broker result handles. Durable joins use fixed opaque binary IDs rather than display names. Disk-backed records require schema versions, monotonic store and record versions, policy epochs, previous hashes, content hashes, and compare-and-set mutation preconditions. Recovery import from manifest seed data is additive and conservative: preserve validated IDs, disable stale bindings, avoid automatic authority widening, and emit audit records or stay in bounded emergency mode.

Default Session Resources

The default resource bundle for a session backed by a local account should be useful but narrow:

  • terminal: the foreground TerminalSession for this login.
  • session: read-only UserSession or SessionContext for audit identity, auth freshness, and display.
  • home: read-write Namespace or Directory scoped to the user’s home root.
  • config: read-write user config namespace, separated from application data.
  • cache: bounded user cache namespace with eviction policy.
  • tmp: bounded per-session temporary namespace deleted at logout or expiry.
  • logs: read-only view of this user’s own session logs plus a write-only application log sink.
  • launcher: restricted launcher for approved applications and demos.
  • approval: client for requesting broker-reviewed grants.
  • credentials: self-service credential update interface that never exposes verifier material.
  • keyring: scoped secret unwrap/use interface for this user’s data classes, not raw global key export.
  • status: read-only system status with sensitive device and security state redacted unless a role grants more.

No entity should receive implicitly unbounded consumption of limited system resources. Every default bundle needs an associated ResourceProfile covering at least memory, CPU share, storage bytes, process/thread/cap counts, pending IPC submissions, endpoint queue state, in-flight calls, network posture, and log volume. This backlog can name the requirement, but the general resource-accounting model should be a separate design proposal because it applies to users, services, guests, anonymous callers, drivers, storage, network stacks, and test workloads.

Default guest resources should be explicitly weaker: terminal, session, ephemeral tmp/home, restricted launcher, self-contained logs, tight memory and CPU quotas, and low process/thread/cap limits. Guests should not receive durable home storage, persistent credentials, network listeners, service management, or administrative approval paths unless policy names that exception.

Anonymous remote sessions should receive almost nothing: a login/account- creation path, optional read-only documentation/help caps, the minimum auxiliary state needed for the protocol, tight memory quota, low CPU share, short expiry, and no default shell, home, launcher, network listener, durable namespace, or broad service cap. Authentication or explicit account creation is the normal path from anonymous to durable authority.

External sessions should be admitted only by explicit configuration. The configuration either maps the external subject to an existing local account, or permits auto-creation of a pseudonymous/tenant-scoped account with a named policy profile and resource profile. A federated login may receive a durable namespace only when an ExternalIdentityBinding or auto-creation rule maps it to a local principal and the provider assertion is fresh enough for that profile.

Service accounts should receive no terminal and no interactive bundle. Their default resources are measured-binary launch authority, service-specific state namespace, log writer, bounded network or IPC caps, and supervisor-approved credential/keyring usage.

Roles

Roles are bundle selectors and approval eligibility, not authority by themselves. The first role set should be conservative:

  • guest: interactive temporary session with no durable storage.
  • local-user: normal local account; owns a home/config/cache profile.
  • developer: may launch development tools, read own build logs, and request scoped test network/client caps.
  • storage-admin: may inspect and repair selected storage services and quota records, but cannot read user homes or unwrap user keys by default.
  • net-operator: may request leased network-stack and listener management caps for named services.
  • service-operator: may restart or inspect named services through init-owned supervisor caps.
  • security-auditor: may read selected audit/security logs but not user private content.
  • account-admin: may create, disable, lock, and bind accounts; cannot read credential verifier material or user homes.
  • policy-admin: may update role, ABAC, and label policy after fresh strong authentication; cannot directly mint end-resource caps.
  • recovery-operator: manifest-seeded break-glass identity with local-console and storage-recovery constraints.
  • system-updater: may update trusted boot packages, policy schema, and service packages through measured update workflows.
  • service-account: non-interactive role profile constrained by measured binary, supervisor, and service name.

External groups should not be imported as roles automatically. A binding rule may map a provider group to a local role only for a named tenant/provider, with expiry, audit, and conflict handling.

Permission Rules

Initial rules should be expressed in terms of cap bundles and wrappers:

  • No session receives raw ProcessSpawner, raw FrameAllocator, broad DeviceManager, or unrestricted StoreAdmin by default.
  • home grants are owner-scoped. Sharing returns attenuated sub-namespace or file capabilities through a broker and records the grant in audit state.
  • config writes are allowed for user-owned preferences. Security-relevant changes such as credential policy, role bindings, and external identity bindings require broker approval and fresh authentication.
  • Credential services expose verify, enroll, rotate, disable, and recovery operations. They never return password hashes, PHC verifier blobs, private passkey material, or raw MFA secrets to ordinary sessions.
  • Keyring caps expose use or unwrap operations scoped to a data class and session. Exportable key material requires a separate explicit backup grant.
  • Storage-admin repair caps should operate on volume metadata, namespace integrity, quota ledgers, and snapshots. They should not imply decrypt/read authority for user content.
  • Network listener authority is opt-in. Normal users may receive client network caps by profile; listeners require net-operator, service policy, or an application-specific grant.
  • Service management is named and leased. service-operator grants must name the service or service group and should not include arbitrary spawn authority.
  • External identity sessions are denied local administrative roles unless a local binding explicitly allows that provider, tenant, principal, role, auth-strength, and expiry.
  • Disabled or locked accounts may authenticate only to recovery flows that are explicitly allowed by account state.
  • Role changes, external binding changes, policy changes, and recovery actions emit audit events with principal, session, source, previous value, new value, policy version, and approval grant.

RBAC And ABAC Split

RBAC should answer coarse questions:

  • Which default bundle profile does this session receive?
  • Which approval requests is this session eligible to make?
  • Which service, storage, or account-management roles can appear in a grant?

ABAC should narrow or deny based on context:

  • auth strength and authentication age,
  • local console vs remote terminal vs browser companion,
  • external provider, tenant, normalized claims, and token freshness,
  • session age, account state, recovery mode, and boot mode,
  • requested capability interface, method class, target object owner, target sensitivity/integrity label, quota impact, and lease duration,
  • service package measurement and supervisor identity for service accounts.

The broker should return capabilities, wrapper caps, leases, or denials. A plain PolicyDecision.allowed = true is not authority and must not be usable outside the broker/minting path.

Ordered Backlog

Gate 0: Grounding And Schema Plan

  • Update the identity proposal with this manifest/disk/external account model once the current Telnet milestone no longer owns serial focus.
  • Update identity docs to use principal, account, session, and profile consistently, reserving user for human-facing prose.
  • Publish the terminology in user-facing mdBook pages, not only this backlog. At minimum update docs/overview.md, docs/capability-model.md, docs/proposals/user-identity-and-policy-proposal.md, and docs/proposals/oidc-and-oauth2-proposal.md, and docs/security/trust-boundaries.md so readers encounter the same terms from normal documentation entry points.
  • Decide whether account records live in the existing user-identity schema slice or a separate account-store schema slice.
  • Define stable IDs for local principals, external bindings, resource profiles, storage root references, and policy versions.
  • Define rollback and version checks for local account-store records.
  • Add design notes for how recovery imports a damaged or missing local account store from manifest seed data.
  • Write the cross-cutting limited-resource and quota proposal before treating any guest, anonymous, local-account, service, or external profile as complete.

Gate 1: Manifest Seed Accounts

  • Extend boot/init config with manifest seed accounts, service accounts, resource profiles, and initial role bindings.
  • Validate that seed account names, principal IDs, roles, resource profiles, and credential references are unique and resolvable.
  • Reject manifests that grant ordinary users privileged kernel caps directly instead of broker-mediated policy inputs.
  • Add host tests for duplicate principals, missing resource profiles, invalid bootstrap roles, and service-account/binary mismatches.
  • Add a QEMU smoke that boots a manifest-seeded local operator and proves the session receives only the expected default bundle.

Gate 2: AccountStore And ResourceProfile Services

  • Add AccountStoreReader and AccountStoreManager userspace interfaces for lookup, create, disable, lock, role binding, external binding, and profile updates while keeping read and mutation authority separate.
  • Add ResourceProfileReader and ResourceProfileManager userspace interfaces, keeping mutation authority separate from session reads.
  • Implement a RAM-backed prototype for account records and resource profiles before durable storage.
  • Add broker integration that assembles default local-account, guest, anonymous, external, and service-account bundles from account/profile records. - [x] Add the config-side default bundle planner over account/profile records for a follow-up AuthorityBrokerCap wiring slice. - [x] Wire the bootstrap AuthorityBrokerCap shell-bundle path to the config-side planner for manifest-backed local/operator sessions. - [x] Add manifest-backed guest identity/planner wiring for shell bundles and QEMU proof coverage without preserving a bootstrap guest fallback. Guest sessions now require an explicit manifest seed, guest shell bundles receive no default service endpoints, and guest launchers are empty unless a resource-profile launcher posture names a narrow proof binary. - [x] Add a local-users QEMU proof that the initial anonymous shell bundle is minimal before password login. - [x] Wire SessionManager.sshPublicKey through RamAccountStore so SSH-minted sessions inherit account-status enforcement. SSH denial causes are exposed as stable auth= audit codes (ssh-account-missing, ssh-account-disabled, ssh-account-locked, ssh-account-recovery-only, ssh-account-lookup-failed, plus the existing key/signature/ profile codes); failed records keep principal/profile blank by policy. End-to-end QEMU proof of the account-status denial paths waits for AccountStoreManagerCap as a kernel cap source (Gate 2 follow-up below). - [ ] Add AccountStoreManagerCap and ResourceProfileManagerCap as kernel cap sources so a focused QEMU demo can disable an account and prove SessionManager.sshPublicKey rejects with auth=ssh-account-disabled. This is also the prerequisite for external-binding admission tests below.
  • Add host tests proving account-admin cannot read homes, credential verifier material, or key material through account-management caps.

Gate 3: Disk-Backed Local Account Store

  • Store account records, credential references, resource profiles, role bindings, external bindings, and policy-version metadata in capability-native Store/Namespace records.
  • Add atomic update or compare-and-set semantics for account mutations.
  • Add monotonic version/epoch checks to reject stale or replayed account records after reboot.
  • Add local snapshot/export records for recovery and rollback inspection.
  • Add QEMU reboot proof that a created local account, role binding, disabled state, and home namespace survive restart.

Gate 4: Default Resource Bundles

  • Implement bundle construction for guest, local-user, developer, service-account, and anonymous profiles.
  • Allocate per-account home, config, cache, and per-session tmp namespaces through storage caps instead of synthetic path strings.
  • Add quota checks for home bytes, temp bytes, processes, threads, caps, memory, CPU share, pending IPC submissions, endpoint queue state, in-flight calls, and log volume.
  • Add QEMU proof that two local accounts receive different home/config namespaces for the same application binary.
  • Add QEMU proof that guest and anonymous sessions cannot persist data or request durable home caps.

Gate 5: RBAC Runtime

  • Implement a RoleDirectory backed by account-store role bindings.
  • Map roles to named bundle fragments and approval eligibility.
  • Add policy tests for account-admin, policy-admin, storage-admin, net-operator, service-operator, security-auditor, and recovery-operator.
  • Add deny tests showing roles alone do not authorize capability calls after the relevant cap is absent or revoked.
  • Add audit records for role grant, role removal, and role-derived bundle issuance.

Gate 6: ABAC Runtime

  • Define the first PolicyRequest context fields for auth freshness, source, external provider/tenant, object owner, object label, requested interface, method class, quota impact, and lease duration.
  • Prototype the PolicyEngine boundary with a small in-repo evaluator or Cedar-backed host-side prototype hidden behind the same interface.
  • Add ABAC tests for fresh-auth requirements, remote-vs-local denial, provider/tenant scoping, maintenance windows, service measurement, and storage label constraints.
  • Ensure PolicyDecision cannot be used directly by callers; only a broker may turn it into capabilities or leases.
  • Add QEMU proof that a stale authenticated session can keep ordinary home access but cannot obtain a privileged leased cap.

Gate 7: External Users

  • Implement external identity binding records keyed by provider and subject hash, with tenant and expiry.
  • Normalize OIDC/passkey/certificate/cloud claims before they enter policy requests.
  • Add explicit external admission configuration. It must either bind an external subject to an existing account or permit auto-creation with a named policy profile and resource profile.
  • Add an external pseudonymous account profile with bounded temp storage, bounded durable storage only when configured, and no local administrative roles.
  • Add explicit local-account binding flow for external users that need durable local home storage.
  • Add tests rejecting stale tokens, wrong tenants, unmapped provider groups, disabled bindings, and external attempts to assume local admin roles without a binding rule.
  • Add default-deny admission tests for absent external admission config, auto-creation disabled, and unknown policy/resource profile names.

Gate 8: MAC/MIC And Labels

  • Attach confidentiality and integrity labels to account profiles, session profiles, namespaces, logs, secrets, and service accounts.
  • Implement wrapper caps for read-like, write-like, control-like, and transfer-like method classes where labels affect the grant.
  • Add tests for no-read-up, no-write-down, integrity write/control, and trusted-subject exceptions.
  • Decide whether any label/hold-edge metadata must become kernel-visible for mandatory transfer rules, or whether broker and wrapper enforcement is sufficient for the first implementation.

Gate 9: POSIX Profile Adapter

  • Add POSIX profile metadata for uid/gid/user name/group name/home path as compatibility data derived from account records.
  • Ensure setuid, chmod, and ownership metadata cannot grant caps outside the compatibility filesystem service.
  • Add tests proving POSIX metadata changes do not widen cap bundles.

Verification Gates

  • Host tests for manifest validation, account-store mutation policy, role mapping, ABAC request construction, external binding normalization, and audit emission.
  • QEMU smokes for manifest-seeded operator login, two-account namespace separation, guest/anonymous persistence denial, disk-backed account survival across reboot, external pseudonymous login, and stale-session privileged-grant denial.
  • Documentation updates to docs/security/trust-boundaries.md, docs/proposals/user-identity-and-policy-proposal.md, and storage docs before any implementation is treated as selected milestone work.