Skip to content

Damage Scenarios and Quarantine

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

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

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.