blobs
blobs
edia in atproto: uploaded bytes addressed by CID, referenced from records. the interesting part is the lifecycle — who may fetch a blob, and when. evidence here is from zds, a PDS implementation in zig.
blob CIDs use the raw-leaf codec
CIDv1, codec 0x55 (raw), sha2-256 — a plain hash of the bytes, no dag
structure (zds/store.zig:5483). record CIDs use dag-cbor; blob CIDs don't,
and conflating the two breaks reference detection. blob refs inside records
are found structurally: a $link inside a blob/ref map
(store.zig:639,4924).
bytes on disk, metadata in the database
zds splits storage: the bytes live in the filesystem under
{blobstore}/{did}/{cid}, while mime type and size live in a SQLite blobs
table (blobstore.zig:38, store.zig:2412). reads fetch with
limit = size + 1 so truncation and oversize both surface as errors instead
of silently serving a short file (store.zig:2440).
authorization is reference-counted through records
the non-obvious rule: an uploaded blob is private until a record references
it. zds maintains expected_blobs(blob_cid, record_uri) rows on every write
and delete, and getPublicBlob only serves a blob that joins to a live
record (store.zig:2456,1964,1984). two things fall out:
- upload-then-abandon leaks nothing — the blob simply never becomes public,
and
listMissingBlobsis a query over the same table. - deleting the last referencing record de-publishes the blob without any separate garbage-collection bookkeeping for the public surface.
serve public blobs defensively
blobs are user-supplied bytes served from your origin. zds's headers on the
public blob route: content-security-policy: default-src 'none'; sandbox,
x-content-type-options: nosniff, immutable caching (sync.zig:186) — an
svg or html file uploaded as a "blob" must never execute in your origin's
context.
sources
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.