# 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 `CapObject`s. `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 concept | What it really is | capOS pattern |
| --- | --- | --- |
| `appDataFolder` + `drive.appdata` scope | Per-app hidden storage, server-scope-gated | `AppData` cap: one holder, structural isolation |
| `drive.file` + Picker | User-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 request | Attenuated wrapper caps (subset of methods) |
| `expirationTime` permission | Server-enforced time-boxed ACL entry | Revocation/expiry membrane held by the grantor |
| `anyone` / link sharing | Bearer grant (authority = possession) | Bearer cap -- deliberately flagged, audited |
| Shortcut (pointer file) | Reference to a target id | `Namespace` name -> cap binding |
| Revisions / `keepForever` | Per-file version list | Content-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.

```capnp
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."

```capnp
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.

```capnp
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?
