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

Proposal: Standard App Capabilities (AppData, Powerbox, Attenuated Sharing)

Status: future design. No implementation. This proposal defines three app-facing capability patterns; the AppData cap is the nearest-term, self-contained piece, the powerbox and sharing-mint depend on a trusted display path and the attenuation wrappers respectively.

Summary

Google Drive, examined closely, spends a lot of effort reluctantly re-inventing capabilities on top of an ambient REST API: the drive.file scope plus the Picker (an app may touch only files the user explicitly hands it), the appDataFolder space (per-app private storage invisible to the user and other apps), and the role lattice (reader/writer/…) for sharing. Each is a workaround for the fact that the base API is ambient-by-default and gated by OAuth scopes – a category rights bitmask re-checked server-side.

capOS does not have that base problem: there is no ambient authority, no path VFS, and access is narrowed by handing a more-restricted typed capability. So capOS can express Drive’s three good ideas as the native mechanism rather than the exception, and more cleanly:

  • AppData – a per-process private storage root, granted at spawn and never duplicated. Isolation is structural (only one holder), not a server scope check keyed to an OAuth client id.
  • Powerbox (a FilePicker/resource-picker broker) – a user-mediated grant where a trusted selector the app cannot script returns a real, fresh, method-narrowed capability for exactly what the user chose. This is what drive.file + Picker is trying to be.
  • Attenuated sharing – “share read-only” means handing a File wrapper that lacks write; escalation is impossible by construction, not by per-request ACL evaluation.

The goal is to make application development both simpler (apps ask for a private scratch space or a user-picked file instead of negotiating a global namespace and scope ladder) and more secure (least authority by default, enforced structurally). These caps are backend-independent: they sit unchanged in front of RAM, local disk, and a future Google Drive backend (docs/proposals/drive-storage-backend-proposal.md).

What capOS already has (build on, do not reinvent)

  • Storage caps Store / Namespace / Directory / File exist in schema/capos.capnp and as RAM-backed kernel CapObjects. Directory.sub() / Namespace.sub(prefix) already return structurally-scoped child caps that cannot traverse upward (the chroot analog). See docs/proposals/storage-and-naming-proposal.md and kernel/src/cap/.
  • An attenuation table is already designed in storage-and-naming-proposal.md (read-only / append-only File, read-only Directory/Store/Namespace wrappers) but is not yet implemented – current sub() has structural scoping with no per-method attenuation. This proposal’s sharing pattern depends on landing those wrappers.
  • authority_broker (kernel/src/cap/authority_broker.rs) is already a decision point that mints a bundle of capabilities for a session based on its SessionContext principal/profile (the login -> shellBundle / remoteClientBundle flow). It is the proto-powerbox; the powerbox below generalizes it from session-establishment-time to a per-request, user-confirmed grant.
  • session_context (kernel/src/session_context.rs) binds one immutable identity per process. An AppData root and a powerbox grant can both key on SessionContext.principal_id, exactly as authority_broker already does.
  • Manifests grant exactly the caps an app receives, with a grant mode (Raw/ClientEndpoint/Move/ServiceObject). Per-app scoping today is “the manifest grants a sub()-scoped Directory.”

The genuinely new surface is: a per-app AppData interface, a per-request powerbox/file-picker mechanism (the term “powerbox” is currently unused in the repo), and a service that mints attenuated caps for sharing to another principal.

Design lessons from Google Drive

Drive conceptWhat it really iscapOS pattern
appDataFolder + drive.appdata scopePer-app hidden storage, server-scope-gatedAppData cap: one holder, structural isolation
drive.file + PickerUser-mediated per-file grant (ACL expansion)Powerbox broker mints/returns a per-object cap
OAuth scope ladder (drive vs drive.readonly)Category rights bitmask on a principal(rejected) method-narrowed wrapper caps
Roles (reader..owner)ACL lattice entries, re-checked per requestAttenuated wrapper caps (subset of methods)
expirationTime permissionServer-enforced time-boxed ACL entryRevocation/expiry membrane held by the grantor
anyone / link sharingBearer grant (authority = possession)Bearer cap – deliberately flagged, audited
Shortcut (pointer file)Reference to a target idNamespace name -> cap binding
Revisions / keepForeverPer-file version listContent-addressed Store blobs + mutable pointer + GC pin

The recurring lesson: Drive’s least-privilege features are the ones where it was forced to approximate object capabilities (drive.file, Picker, appDataFolder); its scope ladder and server-side ACL are the ambient base it is working around. capOS should adopt the former natively and not import the latter.

1. AppData – per-app private storage

Every process can be granted, at spawn, a private storage root that no other principal holds a copy of. Isolation requires no policy check: the cap is simply never handed to anyone else.

interface AppData {
  open   @0 (name :Text) -> (file :File);   # create-or-open within this app's root
  list   @1 () -> (entries :List(Text));
  remove @2 (name :Text) -> ();
}
  • Backing: an AppData cap is a thin role over a Directory (or Namespace) scoped to the app – in the simplest form a manifest-granted Directory.sub("<app>"). It can be backed by RAM today, local disk later, or the Drive appDataFolder space (see the backend proposal).
  • Isolation vs Drive: Drive enforces appData isolation with a server-side scope check keyed to the OAuth client id (ambient identity gating a shared namespace). capOS hands each process a private cap and never duplicates it – cross-app leakage is not possible, not merely disallowed.
  • Quota: attach a storage budget to the cap (per the resource-accounting-proposal.md ledger model) instead of charging a global per-user pool. This is a deliberate divergence from Drive’s unified per-human quota (see Non-Goals).
  • Lifecycle: the root and its storage are reclaimed when the principal is destroyed – the cap analog of Drive deleting appDataFolder on app uninstall.

AppData is the nearest-term piece: over RAM it is a small userspace service plus a manifest grant, with no dependency on the powerbox or the attenuation wrappers.

2. Powerbox – user-mediated capability grants

A powerbox is a trusted broker that, on an app’s request, presents the user a selector the requesting app cannot script or read through, and on the user’s confirmation mints and returns a fresh capability for exactly the chosen object – optionally method-narrowed. It generalizes authority_broker from “mint a bundle at login” to “mint one cap per user gesture.”

interface FilePicker {
  pickFile  @0 (mode :AccessMode) -> (file :File);
  pickFiles @1 (mode :AccessMode) -> (files :List(File));
  pickDir   @2 (mode :AccessMode) -> (dir  :Directory);
}
enum AccessMode { readOnly @0; readWrite @1; }
  • Why better than drive.file + scope: the returned File is a real handle scoped to one object, narrowed at mint time (no drive.readonly string), revocable locally by dropping it, with no “+ files the app created” fuzzy second clause and no server ACL round-trip. The user gesture is the grant.
  • Prior art: this is the Genode “parent routes the session request according to policy” pattern (docs/research/genode.md §Session Routing) and the Sculpt/nitpicker user-mediated resource model. capOS’s authority_broker is the analog of Genode parent-routing; the powerbox is its per-request generalization.
  • Hard prerequisite – trusted display: a powerbox is only as trustworthy as the path that shows the selector. The user must be able to trust that the selector UI is the system’s, not a spoof drawn by the requesting app. capOS does not yet have a multiplexed trusted-display primitive (the nitpicker analog); today the trusted surface is the shell/session/terminal. The file-picker powerbox therefore depends on either (a) a text-mode trusted selector hosted by the session/shell, or (b) a future trusted display service. This is the powerbox’s gating dependency and is called out as an open question.
  • The powerbox is not file-specific in principle – the same broker shape can mediate user-confirmed grants of other resource caps. This proposal scopes the first instance to file/dir/storage selection; a general Powerbox is future work.

3. Attenuated sharing – wrapper caps + revocation membrane

Sharing is delegating a capability, optionally narrowed to a smaller interface, optionally through a revoker.

interface File {
  # ... existing read/write/stat/...
  shareAs @N (role :ShareRole, expiresAt :UInt64)
          -> (handle :File, revoke :Revoker);
}
enum ShareRole { reader @0; commenter @1; writer @2; }

interface Revoker { revoke @0 () -> (); }
  • Roles as method subsets: a reader is a File cap exposing only the read-side methods (today read/stat); writer additionally exposes write/truncate. Escalation is impossible because the method literally is not on the object the grantee holds – not because an ACL is re-evaluated. This is a monotone lattice expressed structurally. (A commenter role, as in the ShareRole enum below, implies a comment surface the current File interface – read/write/stat/truncate/sync/close – does not yet have; it is illustrative of the lattice, not of an existing method.)
  • Depends on the attenuation wrappers already designed in storage-and-naming-proposal.md but not yet implemented. Landing those read-only/narrowed File/Directory wrappers is the prerequisite for shareAs.
  • Clawback is the one place capabilities are weaker than Drive’s mutable ACL: a handed-out cap cannot be unilaterally downgraded later. shareAs therefore mints the shared handle through a revocation membrane and returns the Revoker to the grantor, so “un-share later” and expiresAt are supported – at the cost of an interposed membrane and a trusted clock on the sharing path.
  • Shared directories / group ownership (Drive shared drives) map to a group-owned Directory with per-member role wrappers; deferred to future work.

Uniformity across storage backends

All three caps are defined over the existing typed storage interfaces, so they are identical whether the backing is RAM, local disk (docs/proposals/storage-and-naming-proposal.md), or Google Drive (docs/proposals/drive-storage-backend-proposal.md). An app that uses an AppData cap and a FilePicker does not know or care which backend serves it. This is the same backend-agnosticism the storage proposal already states for Store (“backed by virtio-blk, RAM, or network”).

Honest mismatches and non-goals

  • Bearer / link sharing (anyone): capabilities are bearer tokens, so link-sharing maps “cleanly” – which is exactly the risk. It drops user mediation entirely (anyone with the bytes has access). Treat it as a deliberately-flagged, audited exception, never a default; prefer a powerbox grant or shareAs to a named principal.
  • Clawback / instant global revoke: Drive’s owner can demote any grantee at any time via the central ACL. capOS gets this only where caps were minted through a revoker; there is no zero-cost equivalent of Drive’s org-wide instant revoke for already-forwarded caps.
  • Unified human quota: Drive charges one per-user quota across spaces. capOS uses per-cap budgets; reconciling “this app’s AppData counts against the human’s storage” is a policy question with no clean cap answer. Per-cap budgets are the default; a unified human-facing view is out of scope.
  • Scope tiering is administrative, not technical: Drive’s restricted-scope verification is a business/review gate, not a security mechanism. It has no capability analog and is explicitly not imitated; structural narrowing replaces it.
  • Trusted display is a real gap: without it, the powerbox selector can be spoofed by the requesting app. This proposal does not deliver a trusted display; it depends on one (open question below).

Relationship to existing proposals

  • storage-and-naming-proposal.md – owns the storage caps, the attenuation table this proposal’s sharing depends on, and the existing “Managed Cloud Backing” / “User-Owned Browser Transport” sections. A small reconciling update there should cross-reference AppData and the powerbox; this proposal is the standalone home for the three patterns.
  • userspace-authority-broker-proposal.md – proposes moving broker policy into init-owned userspace; the powerbox should live wherever the broker lands.
  • oidc-and-oauth2-proposal.md – the OAuth consent screen is itself a powerbox grant; the patterns are consistent.
  • docs/research/{genode,plan9-inferno,eros-capros-coyotos}.md – Genode parent-routing/powerbox, Plan 9 per-process namespaces (an AppData mounted alongside other storage is the union-namespace pattern), and the EROS persistence contrast (capOS keeps application-level persistence, not transparent single-level store).

Phasing

  1. AppData over RAM (near-term, self-contained): a userspace AppData service plus a manifest grant; QEMU proof that two apps cannot see each other’s data. No powerbox/wrapper dependency.
  2. Attenuation wrappers (implement the already-designed read-only/narrowed File/Directory wrappers): prerequisite for sharing.
  3. shareAs + Revoker (sharing-mint): once wrappers exist; adds the revocation membrane and a trusted clock on the sharing path.
  4. FilePicker powerbox (gated on a trusted display path): start with a session/shell-hosted text-mode selector; generalize to a Powerbox and a trusted display service later.

Open questions

  • What is the trusted-display primitive the powerbox selector renders through – a shell/session-hosted selector, or a new multiplexed display service (the nitpicker analog)?
  • Should AppData quota integrate with resource-accounting-proposal.md ledgers, and how does it relate to a future unified human-facing storage view?
  • Does shareAs belong on each storage interface (File/Directory/Store) or on a separate Sharing minting service that takes a cap and returns a narrowed one?
  • Is the first powerbox instance file/dir/storage-only, or should the general Powerbox shape (mediating any resource cap) be defined up front?