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.mddocs/architecture/manifest-startup.mddocs/proposals/user-identity-and-policy-proposal.mddocs/proposals/userspace-authority-broker-proposal.mddocs/proposals/storage-and-naming-proposal.mddocs/proposals/oidc-and-oauth2-proposal.mddocs/proposals/cryptography-and-key-management-proposal.mddocs/security/trust-boundaries.mddocs/roadmap.mdREVIEW_FINDINGS.md
Relevant prior-art research files checked from the actual docs/research/
tree are:
docs/research/eros-capros-coyotos.mddocs/research/genode.mddocs/research/plan9-inferno.mddocs/research/sel4.mddocs/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, oraccountwhen 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 profileorresource profilewhen the narrower meaning is intended; use plainprofileonly 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 foregroundTerminalSessionfor this login.session: read-onlyUserSessionorSessionContextfor audit identity, auth freshness, and display.home: read-writeNamespaceorDirectoryscoped 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, rawFrameAllocator, broadDeviceManager, or unrestrictedStoreAdminby default. homegrants are owner-scoped. Sharing returns attenuated sub-namespace or file capabilities through a broker and records the grant in audit state.configwrites 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-operatorgrants 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, andprofileconsistently, reservinguserfor 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, anddocs/proposals/oidc-and-oauth2-proposal.md, anddocs/security/trust-boundaries.mdso 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
AccountStoreReaderandAccountStoreManageruserspace interfaces for lookup, create, disable, lock, role binding, external binding, and profile updates while keeping read and mutation authority separate. - Add
ResourceProfileReaderandResourceProfileManageruserspace 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
AuthorityBrokerCapwiring slice. - [x] Wire the bootstrapAuthorityBrokerCapshell-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] WireSessionManager.sshPublicKeythroughRamAccountStoreso SSH-minted sessions inherit account-status enforcement. SSH denial causes are exposed as stableauth=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 keepprincipal/profileblank by policy. End-to-end QEMU proof of the account-status denial paths waits forAccountStoreManagerCapas a kernel cap source (Gate 2 follow-up below). - [ ] AddAccountStoreManagerCapandResourceProfileManagerCapas kernel cap sources so a focused QEMU demo can disable an account and proveSessionManager.sshPublicKeyrejects withauth=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/Namespacerecords. - 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, andanonymousprofiles. - Allocate per-account
home,config,cache, and per-sessiontmpnamespaces 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
RoleDirectorybacked 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, andrecovery-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
PolicyRequestcontext fields for auth freshness, source, external provider/tenant, object owner, object label, requested interface, method class, quota impact, and lease duration. - Prototype the
PolicyEngineboundary 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
PolicyDecisioncannot 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.