Skip to content

Editor image uploads via Blossom (4 public servers) - #9

Merged
sovITxyz merged 3 commits into
mainfrom
feat/blossom-uploads
Jul 18, 2026
Merged

Editor image uploads via Blossom (4 public servers)#9
sovITxyz merged 3 commits into
mainfrom
feat/blossom-uploads

Conversation

@sovITxyz

Copy link
Copy Markdown
Owner

Adds image upload to the editor. Click the image button, drag, or paste an image and it uploads to public Blossom servers and inserts the markdown — no more hand-pasting URLs.

How

  • public/js/blossom.js — uploads a NbreadSigner-signed kind-24242 Blossom auth event + the raw blob via PUT /upload to blossom.band (primary, indefinite retention), then BUD-04 mirrors to blossom.nostr.build / nostr.download / cdn.nostrcheck.me for redundancy. All four were live-probed (real anonymous signed upload + CORS preflight) before selection.
  • Reuses the signer stack from Multi-signer support: NIP-46 (Amber/bunker), NIP-55 Android intents, nsec paste #7: uploads work with the extension / NIP-46 remote / pasted-key signers. The Amber NIP-55 redirect signer navigates away when signing, so it can't upload mid-flow — it cleanly falls back to the URL template.
  • Three entry points (button, drag-drop, paste-image) all insert through the existing execCommand seam, so native undo survives; an ![uploading…]() placeholder is swapped for the final ![](url) on success.
  • CSP: connect-src opens to exactly those four https:// origins. script-src and every other directive are unchanged; BLOG_CSP is untouched (blog subdomains stay JS-free); img-src * already displayed the results.

Security

Adversarially reviewed (CSP-regression, upload/secret handling, editor UX). Fixes applied:

  • Markdown-breakout blocked — a hostile server can't return https://evil/x.png) [phish](…) to escape ![](url); the returned URL is rejected if it contains whitespace, (), <>, quotes, or backtick, on top of the https://-only check.
  • Focus/caret no longer hijacked if you're typing in another field mid-upload.
  • No secret key is ever read or sent — only the signed 24242 event; server error text renders as textContent.

Known low-severity, accepted: the returned image host isn't pinned to the upload server (harmless — an https image under img-src *), and the optional sha256 integrity check is skipped if a server omits it.

Durability caveat (Approach A)

Images live on public servers, hedged by 4-way mirroring, not first-party-durable — the tradeoff of attaching public servers vs. running our own R2 Blossom. Content-addressing means any mirror can re-serve a blob by its sha256 if one dies.

Testing

856/856 tests (45 files); wrangler deploy --dry-run green; vendor drift clean. New test/unit/blossom.spec.ts covers the pure helpers (auth-event shape, base64url encoding, file validation, URL validation). docs/manual-signer-tests.md §9 covers the real-device upload path.

sovITxyz added 3 commits July 17, 2026 19:49
Adds public/js/blossom.js — click the image button, drag, or paste an
image and it uploads to public Blossom servers (blossom.band primary +
BUD-04 mirror to blossom.nostr.build / nostr.download / cdn.nostrcheck.me)
and inserts the markdown. Auth is a NbreadSigner-signed kind-24242 event;
the returned URL is validated as a clean https:// destination before it's
spliced into the post (rejects markdown-breakout chars). Uploads reuse the
in-page signers (extension / NIP-46 / pasted key); the Amber NIP-55
redirect signer falls back to the URL template. All inserts go through the
existing execCommand undo seam.
Opens connect-src to the four Blossom origins so browser uploads reach
them; script-src and every other directive are unchanged, and BLOG_CSP
(blog subdomains, JS-free) is untouched. img-src * already covered
displaying the results.
@sovITxyz
sovITxyz merged commit a407370 into main Jul 18, 2026
1 check passed
@sovITxyz
sovITxyz deleted the feat/blossom-uploads branch July 18, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant