Skip to content

Damage Scenarios and Quarantine

Draft

The lookup table for “what damage X is prevented by which invariant Y in which doc Z.” Each row names a concrete vector found during the audit and the single owner-doc anchor that defeats it. The table itself is the operational core of the threat model — adding a row obliges declaring the defense in exactly one owner doc.

#Damage scenarioDefenseOwner doc
1Old client writes a sidecar after stripping unknown fieldsSidecar signature covers _unknown; old client refuses to write when sidecar_schema > its max knownMetadata — Schema Versioning Rules
2Faulty client uploads bytes that don’t match the declared content typeServer’s content_type allow-list per protocol version (no-key check) + receiving client decoder sandboxValidation §, Clients — Sandboxed Decoder
3Buggy client uploads chunk with wrong offset and re-triesIdempotency tuple (upload_id, offset, chunk_hash); duplicate at offset with different hash → rejectImport — Upload Protocol
4Hostile peer sends an old-but-validly-signed manifest to revive a deleted assetprior_provenance_hash chain advance check on both client and serverCryptography — Provenance, Validation §
5Malicious client re-signs an existing manifest under a weaker crypto_suite_idSignatures cover crypto_suite_id and protocol_versionCryptography — Write Authorization
6Two devices concurrently caption the same photoCaption LWW + superseded_captions array surfaces the loserMetadata — Surfacing Concurrent Edits
7Client issues an OR-set remove for an element it never observed an add forAdd-id binding: removes target a specific add_id; unknown add_id is rejectedMetadata — Add-id Binding
8Buggy client overwrites a good thumbnail with a corrupt oneEvery derivative carries a signed DerivativeManifest on its own chain; overwrite is a derivative-replace lifecycle actionCryptography — Derivative Provenance
9A client declares timestamp = 2099-01-01 to distort the audittimestamp is self-asserted and audit-only — never load-bearing for ordering or authorization (those ride the chain + epoch); a gross-drift sanity bound surfaces it, and the server’s own received_at is the authoritative clockCryptography — Write Authorization
10Server-side TOCTOU on blob dedup creates a duplicateDedup-check and pending-row insert are atomic on a single Postgres transactionFilesystem — Content-Addressing and Deduplication
11A faulty client uploads bytes that exceed its declared sizeServer bounds cumulative received at every chunk, not only at finalizationImport — Chunk Rules
12A new client writes a manifest with a crypto_suite_id the server does not recognizeRefuse-by-default at handshake: 400 before any session is createdValidation — Protocol Negotiation
13A federated peer floods the rejected-hash table to exhaust memoryPer-peer quota; bounded LRU memoryFederation — Soft-Fail Semantics
14A model swap silently invalidates the AI tag namespaceEvery tags_ai entry carries model_id+model_version; the vector-index insert API and query layer (capsule-core::db) reject unknown-model inserts and exclude stale entries — cross-model comparison is forbiddenMetadata — Tag Provenance and Namespacing
15A leaked session token revokes all of a user’s other sessions to lock them outrevoke_all_sessions requires master-key proof, not session authAuthentication — Explicit revocation
16An attacker holding every current key tries to rewrite the asset’s historyProvenance chain references each predecessor’s hash; rewriting any past record requires forging an earlier (possibly retired) device’s hybrid signatureCryptography — Provenance
17A client picks a random amk_version to skip MLSServer’s no-key check: amk_version must be monotonic per album and known to the serverValidation §
18A v_old client tries to write into an album that has been upgraded to v_newAlbum pinning + upgrade ceremony quiescence: server returns 409 for writes carrying a stale intent_idVersioning — Album Upgrade Ceremony
19A malformed CBOR sidecar lands on disk after a crash mid-writeMalformed sidecar → quarantined to .library/quarantine/; never silent-skippedFilesystem — Repair
20A federation pull returns a manifest claiming a device that’s not in the user’s directoryServer’s no-key check: created_by_device must be in the user’s published device directoryValidation §
21A buggy client uploads a metadata blob with a hand-crafted wire formatMetadata blob wire format is byte-exact; mismatched envelope rejected at decodeCryptography — Metadata Blob Wire Format
22A retry of a delete manifest decrements blob refcount twiceManifest idempotency keyed by prior_provenance_hash: a duplicate manifest is a no-opValidation — Idempotency
23A backup restore from 6 months ago silently overwrites current stateRestore is a chain-reconciliation, never a blind overwrite: newer local state always wins; an older or divergent restored manifest is quarantined for explicit merge, never auto-appliedBackup & Recovery — Backup Verification
24A new device claims its key is older than the account itselfDevice entry in the device directory is signed by the IK and carries added_at; a server rejects an upload from a device whose added_at postdates the manifestCryptography — Device Keys, Validation §
25A peer floods notifications to make Capsule pull garbageNotifications are advisory; pull is on Capsule’s schedule and goes through full validationFederation — Pull-Only Federation
26A federated server’s TLS endpoint silently changes its public keyServers TOFU-pin each other’s keys; a rotation is accepted only after a multi-vantage perspective check corroborates it, else surfacedFederation — Server Identity and Key Rotation
27A buggy client writes a stack edit that updates one member’s sidecar and not the othersStack edits are bundle-atomic: all .tmp files staged first, all renamed together; any rename failure discards the bundleFilesystem — Atomic Writes
28A federated peer serves a stale capability token after revocationCapability TTL ≤ 24h + published revocation list polled ≤ 15 minFederation — Federation Capabilities
29A faulty client uploads embeddings derived from a model the receiver does not runVector index refuses inserts whose model_id is unknownAI — Embedding Provenance
30A client tries to forge a server-derived field (computed ciphertext hash, received_at, sync_seq, blob reference counts)The server assigns/recomputes these itself and ignores client-supplied values; the ciphertext hash is recomputed at finalization and a mismatch is rejectedImport — Finalization and Integrity
31A server serves a stale device directory to undo a revocation or hide a new deviceMaster-signed monotonic directory_version; every reader refuses a directory below its per-user high-water markCryptography — Device Directory
32A server fabricates or rewinds an album epoch, or a manifest cites an epoch whose key is mid-distributionamk_version ceiling anchored to the admin-signed MLS commit chain, not the server’s counter; a beyond-attested epoch is rejected, an in-flight key yields verify_asset pending/retry (not a forgery)Cryptography — Write Authorization
33A share link is enumerated or brute-forced≥128-bit opaque-id + per-IP/per-link rate limits + indistinguishable 404 + home-server-only servingShare Links — Security Contract
34A peer mass-reports or false-flags a user to force a takedownFederated reports are signed by the reporting server and rate-limited per (reporting_server, reported_user); per-user blocks never propagate as server-wide blocksModeration — Federated Reporting
35A user pulls from many federated peers to exhaust the home server’s storageFederated-received blobs charged to the receiving user’s quota (deduped) under a per-(receiving_user, source_peer) capQuota — Accounting Model
36A leaked upload link is used to exhaust the provisioning user’s quota or flood their inboxPer-link caps (bytes, count, size, expiry) + quota charged to the owner at drop-session creation + per-IP/per-link rate limits + revocation; a drop never enters the libraryWeb Upload — Security Contract
37A guest uploads a hostile or malformed file through an upload linkDrops never enter the library; the adopter decodes external-origin bytes in the sandboxed decoder and reviews before adoption; a rejected drop is discardedWeb Upload, Clients — Sandboxed Decoder
38A server substitutes its own encapsulation key to read drop contentsThe Drop Key public half is delivered in the URL fragment and never reaches the server, so it cannot splice in a key it controls; the server holds only a kem_ct it cannot openWeb Upload — Two Confidentiality Properties
39A guest drop is silently injected into the library as if user-authoredA drop carries no manifest, no signatures, and no provenance, and never flows through verify_asset; adoption produces a fresh adopter-signed create manifest and records the guest as unverified descriptive origin onlyWeb Upload — Drop and Adoption Lifecycle
40A guest brute-forces an upload link’s optional passphrase to spend the owner’s quotaThe stored Argon2id verifier makes every guess pay the KDF cost; per-IP + per-{opaque-id} rate limits throttle attempts; per-link caps and quota-at-creation bound the damage even on successWeb Upload — Security Contract
41A peer doctors a capability’s min_protocol_version to coax a downgraded parse of album eventsThe claim is inside the Ed25519-signed token — tampering breaks the signature — and every pulled event still passes the receiver’s own protocol handshake and the album’s pin regardless of what the token claimsFederation — Federation Capabilities
42A share link’s passphrase is brute-forced offline from the fetched wrapped secretArgon2id cost per guess on the client-side unwrap; the wrapped-secret endpoint is rate-limited per IP + per link; the fragment-delivered link secret itself is never server-held, so the server cannot shortcut the searchShare Links — Security Contract
43A server loses entrusted bytes and denies ever having accepted themThe hybrid-signed CustodyReceipt issued inside the finalization transaction proves acceptance; a failed content-addressed retrieval (self-verifying) or a signed non-stored attestation proves later non-holding; a device-signed delete + elapsed retention is the server’s only rebuttalStorage Verification — Proof of Loss
44A client fabricates a claim that it entrusted the server with bytes it never sentCustody claims are valid only when backed by a receipt under the server’s attestation signature — no receipt, no claim; the chained receipt log lets the server enumerate everything it did acceptStorage Verification — Proof of Loss
45A receipt is replayed against a different server, or a stale durable attestation replayed as currentserver_id + server_key_id are inside the signed receipt body, binding it to one server; signed attestations echo a client-chosen nonce, so a stale verdict cannot answer a fresh challengeStorage Verification — Custody Receipts

When a scenario surfaces during implementation that does not match any of the above, the rule is: add a row here, then declare the defense in exactly one owner doc. Never restate a defense in multiple docs.

Every “don’t apply, surface it” code path. The union exists so the UI surface and operator audit have a single inventory of “things that need a human to look at.”

SurfaceWhere it lives on disk (client)Source of truth doc
verify_asset reject (any signature or chain failure)Quarantine area surfaced via the audit logCryptography — Write Authorization
Federation soft-failRejected-hash table, bounded LRUFederation — Soft-Fail Semantics
Orphaned original (no sidecar).library/quarantine/ after a failed recoveryFilesystem — Repair
Malformed CBOR sidecar.library/quarantine/ (the unparseable bytes are preserved)Filesystem — Repair
Stale-revival (peer or restore sends old manifest)Audit log + UI surface “peer sent stale state”Cryptography — Provenance
Album upgrade stranded writeLocal pending_until_upgrade queueVersioning — Album Upgrade Ceremony
Backup restore chain conflictAudit log + UI surface “restore conflicts”Backup & Recovery — Backup Verification
Pending drop awaiting adoptionProvisioning user’s inbox (server); surfaced on their clients as “awaiting your review”Web Upload — Drop and Adoption Lifecycle

A quarantined item is never silently dropped and never silently applied. The user (or operator) can inspect, repair, or discard explicitly.

The append-only hash-chained record per asset is defined in Cryptography — Provenance. This section is the policy layer.

  • No path exists to overwrite or delete an existing provenance entry. Not via the API, not via the local filesystem (the client treats .provenance.cbor as append-only), not via federation. The constraint is structural, not enforced by a permission check.
  • Even a hard-delete preserves provenance. When an asset is purged, its media/{YYYY}/{YYYY-MM}/{uuid}.provenance.cbor remains as a tombstone-with-history. The bytes that go away are the ciphertext blob and the encrypted metadata; the audit trail does not.
  • Export and backup carry the chain. A backup artifact includes every asset’s full provenance chain. On restore, the chain re-enters the local index — reconciled per Backup & Recovery — Backup Verification, which never silently overwrites newer local state.
  • What a key-holding attacker still cannot do. A complete current-key compromise lets the attacker append forward. It does not let them rewrite the past — every prior record is bound by a signature from a (possibly retired) device whose public half is retained in the device directory and never pruned (marked revoked_at, not deleted), so a retired device’s signature stays verifiable forever.