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.mddocs/tasks/README.md
Relevant prior-art research files:
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.
- Session liveness cell: mutable trusted session-manager state behind the
immutable process
SessionContext. It records whether the session islive,logged_out,revoked,expired, orrecovery_only, plus session and policy epochs used by renewal and grant decisions. - 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;
retired12 @12 :UInt32; # was pending IPC submission quota; do not reuse
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, endpoint
queue state, in-flight calls, network posture, and log volume. Ring
submissions remain fixed-bound by ring depth and dispatch budget instead of a
profile quota. 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.
Username-Aware Local Password Login
The current shell-led login command is username-aware for the local password
path as of 2026-04-30 02:18 UTC: it prompts username> before hidden
password>, sends an account/principal selector plus proof/source metadata to
SessionManager.login, and lets SessionManager choose the account-owned
credential reference before minting a session. This is still a bootstrap
implementation over one console verifier; disk-backed credential records and
multi-verifier account storage remain future work.
Status 2026-05-01 08:47 UTC: default password-authenticated local operator
sessions mint with expiresAtMs = u64::MAX; the shell renders that as
expires_at_ms=never. Manifests that set a non-default operatorMs still
exercise wall-clock expiry for focused stale-session proofs.
The target console UX is:
loginprints generic login text and promptsusername>.- The shell reads the account name with visible echo and bounded line length.
- The shell prompts hidden
password>only after a submitted username. - All denials print the same
authentication denied.text, regardless of whether the account name is missing, disabled, locked, recovery-only, profile-incompatible, or the password proof is wrong. - Setup remains explicit. A fresh-image
setuppath must either create the first local operator-kind account name or clearly state which volatile compatibility account owns the credential.
The implementation should change the request shape before adding user-visible multi-account behavior:
SessionManager.loginshould carrymethod, an account/principal selector, proof bytes, and source metadata. For the password path, the selector is a normalized local account name or opaque account ID; proof bytes remain the submitted password until a challenge/response verifier exists.SessionManagerverifies the bootstrap console password only after the selected manifest/default account owns credential referenceconsole-password. FutureCredentialStorerecord APIs should preserve the same account-owned reference rule without exposing whether the selector, account, credential reference, or verifier failed.SessionManageruses the account store as the source of principal ID, display name, principal kind, policy profile, resource profile, account status, and credential references when seed account data exists. The no-store fallback accepts the normalized manifest operator seed account name when one exists, and retainsoperatoronly as the bare compatibility default when no seed account exists.- Default password-authenticated local operator sessions should not use fixed wall-clock expiry as their normal lifecycle. They should end through explicit logout, terminal/connection/process-tree close, or administrator revocation; configured hard maxima remain opt-in policy for proofs or deployments that require them.
AuthorityBroker.shellBundlecontinues to derive the shell bundle from the minted session’s policy and resource profiles; it must not trust the typed username as authority after session minting.
Audit and redaction rules are part of the contract:
- Failed pre-auth attempts record only a terminal-local event ID, source class, generic password-denied or password-unavailable reason, auth method, and volatile flag. They leave principal, account, profile, and session fields blank so account enumeration is not possible through logs.
- Successful login records stable principal/session/profile metadata from the minted session, not from raw username text. The password proof, verifier, credential reference secret, and full terminal line never appear in audit, kernel logs, QEMU transcripts, or panic text.
- Wrong username and wrong password should have indistinguishable terminal text and audit shape except for terminal-local event IDs and timing/backoff.
Migration from the existing seeded operator password is explicit:
kernelParams.consolePasswordVerifierPhcmaps to a manifest seed account namedoperatorwith a stable credential reference such asconsole-passwordwhen no richer seed account owns that verifier.- If the manifest already declares a seed account with that credential reference, the verifier belongs to that account and no synthetic account is created.
- The shell accepts
operatoras the username for migrated manifests. A wrong or unknown username follows the same denial path as a wrong password. - Setup-created credentials remain volatile until disk-backed account storage lands; the prompt and audit record must keep saying so.
- Documentation and smoke transcripts should stop treating a bare password as sufficient identity once the username-aware flow lands.
Required proof coverage for the first implementation slice:
make run-loginandmake run-smokeprompt forusername>before hiddenpassword>, acceptoperatorplus the existing demo password, and reject a wrong username and wrong password with identical terminal denial text.make run-login-setupcovers first-credential setup and then username-aware login for the resulting account or the default migration account.make run-local-usersproves manifest-backed operator account lookup, resource/profile inheritance, and account-status denial without exposing account existence in failed audit records.- Host tests cover manifest migration from
consolePasswordVerifierPhcto the default seed account, duplicate credential-reference rejection, and normalized account-name lookup.
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). - [x] Migrate local password login planning into schema and proof work: add a username-awareSessionManager.loginselector, move password verification to account-owned credential references, preserve anti-enumeration audit/terminal behavior, and keep the existing single seeded operator password as an explicitoperatoraccount migration path. Implemented 2026-04-30 02:18 UTC as prioritized ad-hoc work; durable multi-verifier credential storage remains future Gate 2 account-store work. - [ ] 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. - Complete mutable session lifecycle methods before treating short session
expiry as production shell UX. The first
live/logged_outliveness cell andUserSession.logoutpath is implemented forSessionManager-minted sessions, including explicit remote DTO gateway logout and owned-session connection-close propagation. Remaining work includes owner-shell exit, terminal close, administrator revocation, renewal/recovery, full audit reason separation, and in-flight endpoint result cancellation after logout.SessionManager.renewshould extend or rotate a session only after account status, auth freshness, policy/resource profile epochs, requested duration, and revocation state pass. Renewal must mint fresh grant leases or wrappers when policy needs a new decision and must not silently revive stale ordinary grants. - 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, 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 only through policy-explicit recovery/renewal state and cannot obtain a privileged leased cap until renewal or reauth mints fresh grant leases.
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.