This is the adversarial pass over the original proposal. Each pillar is attacked as hard as possible against what the code actually does today, followed by a verdict on what survives. The synthesis rebuilds from the survivors.
Attack 1: The publish envelope already exists as the Ref
The proposal's first pillar reads: "the publish API must have a signature envelope where the CID of the new data gets signed by some account, with a timestamp and whether the content is public". That describes, field for field, a blob that has been in the system since the beginning, the Ref:
Proposed envelope field | Existing Ref field |
|---|---|
CID of the new data |
|
signing account |
|
timestamp |
|
is the content public |
|
(none) | plus |
The Ref already plays exactly the envelope's role. Changes have no visibility of their own. They are born private and become public only when a public Ref reaches them through propagation. The Ref is the sole publicness authority today. Proposing it as new shows that the actual design is under-documented. That is a real finding, but it is not a design contribution. A second envelope wrapping the same facts would create two sources of truth for visibility and two conflict-resolution registers to reconcile.
What is actually missing is narrower and more interesting:
Raw uploads are unclaimed. POST /ipfs/file-upload accepts anonymous, unsigned bytes. No signed statement covers them until a document links them. This is an ownership gap, and the envelope has nothing to do with it: quotas, garbage collection, and abuse handling have nothing to attach to.
The server doesn't defend the claims. CreateRef hardcodes public visibility (open VULN-5). Nothing enforces "visibility set only at first publish". A modified client can flip any doc public on any publish. The envelope exists. The validation rules around it are missing.
Publicness is irreversible. blob_visibility rows are only ever added. Once public, a blob is served forever, whatever later Refs say. The proposal's timestamp-ordered supersession gestures at fixing this, but see Attack 2.
Verdict: the pillar dissolves into three real work items: claim raw uploads, validate visibility transitions at index time, and make un-publishing mean something at the blob layer. None of them is a new blob type.
Attack 2: The timestamp is already a live vulnerability class
Signing a timestamp is free. Believing one is not. No verifier can tell an honest timestamp from a backdated or future-dated one, so any semantics hung on ts hang on attacker-controlled input.
In Seed this is not hypothetical. Security depends on it right now. A document is deleted iff max(tombstone ref ts) > max(alive ref ts), and visibility is a last-writer-wins register keyed on Ref timestamp, with zero index-time validation. A writer can publish a Ref with a far-future timestamp and win the visibility register permanently. A compromised key can backdate around any future revocation scheme built on "later statement wins." The proposal would pour more security weight onto exactly this foundation.
The system already owns a trustworthy ordering primitive: causal position in the signed DAG. You cannot claim to precede a blob you reference. Ref even has an unvalidated generation counter waiting to be promoted into a real epoch.
Verdict: invert the pillar. Timestamps become advisory (display, tie-breaks). Everything with security meaning orders by DAG position or epoch: visibility transitions, revocation, supersession. Expiry, if used, checks the enforcing server's clock. Details in rabbit holes.
Attack 3: "Read capabilities" without an enforcement story is a policy file
Here the proposal points at a real hole: there is no read permission in the system. Every private-read gate reuses the write check (canReadPrivate literally calls IsValidWriter). It only honors root-scoped grants. Capabilities have no expiry and no revocation. And the whole apparatus is inert unless the operator runs -public-only. On a default desktop daemon, every local caller reads everything (open issue #664).
But the proposal doesn't say against whom the caps defend. Two different products hide in that gap:
Trusted-server enforcement: your home server and chosen gateways enforce grants at serve time. This defends against strangers. It does not defend against a malicious server operator or any peer that already synced the bytes.
Cryptographic enforcement (Tahoe-style): content is encrypted and the cap carries the key. This defends against dishonest servers too, at the cost of server-side search, dedup, cheap re-sharing, and sane revocation (prior art).
The proposal silently assumes the first while promising the second ("a versatile and simple privacy system"). Users get hurt in that gap: the UI says private while the guarantee is only polite. The sync layer makes it worse. Private blobs already replicate to space peers and site servers, so every replica silently joins the trusted computing base.
Verdict: survives once it is made honest. A READER role is a natural, small extension of the existing Capability blob (the enum is literally WRITER/AGENT with an EDITOR TODO). But it must ship with an explicit trust-model statement in spec and UI, expiry and revocation semantics, path-scoped grants that actually work, sync limited to covered audiences, and a schema slot for a wrapped key so encryption can arrive later without a redesign.
Attack 4: Transitive read is the best idea here, in the most dangerous phrasing
As phrased, "You can read a private blob if you're allowed to read a blob that links to it" is a machine for laundering access. Anyone who learns a CID can mint a blob linking to it and grant themselves passage. The idea worth saving underneath: a grant on a document covers the blobs its owner bundled into it, such as deps, heads and embedded files, because the grantor had authority over those. One word, whose links, separates the vulnerability from the feature.
The safe version isn't even new. It is precisely how the four-row blob_visibility_rules table (Change→dep, Ref→head, →DagPB, →Raw) already propagates publicness and space visibility down owner-signed structure. The proposal reinvented the system's own indexing rule and removed the safety fence.
Verdict: survives, renamed. The rule becomes "grants cover the owner's bundle", in place of "linked blobs are readable". It is the existing rule table, generalized from two hard-coded audiences to arbitrary ones. The remaining sharp edges (history scope, embeds crossing ownership boundaries) are in rabbit holes.
Attack 5: Does this provide much benefit? The honest accounting
Attack the premise. The system already has public and private documents, space-scoped visibility, member access through capabilities, filtered sync, and three working signed-auth transports. What can a user do after this project that they can't do today?
Share a private doc with a specific outside person. Today this is literally inexpressible: the only way to grant read is to grant root write on the entire space. This is the headline feature, the Google Docs moment, and most of the user-visible value.
Share links ("anyone with this link"). These come nearly free from bearer-audience grants once (1) exists.
Honest revocation and unpublish. Today revocation doesn't exist and unpublish is impossible at the blob layer. Epoch machinery gives both a defined, if limited, meaning.
Coherent enforcement. One rule replaces today's two contradictory capability checks, opposite fail-open and fail-closed defaults, and privacy that each deployment opts into. This is a debt payment the redesign forces, with no new feature for users.
Consistent multi-server policy. Any authorized replica reaches identical access decisions from signed statements alone. This matters exactly as much as multi-server private hosting matters: little today, a lot for the architecture.
The cost is the query-surface perimeter. Today's publicOnly boolean becomes per-caller audience evaluation across search, citations, feeds, listings, comments and sync: every surface, forever, including ones not written yet. The saving grace: if grants materialize into the access table at indexing time (the blob_visibility generalization), every surface keeps filtering by a dumb join, which is the shape the code already has.
Verdict: the benefit is real, but it is one feature plus one debt payment. It is not a platform. If sharing with outsiders isn't worth building, none of this is.
What survived the shredding
Ownership claims for raw uploads, index-time validation of visibility transitions, and unpublish with defined semantics (from Attack 1).
DAG or epoch ordering, with timestamps demoted to advisory, which also fixes two live bugs (Attack 2).
A READER capability with expiry, revocation, path scope, an explicit trusted-server model, and an encryption slot (Attack 3).
Grants cover the owner's bundle: the existing propagation rules, generalized to audiences (Attack 4).
Scope discipline: build the share feature, and let multi-server consistency fall out of the architecture (Attack 5).
The deepest thing the shredding exposed: the original proposal treated public and private as different systems that needed a bridge. The codebase quietly disagrees. blob_visibility already models publicness as "visible to space 0", which is a grant to everyone. The synthesis rebuilds everything on that one move.
See also
Permissions, the model as it ships.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime