Conversation
A Blossom blob was readable by anyone who knew its hash, whatever the group's private flag said — and the hash is derived from the content, so for a guessable file it is guessable. The file server now carries the rule the relay cannot: an upload files the blob under a group (`h`) and binds the token to its content (`x`); a read needs a `t=get` token whose key is a member of one of those groups, checked against the relay's `39002` as a service identity, because a private group's member list is not served to an anonymous reader. The app signs the read token, fetches protected blobs and draws object URLs — a plain `<img src>` cannot send an Authorization header. Pictures on foreign hosts are left exactly as they were. The dev seed adds a Blossom service key and a space created in the app adds `VITE_BLOSSOM_SERVICE_PUBKEY` when configured. Server checks are unit-tested (`scripts/blossom-acl.test.mjs`) and the fail-closed read path is smoke-tested; the client's URL classification, token signing, fetch and object-URL cache in `src/nostr/attachment-access.test.ts`.
Owner
Author
|
Correction to the manual test plan (found while setting up the live environment): the bullet " The non-member case is covered where it can actually be observed — server-side, and already verified against the live relay:
What remains for the client is therefore: a member sees the attachment, a signed-out session does not, and a fetch failure leaves the picture empty rather than drawing a broken one. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes CON-26.
What this does
A Blossom blob was readable by anyone who knew its hash — no npub, no membership, no AUTH. Since the hash is derived from the content, "the URL is secret" is not a guarantee for any guessable file, and for a private space the text was protected while every attachment in it was not.
The rule now lives where the bytes are: the file server files each upload under a group and checks membership before it reads a blob back.
24242token must name its group (h) and is bound to the content (x), scoped to this server (server) and to five minutes (expiration). The server recordshash → group(s)(union, so the same bytes in two spaces belong to both).GET/HEAD /<sha256>requires at=gettoken whose key is a member of one of the blob's groups. Membership is asked of the relay's39002as a service identity, because a private group's member list is not served to an anonymous reader.t=gettoken, fetches protected blobs with it and hands the renderer an object URL (a plain<img src>cannot send anAuthorizationheader). Non-image attachments download the same way. Pictures on foreign hosts are left exactly as they were.scripts/dev-group-seed.shgenerates aBLOSSOM_SECservice key and adds it to the group; creating a space in the app addsVITE_BLOSSOM_SERVICE_PUBKEYwhen configured. Without a service key the server fails closed;--insecure-readsis the explicit way back to the old behaviour.Reviewed against the ticket's open decision
The refinement comment recommended option 1a (per-space ACL in the Blossom layer) over Buzz's community-scoped media, and over "accept and document". This implements 1a. Two decisions remain open and are called out in
docs/09: whether production keeps per-space scope (a stock Blossom server needs an authorising proxy) and how that interacts with CON-4/CON-24.Files
scripts/blossom-acl.mjs+scripts/blossom-acl.test.mjs— token validation (BUD-11), pure and unit-tested.scripts/dev-blossom.mjs— group filing on upload, membership-checked reads, relay membership oracle, fail-closed config.scripts/dev-group-seed.sh— the Blossom service identity and its membership.src/nostr/attachment-access.ts+ test — URL classification,t=getsigning, object-URL cache.src/nostr/blossom.ts,src/session/session.tsx,src/ui/Markdown.tsx,src/ui/editor-image.ts,src/ui/PageEditor.tsx,src/ui/CreateSpaceForm.tsx.docs/02,docs/04,docs/07,docs/08,docs/09,docs/13,NOSTR.md,.env.example.Test plan
Automated (run locally — all green)
npm run typecheckpassesnpm run lintpassesnpm testpasses — 239 files / 2226 tests, including the newscripts/blossom-acl.test.mjs(14) andsrc/nostr/attachment-access.test.ts(6)npm run buildpassesServer (unit + smoke, already exercised locally)
x→401; withouth→400; no token →401; valid →200503(fails closed, even with a validt=gettoken)--insecure-readsserves the blob again (escape hatch works)created_at, expired/missingexpiration, wrongtverb, wrong/missingxwhen required,serverscopingblobHashFromPathaccepts/<sha256>and/<sha256>.png, rejects everything elseManual, end to end against the real relay (needs the relay + seed)
./scripts/dev-relay-up.sh && ./scripts/dev-group-seed.sh→ the new Blossom service key is created and added toengineeringnode scripts/dev-blossom.mjs+VITE_BLOSSOM_SERVER=http://localhost:3355+VITE_BLOSSOM_SERVICE_PUBKEY=<BLOSSOM_PK>403)Regression / honesty
VITE_BLOSSOM_SERVERbehaves exactly as before (no critical path touches the new code)docs/09-security-privacy.md,docs/04andNOSTR.mdno longer claim attachments sit outside the group boundaryKnown limits (deliberate)
docs/09.🤖 Generated with DeepSeek Harness