Skip to content

Repository files navigation

hush.md logo — a markdown blockquote chevron followed by a fading ellipsis

hush.md

Share markdown that keeps quiet.
End-to-end encrypted markdown sharing with expiring links. No accounts, no tracking, nothing readable on the server.
hush.md


Write a markdown document in a split-pane editor, hit Share, and get two links — an edit link and a read-only view link. The document is encrypted in your browser before anything leaves it, and it deletes itself on the schedule you pick.

Features

  • End-to-end encrypted — AES-256-GCM in the browser via WebCrypto. The key travels in the URL fragment (#k=…), which browsers never send to servers.
  • Expiring links — presets from 1 hour to 30 days, or any custom duration from 1 minute to 90 days. Expiry means deleted from disk, enforced on read, at boot, and by a background sweep.
  • View limits — let a document self-destruct after N views (preset or custom). The Nth reader gets it; the N+1th gets nothing.
  • Password protection — an optional password is mixed into the key derivation (PBKDF2-SHA-256, 600k iterations), so the link alone isn't enough. There is no server-side "has password" flag: protected and plain documents are indistinguishable to the server.
  • Burn-once secrets — drop an API key or password into a document as a {{secret}}; it's encrypted with its own fresh key and can be revealed exactly once. Everyone after sees only when it was taken.
  • Edit links vs view links — the edit capability is a separate token, enforced server-side, not a hidden button.
  • Live split-pane editor — CodeMirror 6 + GFM preview (tables, task lists, code), light/dark themes, editor/split/preview layouts. Drag the divider to rebalance the panes, and the two scroll together so the rendered result keeps pace with the source.
  • Maths and diagrams — LaTeX via KaTeX ($inline$, $$display$$) and Mermaid diagrams from ```mermaid fences, both rendered in your browser from the same encrypted text. Neither renderer is in the initial bundle; they load only when a document uses them.
  • Callouts — Obsidian-style > [!warning] Title blocks across all thirteen kinds, each with its own icon.
  • Document outline — a collapsible panel of the document's headings, click to jump. Read-only readers get it too, which is where a long shared document needs it most.
  • .md download — from the editor before sharing too, so "keep it entirely local" is a first-class path.
  • Self-contained HTML export — one file with fonts, diagrams and maths embedded and every dead control stripped. No reference back to hush.md, so it opens with no network.
  • QR codes — optional, per link, generated locally. Pairs with short links: a full link makes a 41-module code, a shortened one 29.
  • No accounts — a document and its link are the entire relationship.

Security model

The server is a blind relay and blob store. What it stores per document:

Stored Meaning
ciphertext snapshot your document, AES-256-GCM encrypted client-side
wrapped content key the doc key, itself encrypted with a key derived (PBKDF2-SHA-256, 600k iterations) from the link key
KDF salt public
SHA-256 of the edit token lets the server verify editors without being able to become one
creation/expiry timestamps, view counters scheduling metadata
burn-once secret blobs each encrypted client-side with its own key (carried inside the encrypted document, never sent); burned rows keep only a timestamp

The link key and content key exist only in URL fragments and browser memory. The server cannot read a document, and a database leak yields ciphertext plus hashes.

What the server does see (honesty section): request IPs and timing, document sizes, and view counts. Request logs redact document IDs.

The standard web-E2E caveat applies: the encryption is only as trustworthy as the JavaScript this server delivers. If your threat model includes the operator, self-host it — the code you're reading is the code that ships.

Hardening in place: strict CSP, HSTS, per-IP rate limits, storage budget guard, timing-safe token comparison, DOMPurify over markdown-it (html: false) as the XSS boundary.

How it works

  1. Write — the document lives in your browser. Nothing is sent while you type.
  2. Share — the client generates a random content key + link key + edit token, encrypts the document, wraps the content key, and uploads only ciphertext. The link it hands you carries the keys in the fragment.
  3. Open — a recipient's browser fetches the ciphertext, derives the wrapping key from the fragment, unwraps, decrypts, renders. Wrong link ⇒ decryption fails; there is no password-reset because there is nothing to reset.
  4. Hush — at expiry the ciphertext is deleted. Links to deleted documents are indistinguishable from links that never existed.

Self-hosting

Everything runs from one small container: Node + Fastify + SQLite, with the built client served statically.

docker build -t hush-md .
docker run -p 8080:8080 -v hush_data:/data hush-md

Environment: PORT (default 8080), DATA_DIR (default /data), STATIC_DIR (set in the image). A fly.toml is included — the public instance runs on a single 256 MB scale-to-zero Fly.io machine.

Development

npm workspaces monorepo:

packages/envelope   shared crypto (browser + Node, WebCrypto only)
packages/server     Fastify blind blob store + SQLite
packages/web        Vite + CodeMirror client
npm install
npm test              # vitest, all packages
npm run dev:server    # API on :8080
npm run dev:web       # Vite dev server, proxies /api

The crypto envelope is implemented exactly once and imported by every consumer — a deliberate rule to avoid E2E implementations drifting apart.

Roadmap

Live collaboration (encrypted CRDT relay) and a CLI for sharing from the terminal. Everything else listed above is already live. Standing rule: if a feature would require the server to read your words, it doesn't ship.

One consequence worth stating plainly: rendering happens in your browser, so a diagram or a formula never leaves it. That is also why Mermaid output is sanitized a second time on the way into the page — a diagram is someone else's content, and strict mode alone isn't the only lock worth having.

License

MIT

About

Share markdown that keeps quiet — end-to-end encrypted markdown sharing with expiring links. No accounts, nothing readable on the server.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages