Proposal: User Identity, Sessions, and Policy
How capOS should represent human users, service identities, guests, anonymous callers, and policy systems without reintroducing Unix-style ambient authority.
Problem
capOS has processes, address spaces, capability tables, object identities,
badges, quotas, and transfer rules. It deliberately does not have global
paths, ambient file descriptors, a privileged root bit, or Unix uid/gid
authorization in the kernel.
Interactive operation still needs a way to answer practical questions:
- Who is using this shell session?
- Which caps should a normal daily session receive?
- How does a service distinguish Alice, Bob, a service account, a guest, and an anonymous network caller?
- How do RBAC, ABAC, and mandatory policy fit a capability system?
- How does POSIX compatibility expose users without letting
uidbecome authority?
The answer should keep the enforcement model simple: capabilities are the authority. Identity and policy decide which capabilities get minted, granted, attenuated, leased, revoked, and audited.
Design Principles
useris not a kernel primitive.uid,gid, role, and label values do not authorize kernel operations.- A process is authorized only by capabilities in its table.
- Authentication proves or selects a principal; it does not itself grant authority.
- A session is a live policy context that receives a cap bundle.
- A workload is a process or supervision subtree launched with explicit caps.
- POSIX user concepts are compatibility metadata over scoped caps.
- Guest and anonymous access are explicit policy profiles, not missing policy.
Concepts
Principal
A principal is a durable or deliberately ephemeral identity known to auth and policy services. It is useful for policy decisions, ownership metadata, audit records, and user-facing display. It is not a kernel subject.
Examples:
- human account
- operator account
- service account
- cloud instance or deployment identity
- guest profile
- anonymous caller
- pseudonymous key-bound identity
enum PrincipalKind {
human @0;
operator @1;
service @2;
guest @3;
anonymous @4;
pseudonymous @5;
}
struct PrincipalInfo {
id @0 :Data; # Stable opaque ID, or random ephemeral ID.
kind @1 :PrincipalKind;
displayName @2 :Text;
}
PrincipalInfo is intentionally descriptive. Possessing a serialized
PrincipalInfo value must not grant authority.
Session
A session is a live context derived from a principal plus authentication and policy state. Sessions carry freshness, expiry, auth strength, quota profile, audit identity, and default cap-bundle selection.
enum AuthStrength {
none @0;
localPresence @1;
password @2;
hardwareKey @3;
multiParty @4;
}
struct SessionInfo {
sessionId @0 :Data;
principal @1 :PrincipalInfo;
authStrength @2 :AuthStrength;
createdAtMs @3 :UInt64;
expiresAtMs @4 :UInt64;
profile @5 :Text;
}
interface UserSession {
info @0 () -> (info :SessionInfo);
defaultCaps @1 (profile :Text) -> (caps :List(GrantedCap));
auditContext @2 () -> (sessionId :Data, principalId :Data);
logout @3 () -> ();
}
interface SessionManager {
login @0 (method :Text, proof :Data) -> (session :UserSession);
guest @1 () -> (session :UserSession);
anonymous @2 (purpose :Text) -> (session :UserSession);
}
GrantedCap should be the same transport-level result-cap concept used by
ProcessSpawner, not a parallel authority encoding.
Workload
A workload is a process or supervision subtree started from a session, service, or supervisor. Workloads may carry session metadata for audit and policy, but they do not run “as” a user in the Unix sense. They run with a CapSet.
Common workload shapes:
- interactive native shell
- agent shell
- POSIX shell compatibility session
- user-facing application
- per-user service instance
- shared service handling many user sessions
- service account process
Capability
A capability remains the actual authority. A process can only use what is in its local capability table. Policy services can choose to mint, attenuate, lease, transfer, or revoke capabilities, but they do not create a second authorization channel.
Session Startup Flow
flowchart TD
Input[Login, guest, or anonymous request]
Auth[Authentication or guest policy]
Session[UserSession cap]
Broker[AuthorityBroker / PolicyEngine]
Bundle[Scoped cap bundle]
Shell[Native, agent, or POSIX shell]
Audit[AuditLog]
Input --> Auth
Auth --> Session
Session --> Broker
Broker --> Bundle
Bundle --> Shell
Broker --> Audit
Shell --> Audit
The shell proposal’s minimal daily cap set is a session bundle:
terminal TerminalSession or Console
self self/session introspection
status read-only SystemStatus
logs read-only LogReader scoped to this user/session
home Directory or Namespace scoped to user data
launcher restricted launcher for approved user applications
approval ApprovalClient
The shell still cannot mint additional authority. It can ask
ApprovalClient for a plan-specific grant, and a trusted broker can return
a narrow leased capability if policy and authentication allow it.
Multi-User Workloads
capOS should support two normal multi-user patterns.
Per-Session Subtree
The session owns a shell or supervisor subtree. Every child process receives an explicit CapSet assembled from the session bundle plus workflow-specific grants.
Example:
- Alice’s shell receives
home = Namespace("/users/alice"). - Bob’s shell receives
home = Namespace("/users/bob"). - The same editor binary launched from each shell receives different
homeandterminalcaps. - The editor cannot cross from Alice’s namespace into Bob’s unless a broker deliberately grants a sharing cap.
This is the right default for interactive applications and POSIX shells.
Shared Service With Per-Client Session Authority
A server process may handle many users in one address space. It should not infer authority from a caller’s self-reported user name. Instead, each client connection carries one or more of:
- a badge on the endpoint cap identifying the client/session relation,
- a
UserSessionorSessionContextcap, - a scoped object cap such as
Directory,Namespace,LogWriter, orApprovalClient, - a quota donation for server-side state.
The service uses these values to select scoped storage, enforce per-client limits, emit audit records, and return narrowed caps. This is the right shape for HTTP services, databases, log services, terminals, and shared daemons.
Service Accounts
Service identities are principals too. They are usually non-interactive and receive caps from init, a supervisor, or a deployment manifest rather than from a human login flow.
Service-account policy should be explicit:
- which binary or measured package may use the identity,
- which supervisor may spawn it,
- which caps are in its base bundle,
- which caps it may request from a broker,
- which audit stream records its activity.
Anonymous, Guest, and Pseudonymous Access
These are distinct profiles.
Empty Cap Set
An untrusted ELF with an empty CapSet is not a user session. It is the roadmap’s “Unprivileged Stranger”: code with no useful authority. It can terminate itself and interact with the capability transport, but it cannot reach a resource because it has no caps.
Anonymous
Anonymous means unauthenticated and usually remote or programmatic. It should receive a random ephemeral principal ID and a very small cap bundle.
Typical properties:
- no durable home namespace by default,
- strict CPU, memory, outstanding-call, and log quotas,
- short session expiry,
- no elevation path except “authenticate” or “create account”,
- audit records keyed by ephemeral session ID and network/service context.
Guest
Guest means an interactive local profile with weak or no authentication.
Typical properties:
- terminal/UI access,
- temporary namespace,
- optional ephemeral home reset on logout,
- restricted launcher,
- no administrative approval path unless policy grants one explicitly,
- clearer user-facing affordance than anonymous.
Pseudonymous
Pseudonymous means durable identity without necessarily naming a human. A public key, passkey, service token, or cloud identity can select the same principal across sessions. This can receive persistent storage and quotas while still remaining separate from a verified human account.
POSIX Compatibility
POSIX user concepts are compatibility metadata, not authority.
uid,gid, user names, groups,$HOME,/etc/passwd,chmod, andchownlive inlibcapos-posix, a filesystem service, or a profile service.open("/home/alice/file")succeeds only if the process has aDirectoryorNamespacecap that resolves that synthetic path.setuidcannot grant new caps. At most it asks a compatibility broker to replace the process’s POSIX profile or launch a new process with a different cap bundle.- POSIX ownership bits may influence one filesystem service’s policy, but they cannot authorize access to caps outside that service.
This lets existing programs inspect plausible user metadata without making Unix permission bits the capOS security model.
Policy Models
RBAC, ABAC, and mandatory access control fit capOS as grant-time and
mint-time policy. They should mostly live in ordinary userspace services:
AuthorityBroker, PolicyEngine, SessionManager, RoleDirectory,
LabelAuthority, AuditLog, and service-specific attenuators.
The kernel should keep enforcing capability ownership, generation, transfer rules, revocation epochs, resource ledgers, and process isolation. It should not evaluate roles, attributes, or label lattices on every capability call.
RBAC
Role-based access control maps principals or sessions to named role sets. Roles select cap bundles and approval eligibility.
Examples:
developercan receive a launcher for development tools and read-only service logs.net-operatorcan request a leasedServiceSupervisor(net-stack).storage-admincan request repair caps for selected storage volumes.
Implementation shape:
interface RoleDirectory {
rolesFor @0 (principal :Data) -> (roles :List(Text));
}
interface AuthorityBroker {
request @0 (
session :UserSession,
plan :ActionPlan,
requestedCaps :List(CapRequest),
durationMs :UInt64
) -> (grant :ApprovalGrant);
}
Roles do not bypass capabilities. They only let a broker decide whether it may mint or return particular scoped caps.
ABAC
Attribute-based access control evaluates a richer decision context:
- subject attributes: principal kind, roles, auth strength, session age, device posture, locality,
- action attributes: requested method, target service, destructive flag, requested duration,
- object attributes: service name, namespace prefix, data class, owner principal, sensitivity,
- environment attributes: time, boot mode, recovery mode, network location, cloud instance metadata, quorum state.
ABAC is useful for contextual narrowing:
- allow log read only for the caller’s session unless break-glass policy is active,
- issue
ServiceSupervisor(net-stack)only with fresh hardware-key auth, - grant
Namespace("/shared/project")read-write only during a maintenance window, - deny network caps to guest sessions.
ABAC decisions should return capabilities, wrappers, or denials. They should not create hidden ambient checks downstream.
ABAC Policy Engine Choices
Do not invent a policy language first. The capOS-native interface should be small and capability-shaped, while the broker implementation can start with a mainstream engine behind that interface.
Recommended order:
-
Cedar for runtime authorization. Cedar’s request shape is already close to capOS:
principal,action,resource, andcontext. It supports RBAC and ABAC in one policy set, has schema validation, and has a Rust implementation. That makes it the best fit forAuthorityBrokerandMacBrokerservice prototypes. -
OPA/Rego for host-side and deployment policy. OPA is widely used for cloud, Kubernetes, infrastructure-as-code, and admission-control style checks. It is useful for validating manifests, cloud metadata deltas, package/deployment policies, and CI rules. The Wasm compilation path is worth tracking for later capOS-side execution, but OPA should not be the first low-level runtime dependency.
-
Casbin for quick prototypes only. Casbin is useful for simple RBAC/ABAC experiments and has Rust bindings, but its model/matcher style is less attractive as a long-term capOS policy substrate than Cedar’s schema-validated authorization model.
-
XACML for interoperability and compliance, not native policy. XACML remains the classic enterprise ABAC standard. It is useful as a conceptual reference or import/export target, but it is too heavy and XML-centric to be the native capOS policy language.
The capOS service boundary should hide the selected engine:
interface PolicyEngine {
decide @0 (request :PolicyRequest) -> (decision :PolicyDecision);
}
struct PolicyRequest {
principal @0 :PrincipalInfo;
action @1 :Text;
resource @2 :ResourceRef;
context @3 :List(Attribute);
}
struct PolicyDecision {
allowed @0 :Bool;
reason @1 :Text;
leaseMs @2 :UInt64;
constraints @3 :List(Attribute);
}
PolicyDecision is still not authority. It is input to a broker that returns
actual caps, wrapper caps, leased caps, or denial.
References:
- Cedar policy language docs: https://docs.cedarpolicy.com/
- Amazon Verified Permissions concepts: https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/terminology.html
- Open Policy Agent docs: https://www.openpolicyagent.org/docs
- Casbin supported models: https://www.casbin.org/docs/supported-models
- OASIS XACML technical committee: https://www.oasis-open.org/committees/xacml/
Mandatory Access Control
Mandatory access control is non-bypassable policy set by the system owner or deployment, not discretionary sharing by ordinary users. In capOS, MAC should be implemented as mandatory constraints on cap minting, attenuation, transfer, and service wrappers.
Examples:
- a
Secretcap labeledhighcannot be transferred to a workload labeledlow, - a
LogReaderfor security logs cannot be granted to a guest session even if an application asks, - a recovery shell can inspect storage read-only but cannot write without a separate exact-target repair cap,
- cloud user-data can add application services but cannot grant
FrameAllocator,DeviceManager, or raw networking authority.
Implementation components:
enum Sensitivity {
public @0;
internal @1;
confidential @2;
secret @3;
}
struct SecurityLabel {
domain @0 :Text;
sensitivity @1 :Sensitivity;
compartments @2 :List(Text);
}
interface LabelAuthority {
labelOfPrincipal @0 (principal :Data) -> (label :SecurityLabel);
labelOfObject @1 (object :Data) -> (label :SecurityLabel);
canTransfer @2 (
from :SecurityLabel,
to :SecurityLabel,
capInterface :UInt64
) -> (allowed :Bool, reason :Text);
}
For ordinary services, MAC can be enforced by brokers and wrapper caps. For high-assurance boundaries, the remaining question is whether transfer labels need kernel-visible hold-edge metadata. That should be added only for a concrete mandatory policy that cannot be enforced by controlling all grant paths through trusted services.
GOST-Style MAC and MIC
Russian GOST framing is stricter than the generic “MAC means labels” summary. The relevant standards split at least two policies that capOS should keep separate:
-
Mandatory access control for confidentiality. ГОСТ Р 59383-2021 describes mandatory access control as classification labels on resources and clearances for subjects. ГОСТ Р 59453.1-2021 goes further: a formal model that includes users, subjects, objects, containers, access levels, confidentiality levels, subject-control relations, and information flows. The safety goal is preventing unauthorized flow from an object at a higher or incomparable confidentiality level to a lower one.
-
Mandatory integrity control for integrity. ГОСТ Р 59453.1-2021 treats this separately from confidentiality MAC. The integrity model constrains subject integrity levels, object/container integrity levels, subject-control relationships, and information flows so lower-integrity subjects cannot control or corrupt higher-integrity subjects.
For capOS, this should map to labels on sessions, objects, wrapper caps, and eventually hold edges:
struct ConfidentialityLabel {
level @0 :Text; # e.g. public, internal, secret.
compartments @1 :List(Text);
}
struct IntegrityLabel {
level @0 :Text; # ordered by deployment policy.
domains @1 :List(Text);
}
struct MandatoryLabel {
confidentiality @0 :ConfidentialityLabel;
integrity @1 :IntegrityLabel;
}
Capability methods need a declared flow class. capOS cannot rely on generic
read and write syscalls:
- read-like:
File.read,Secret.read,LogReader.read; - write-like:
File.write,Namespace.bind,ManifestUpdater.apply; - control-like:
ProcessSpawner.spawn,ServiceSupervisor.restart; - transfer-like:
CAP_OP_CALL,CAP_OP_RETURN, and result-cap insertion when they carry caps or data across labeled domains.
Initial rules can be expressed as broker/wrapper checks:
read data-bearing cap:
subject.clearance dominates object.classification
write data-bearing cap:
target.classification dominates source.classification
# no write down
control process or supervisor:
controlling subject is same label, or is an explicitly trusted subject
integrity write/control:
writer.integrity >= target.integrity
This is not enough for a GOST-style formal claim, because uncontrolled cap transfer can bypass the broker. A higher-assurance design needs:
- kernel object identity for every labeled object,
- label metadata on kernel objects or per-process hold edges,
- transfer-time checks for copy, move, result caps, and endpoint delivery,
- explicit trusted-subject/declassifier caps,
- an audit trail for every label-changing or declassifying operation,
- a formal state model covering users, subjects, objects, containers, access rights, accesses, and memory/time information flows.
The proposal therefore has two levels:
- Pragmatic capOS MAC/MIC: userspace brokers and wrapper caps enforce labels on grants and method calls.
- GOST-style MAC/MIC: a formal information-flow model plus kernel-visible labels/hold-edge constraints for transfers that cannot be forced through trusted wrappers. See formal-mac-mic-proposal.md for the dedicated abstract-automaton and proof track.
References:
- ГОСТ Р 59383-2021, access-control foundations: https://lepton.ru/GOST/Data/752/75200.pdf
- ГОСТ Р 59453.1-2021, formal access-control model: https://meganorm.ru/Data/750/75046.pdf
Composition Order
When policies compose, use this order:
- Mandatory policy defines the maximum possible authority.
- RBAC selects coarse eligibility and default bundles.
- ABAC narrows the decision for context, freshness, object attributes, and requested duration.
- The broker returns specific capabilities or denies the request.
- Audit records the plan, decision, grant, use, release, and revocation.
The composition result is still a CapSet, leased cap, wrapper cap, or denial.
Service Architecture
The policy stack should be decomposed into ordinary capOS services. Init or a trusted supervisor grants broad authority only to the small services that need to mint narrower caps.
SessionManager
Creates session metadata caps:
guest()for local guest sessions,anonymous(purpose)for ephemeral unauthenticated callers,- later
login(method, proof)for authenticated users.
The first implementation can be boot-config backed. It does not need a
persistent account database. UserSession should describe the principal,
session ID, profile, auth strength, expiry, and audit context. It should not
be a general-purpose authority vending machine unless it was itself minted as
a narrow wrapper around a fixed cap bundle.
Safer first split:
SessionManager -> UserSession metadata cap
AuthorityBroker(session, profile) -> base cap bundle
Supervisor/Launcher -> spawn shell with that bundle
AuthorityBroker
The broker owns or receives powerful caps from init/supervisors and returns narrow caps after RBAC, ABAC, and mandatory checks.
Examples:
- broad
ProcessSpawner->RestrictedLauncher(allowed = ["shell", "editor"]), - broad
NamespaceRoot->Namespace("/users/alice"), - broad
ServiceSupervisor->LeasedSupervisor("net-stack", expires = 60s), - broad
BootPackage->BinaryProvider(allowed = ["shell", "editor"]).
The broker is the normal policy decision and cap minting point.
AuditLog
Append-only audit interface. Initially this can write to serial or a bounded log buffer; later it should be Store-backed.
Record at least:
- session creation,
- cap request,
- policy input summary,
- policy decision,
- cap grant,
- cap release or revocation,
- denial,
- declassification or relabel operation.
Audit entries must not contain raw auth proofs, private keys, bearer tokens, or broad environment dumps.
RoleDirectory
Role lookup should start static and boot-config backed:
guest -> guest-shell
alice -> developer
ops -> net-operator
net-stack -> service:network
This is enough for early RBAC bundles. Dynamic role assignment can wait for persistent storage and administrative tooling.
LabelAuthority
Owns the label lattice and dominance checks. In the pragmatic phase, it is a userspace dependency of brokers and wrappers. In a GOST-style phase, the same lattice needs a kernel-visible representation for transfer checks.
Wrapper Caps
Wrappers are the main mechanism. Prefer them over per-call ACL checks in a central service:
RestrictedLauncherwrapsProcessSpawner.ScopedNamespacewraps a broader namespace/store.ScopedLogReaderfilters by session ID or service subtree.LeasedSupervisorwraps a broader supervisor with expiry and target binding.ApplicationManifestUpdaterrejects kernel/device/service-manager grants.LabelledEndpointenforces declared data-flow and control-flow constraints.
This keeps authority visible in the capability graph.
Bootstrap Sequence
Early boot can be static:
init
-> starts AuditLog
-> starts SessionManager
-> starts AuthorityBroker with broad caps
-> asks broker for a system, guest, or operator shell bundle
-> spawns shell through a restricted launcher
Before durable storage exists, policy config comes from BootPackage /
manifest config. Before real authentication exists, support guest,
anonymous, and localPresence only.
Revocation, Audit, and Quotas
User/session policy depends on the Stage 6 authority graph work:
- badge metadata lets shared services distinguish session/client relations,
- resource ledgers and session quotas prevent denial-of-service through session creation,
CAP_OP_RELEASEand process-exit cleanup reclaim local hold edges,- epoch revocation lets a broker invalidate leased or compromised caps,
- audit logs record the cap grant and release lifecycle.
Audit should record identity and policy metadata, but it should not contain secrets, raw authentication proofs, or broad environment dumps.
Implementation Plan
-
Document the model. Keep user identity out of the kernel architecture and link this proposal from the shell, service, storage, and roadmap docs.
-
Session-aware native shell profile. Treat the shell proposal’s minimal daily cap set as a session bundle. Add
self/sessionintrospection and scopedlogs/homecaps once the underlying services exist. -
Authority broker and audit log. Add
ActionPlan,CapRequest,ApprovalClient, leased grant records, and an append-only audit path. Start with RBAC-style profile bundles and explicit local authentication. -
ABAC policy engine. Extend the broker decision with session freshness, auth strength, object attributes, requested duration, and environment state. Prefer Cedar for the runtime broker interface; use OPA/Rego for host-side manifest and deployment checks. Keep decisions visible in audit records.
-
Mandatory policy labels. Add pragmatic labels to policy-managed services and wrappers. Keep confidentiality and integrity separate. Defer kernel-visible labels until a specific MAC/MIC policy cannot be enforced by trusted grant paths.
-
Guest and anonymous demos. Show a guest shell with
terminal,tmp, and restrictedlauncher, and show an anonymous workload with strict quotas and no persistent storage. -
POSIX profile adapter. Provide synthetic
uid/gid,$HOME,/etc/passwd, and cwd behavior from a session profile and granted namespace caps. -
GOST-style formalization checkpoint. If capOS later claims GOST-style MAC/MIC, write the abstract state model before implementation: users, subjects, objects, containers, access rights, accesses, labels, control relations, and information flows. Then decide which labels must become kernel-visible.
Non-Goals
- No kernel
uid/gid. - No ambient
root. - No global login namespace in the kernel.
- No authorization from serialized identity structs.
- No model-visible authentication secrets.
- No POSIX permission bits as system-wide authority.
- No per-call role/attribute/label interpreter in the kernel fast path.
- No claim of GOST-style MAC/MIC until the formal model and transfer enforcement story exist.
Open Questions
- Which session interfaces are needed before persistent storage exists?
- Should
UserSession.defaultCaps()return actual caps or a plan that must be executed byProcessSpawner? - Which audit store is acceptable before durable storage and replay exist?
- Which MAC policies, if any, justify kernel-visible hold-edge labels?
- How should remote capnp-rpc identities map into local principals?
- Should the first broker prototype embed Cedar directly, or use a simpler hand-written evaluator until the policy surface stabilizes?