Root-Only Collections
A root-only collection can be accessed only by the user's root device — the
device whose cap is self-signed (iss === sub), produced by bootstrapRootIdentity.
Every other credential the same user holds — a paired/provisioned device cap (minted by
the root, so iss !== sub) or a member cap shared with another user — is rejected with
403, on top of the collection's normal readRoles / writeRoles checks.
Because the server gatekeeps every pull / push / list (and bundle pull), rejecting
there fully blocks access regardless of which keys or caps a device already holds — no
keyring rotation or cap revocation is required.
When to use
- A collection that should never leave the primary device — recovery secrets, a device's master configuration, an export/escrow blob.
- Paired devices are convenient for day-to-day data but must not reach a privileged collection, even though they share the same root identity.
Configuration
// TypeScript server
{
name: "recovery",
storagePath: "recovery/{slot}",
readRoles: ["cap:read:recovery"],
writeRoles: ["cap:write:recovery"],
encryption: "none",
maxBodyBytes: 65536,
rootOnly: true,
}
# Python server
CollectionConfig(
name="recovery",
storage_path="recovery/{slot}",
read_roles=["cap:read:recovery"],
write_roles=["cap:write:recovery"],
encryption="none",
max_body_bytes=65536,
root_only=True,
)
Behavior
- The cap-resolver synthesizes a
device:rootrole (ROLE_ROOT_DEVICE) for a self-signed device cap. A root-only collection requires that role in addition to its configuredreadRoles/writeRoles. - Paired devices typically share the root's scope, so the role check alone would admit them — the root-only gate is what distinguishes the root device from its delegates.
- Bundle pulls enforce the same rule: a root-only member is silently omitted from the bundle for a non-root caller and included for the root device.
- A root-only collection is never public: combining
rootOnlywith a"public"entry inreadRoles/writeRolesis rejected at config load.
Soundness boundary
rootOnly distinguishes the root device from the user's own delegated devices/members
— the intended threat model. It is purely additive: it can only deny access the normal
checks would otherwise grant. The predicate identifies a self-signed device cap; it does
not by itself prove the cap belongs to a particular root identity. Cross-user isolation
continues to come from the existing {identity} URL-param binding and identity-scoped
storage paths, exactly as for non-root-only collections.
Scope
rootOnly governs data access (pull / push / list / bundle). It does not change the
opt-in GET /config discovery endpoint, which lists collection definitions (not data)
according to its own auth mode.