Self-destructing secret notes with zero-knowledge encryption. Create an encrypted note, get a one-time link, share it β the note is gone after a single read. The server never sees your content. Not even once.
Notefade splits your encryption key so the server stores only 16 meaningless bytes. Everything else lives in the URL fragment, which browsers never send to the server. No accounts. No cookies. No tracking. Just end-to-end encrypted, one-time-read secret sharing that you can self-host anywhere.
- π AES-256-GCM encryption β Web Crypto API only, zero external crypto dependencies
- π XOR key splitting β server stores just 16 bytes; each shard alone is an information-theoretic one-time pad, making reconstruction without both halves mathematically impossible regardless of computational power
- π URL fragment architecture β
#fragmentis never sent to the server, by design - π₯ One-time read β shard is deleted the moment it's served
- β³ Auto-expiring links β 1 hour, 24 hours, or 7 days
- ποΈ Password protection β optional PBKDF2 layer (600k iterations, SHA-256)
- π± QR code sharing β generate and export as PNG
- π΅οΈ Steganographic sharing β hide your note link inside innocent-looking text (zero-width Unicode) or images (LSB pixel encoding)
- π Dark / light theme β auto-detects system preference, manual toggle to override
- βοΈ Rich text editor β formatting toolbar with bold, italic, headings, lists, code blocks, and links; notes render as markdown
- π¬ Dead drop mode β encrypt now, share an inert link, activate later via launch code
- π―οΈ Burn-after-reading β configurable fade timer (30 s, 1 min, 5 min, 15 min) clears the decrypted note from the browser
- ποΈ Multi-read notes β allow up to 10 reads before the note is gone
- π Long notes β write up to 50,000 characters; internally split into encrypted chunks and bundled into a single URL
- π Time-lock β schedule when a note becomes readable with a live countdown
- π§Ύ Proof of read β cryptographic HMAC receipt the sender can verify without knowing who read it
- π Decoy links β generate 1β3 extra encrypted notes with plausible alternate content for deniability
- π Use already encrypted notes β encrypt your content externally (or browser local with built-in /encrypt tool), paste the encrypted text, and include your AES-256-GCM decryption key (32-byte key, 12-byte IV) in the shared note URL for double-encrypted, zero-trust notes
- π§© 7 backend adapters β Cloudflare KV, Cloudflare D1, Upstash Redis, Vercel KV, Supabase, AWS DynamoDB, or your own API
- π Self-hostable β frontend and backend, no vendor lock-in
- π Reproducible builds β deterministic Docker builds, SHA-256 build manifests on every release
- π‘οΈ Subresource Integrity β every script and stylesheet in production includes
integrityattributes - π» No accounts, no cookies, no tracking β anonymous by default
- π Open source β MIT licensed
- You write a note
- The client encrypts it with AES-256-GCM (Web Crypto API)
- The 32-byte encryption key is split via XOR into two shares
- A 16-byte shard goes to the server (stored in KV with a TTL)
- Everything else goes into the URL fragment:
notefade.com/#<id>:<payload> - The recipient opens the link β client fetches the shard β server deletes it β client reconstructs the key β decrypts β done
URL fragment (#) β never sent to server:
ββ shard ID β tells server which shard to fetch
ββ integrity check β FNV-1a hash for tamper detection
ββ XOR share (48 B) β meaningless without the server shard
ββ IV (12 B) β safe to be public
ββ ciphertext β the encrypted note (padded to fixed length)
Server KV:
ββ 16 bytes β deleted after first read or TTL expiry
The server never has enough information to decrypt anything. Even if it's compromised, seized, or subpoenaed β there's nothing useful to hand over.
Notefade is designed so the server is never trusted with secrets.
What the architecture protects against:
- π₯οΈ Server compromise β the shard alone can't decrypt anything
- πΎ Data breaches β no content is ever stored server-side
- βοΈ Subpoenas / legal requests β there's nothing meaningful to produce
- π‘ Network surveillance β the URL fragment never leaves the browser
- π URL length analysis β notes up to 1,800 characters are padded to a fixed length; longer notes use encrypted multi-chunk URLs whose length reveals approximate size
- β»οΈ Link reuse β the shard is deleted after a single read
What it does not protect against:
- πΈ Screenshots or copy-paste by the recipient
- π¦ Compromised devices (keyloggers, screen capture malware)
- πΎ A recipient intentionally saving the content
We're honest about this. Once someone reads a note, they have the plaintext. Notefade ensures only one person reads it, and that the server is never in a position to.
Security headers: no-referrer policy, no-store cache headers, HTTPS-only in production, origin-locked CORS.
For a full technical breakdown, see notefade.com/docs.
Two methods to disguise a note link so it doesn't look like a link at all.
The URL is converted to binary, then encoded as invisible zero-width characters β U+200B (zero-width space) for 0, U+200C (zero-width non-joiner) for 1, and U+200D (zero-width joiner) as a separator β interleaved into cover text you provide.
The result looks like a normal sentence. The link is invisible to human eyes but recoverable by the decode page.
Limitations: Some apps strip zero-width characters on paste. If the recipient can't decode, send the link directly. The encoding is invisible to humans but detectable by tools inspecting Unicode codepoints.
URL bits are written into the least-significant bit of each R, G, B channel (alpha is untouched). A 4-byte big-endian length header precedes the UTF-8 payload.
Two modes:
- Generate β creates random abstract art as the cover image
- Upload β use your own image as the cover
The image must have enough pixels to hold the URL data. The LSB changes are visually imperceptible.
Messengers (WhatsApp, Telegram, Signal, etc.) recompress images by default, which destroys LSB-encoded data.
- PNG download β use when sending as a file/document (e.g. WhatsApp's "send as document" or "original quality" mode), or over channels that don't recompress (email attachments, cloud storage links, AirDrop)
- ZIP download β wraps the PNG in a ZIP archive that messengers won't recompress. The recipient extracts the PNG and decodes it. Safest option for messenger sharing.
Every download gets a randomized filename from 16 patterns (camera roll, screenshot, casual share, art/creative, social, etc.) using cryptographically random selection. An interceptor seeing the file cannot determine it came from notefade β filenames look like IMG_20260303_142517.png, sunset_v2.png, from_alex_painting.png, etc. No two downloads produce the same filename.
The built-in /decode page extracts hidden links from both images and text. Drag-and-drop or file upload for images, paste for text.
yarn buildServe the dist/ directory from any static host β Cloudflare Pages, Vercel, Netlify, Nginx, or a simple file server.
The default backend is a Cloudflare Worker with KV storage. Deploy your own:
yarn worker:deployOr use any of the 7 supported shard storage providers:
| Provider | Type | Notes |
|---|---|---|
| Cloudflare KV | cf-kv |
Default. Native TTL support. |
| Cloudflare D1 | cf-d1 |
SQL-based alternative. |
| Upstash Redis | upstash |
REST API, serverless Redis. |
| Vercel KV | vercel |
Backed by Upstash. |
| Supabase | supabase |
Postgres-backed. |
| AWS DynamoDB | dynamodb |
Via API Gateway. |
| Self-hosted API | self |
Any server implementing the ShardStore interface. |
Implement this interface and you can use any storage backend:
interface ShardStore {
put(id: string, shard: string, ttl: number): Promise<void>;
get(id: string): Promise<string | null>; // fetch and delete
exists(id: string): Promise<boolean>;
delete(id: string): Promise<boolean>;
}The get method must delete the shard after returning it (one-time read semantics).
When someone opens a note stored on a non-default server, notefade displays which provider holds the shard so users know what they're trusting.
- Node.js 22.14.0 (see
.nvmrc) - Yarn
# Clone the repo
git clone https://github.com/SaschaWebDev/notefade.git
cd notefade
# Install dependencies
yarn install
# Start the dev server (frontend)
yarn dev
# Start the worker (backend, separate terminal)
yarn worker:devThe dev server proxies /shard requests to the local worker on port 8787.
# Type-check and build
yarn build
# Run tests
yarn test
# Run tests in watch mode
yarn test:watch538 unit tests across 31 test suites (Vitest) covering cryptography, BYOK encryption, steganography, key splitting, receipt verification, URL parsing, decoy generation, API integration endpoints, backend adapters, worker shard storage, rate limiting, ZIP building, time formatting, and random utilities. 9 end-to-end tests (Playwright, Chromium) covering page routing, the create-note flow, full create-to-read roundtrip decryption, note-gone states, and password gate rendering.
# Run unit tests
yarn test
# Run E2E tests (auto-starts Vite dev server)
yarn test:e2eyarn build:prodRuns four steps: type-check β Vite build β SRI injection β build manifest generation. The output dist/build-manifest.json contains SHA-256 hashes of every file.
You can verify that the code running on notefade.com matches this repository.
With Docker (most reliable β pins exact Node version and lockfile):
git checkout v0.1.0
yarn build:docker
# Compare dist-verify/build-manifest.json against the GitHub release manifestWith the CLI script (requires Node 22.14.0):
git checkout v0.1.0
yarn install --frozen-lockfile
yarn build:prod
node scripts/verify-build.cjsThe verify script fetches the build manifest from notefade.com, downloads every listed file, and compares SHA-256 hashes for self-consistency. It also verifies SRI integrity attributes on script and stylesheet tags. If a local build exists, it warns when Node versions or commits differ between local and remote. Tagged releases on GitHub include build-manifest.json as an artifact.
# Deploy frontend to Cloudflare Pages
yarn deploy
# Deploy worker
yarn worker:deployIf using Cloudflare Pages automatic builds (connected to GitHub), set NODE_VERSION to 22.14.0 in your Pages project settings (Settings β Environment variables). This ensures Cloudflare builds with the same Node version pinned in .nvmrc, which is required for reproducible builds and matching SRI hashes.
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript (strict), CSS Modules |
| Build | Vite 6 |
| Encryption | Web Crypto API (AES-256-GCM, PBKDF2, XOR) |
| Steganography | LSB image encoding, zero-width Unicode text encoding |
| Backend | Cloudflare Workers + KV |
| Validation | Zod |
| Testing | Vitest, Playwright |
| QR Codes | qrcode-generator |
src/
βββ api/ # Shard API client & 7 backend adapters
β βββ adapters/ # cloudflare-kv, d1, upstash, supabase, dynamodb, self-hosted
βββ components/ # React UI (CreateNote, ReadNote, NoteLink, QrCode, ...)
β βββ docs/ # Documentation pages
βββ crypto/ # AES-256-GCM, XOR key splitting, PBKDF2, steganography (LSB + zero-width)
βββ hooks/ # useCreateNote, useReadNote, useHashRoute, ...
βββ styles/ # CSS variables & animations
worker/
βββ index.ts # Cloudflare Worker (shard CRUD + rate limiting)
Eight endpoints. That's the entire backend.
| Method | Endpoint | Description |
|---|---|---|
POST |
/shard |
Store a shard (returns ID) |
HEAD |
/shard/:id |
Check if a shard exists |
GET |
/shard/:id |
Fetch and delete a shard |
DELETE |
/shard/:id |
Destroy a shard without reading |
POST |
/shard/defer |
Create a defer token (dead drop) |
POST |
/shard/activate |
Activate a deferred note |
Rate limited per IP. Max request body: 1 KB. Full API docs at notefade.com/docs.
Security tradeoff: These endpoints do NOT follow the zero-knowledge model. The server briefly sees plaintext (~1-2ms in volatile Worker memory) during encryption or decryption. Never stored, never logged β but the server processes content, which the main application never does. The read endpoint additionally requires the full note URL (including the
#fragment) to be sent to the server. Use the main app for sensitive secrets.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/create-note |
Create a note (server-side encryption) |
POST |
/api/v1/read-note |
Read a note (server-side decryption) |
Requires an X-Api-Key header. Rate limited per key (60 req/min, KV-based). Max body: 16 KB (create and read). Fixed 24-hour TTL for created notes.
These are convenience endpoints for trusted third-party apps that need to create or read notes programmatically. They produce and consume the same encrypted format as the main app β AES-256-GCM, XOR key splitting, one-time-read β but crypto operations happen on the server instead of in the browser.
BYOK support: The read endpoint supports BYOK (Bring Your Own Key) URLs. If the URL contains a !keyBase64url suffix, the server performs notefade's standard decryption first, then applies a second AES-256-GCM decryption using the provided key β returning the fully-decrypted plaintext. Third-party apps can pre-encrypt content with their own key before creating notes, ensuring the server never sees the original plaintext. See notefade.com/docs#integration-api for full documentation and security details.
MIT β Sascha Majewsky

