Skip to content

Storage backends: dumb-store WIT, the cryptographic pull layer, and the S3-compatible provider spike #19

Description

@lannbot

Design recorded in NOTES.md — Storage backends and the cryptographic pull layer (from the 2026-08-16 design discussion; leaning, not ruling). Summary of what this issue tracks:

The design

  • Backend contract: authenticated owner PUT/DELETE + GET by unguessable name. No ACLs, no LIST, no conditional writes (content-addressed chunks; per-device signed head manifests; readers merge). Admits S3-anything, consumer drives as dumb stores, static hosts, CDNs, IPFS.
  • Cryptographic pull layer: per (doc × epoch) name-keys; object name = HMAC(name-key, cref); optional outer AEAD hiding keyhive envelope metadata. Name-keys travel over the E2E channel as capabilities; Access::Relay ≈ name-key possession, uniform with the realtime pull tier. Read keys stay in keyhive's op stream (also stored as blobs).
  • Cooperative fetch revocation (K_p): per-recipient-device wrapped name-key at a pairwise-derived location, deleted on revocation ingest; honest clients never persist pull-layer material. Four-layer revocation: BeeKEM rotation (read, hard) / name-key rotation (pull-forward, hard) / K_p deletion (pull-now, cooperative) / compaction relocation (pull-past, hard, eventual) — plus storage-credential rotation for the owner's own devices.
  • Motivating scenario: stolen device cracked offline after revocation — compromise narrows from everything the device could reach to everything it had reached; the layers act at revocation time, independent of when the crack lands.
  • Accepted losses (threat-model entries): no retroactive fetch-denial against modified clients until relocation; HNDL surface widens to name-holders (bounded by rotation; expiring-URL minting as leak hygiene where cheap); egress abuse on paid-egress backends (private buckets + minted URLs there); provider sees recipient counts via K_p objects.

Deliverables

  • polymorph:blobstore-shaped WIT: core dumb-blob interface (put/get/delete by name); optional share-mint (expiring URL) capability interface, class-D-style declared
  • Pull layer in the framework core (backend-independent): name-key derivation, per-device manifests, K_p wrap/pickup/delete, rotation hooks into the existing revocation path, relocation folded into compaction
  • S3-compatible provider spike (first): SigV4 via polymorph:webcrypto HMAC in-guest, network grant scoped to the backend host; R2 + B2 as documented defaults, MinIO for self-host; scenario = two devices converge through the bucket, a recipient fetches via name-keys with no backend account, K_p deletion goes dark for a stock client
  • Google Drive provider as a dumb store (fast follow): drive.file scope, appDataFolder; start the OAuth-verification clock early (wall-time dependency)
  • Provider-config conformance notes: object-versioning off for the K_p prefix (non-load-bearing; see NOTES), CORS recipe per backend
  • Recovery-bundle placement (Key lifecycle: disposable device keys, recoverable data root (recovery phrase / WebAuthn PRF), last-device loss #11): public-fetch mode, name + KEK from the recovery phrase
  • Threat-model entries (Threat model: the adversary table and the "nothing is both live and trusted" invariant #1): cooperative-revocation classification, HNDL widening, egress abuse, provider metadata (incl. recipient counts)

Related: #8 (data services; the storage path is its non-realtime half), #11 (recovery bundle), #1 (threat model), #6/#7 (provider confinement is the dogfooded plugin model).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions