Secure one-time secret sharing with end-to-end encryption.
Share passwords, API keys, and sensitive information securely. View once, then it's gone forever.
Burnvelope is a privacy-focused tool for sharing sensitive information without leaving traces. Unlike emails, chat messages, or texts that persist indefinitely, Burnvelope secrets exist only until they're viewed—then they're permanently deleted.
More importantly, Burnvelope is designed so that we can never read your secrets. Even if our servers were compromised, even if we were compelled by law enforcement, we literally cannot access the plaintext of your data.
We've all been there. You need to share a password or API key with a colleague. What do you do? Email it? Slack it? These options all have the same fundamental problem: the message persists. It sits in inboxes and chat logs indefinitely, getting backed up, synced, and potentially exposed in data breaches.
Burnvelope takes a different approach. Instead of trying to secure persistent messages, we eliminate persistence entirely. Your secret exists only until it's viewed—then it's gone forever. And through our zero-knowledge architecture, we never have access to your plaintext data.
- End-to-End Encryption — Secrets are encrypted in your browser before leaving your device using AES-128-GCM
- Zero-Knowledge Architecture — The encryption key never touches our servers; it stays in the URL hash
- One-Time Access — Secrets are immediately and permanently deleted after viewing
- Automatic Expiration — Unviewed secrets expire after 1 hour to 7 days
- Double Encryption — Client-side encryption plus server-side encryption for defense in depth
- No Account Required — Create and share secrets instantly
- Open Source — Fully transparent, verify our security claims yourself
Burnvelope uses a two-layer encryption model:
┌─────────────────────────────────────────────────────────────────┐
│ YOUR BROWSER │
│ ┌─────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ Secret │ -> │ AES-128-GCM │ -> │ Ciphertext │ │
│ │ (plaintext)│ │ Encryption │ │ + Key in URL │ │
│ └─────────────┘ │ (128-bit key) │ └───────────────┘ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼ (only ciphertext sent)
┌─────────────────────────────────────────────────────────────────┐
│ BURNVELOPE SERVER │
│ ┌─────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ Ciphertext │ -> │ AES-256-GCM │ -> │ Stored in │ │
│ │ (encrypted) │ │ Encryption │ │ Cloudflare KV │ │
│ └─────────────┘ │ (server key+HKDF)│ │ with TTL │ │
│ └──────────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Why AES-128 for client-side encryption? We use 128-bit keys for the client layer to keep URLs short and easy to share. AES-128 is still highly secure with no known practical attacks. The server adds a second layer of AES-256 encryption for defense in depth.
The client-side encryption key is placed in the URL hash (the part after #):
https://burnvelope.com/abc123#YourSecretKeyHere
└── never sent to server
By design, browsers never send the hash fragment to the server. This is a fundamental part of how URLs work. The key travels with the link but never touches our servers.
- Double-encrypted ciphertext (meaningless without both keys)
- Creation timestamp
- TTL expiration
- Your plaintext secrets
- The client-side encryption keys
- IP addresses in access logs
- Any personally identifiable information
- Framework: Astro 5 with server-side rendering
- Runtime: Cloudflare Workers
- Storage: Cloudflare KV with native TTL expiration
- UI Components: React 19 for interactive forms
- Encryption: Web Crypto API (AES-128-GCM client, AES-256-GCM server, HKDF)
- Language: TypeScript
src/
├── components/
│ ├── CreateForm.tsx # React form for creating secrets
│ ├── ViewSecret.tsx # React component for revealing secrets
│ ├── Layout.astro # Page layout wrapper
│ ├── Header.astro # Site header with navigation
│ ├── Footer.astro # Site footer
│ └── ... # Additional UI components
├── lib/
│ └── crypto.ts # Client-side encryption utilities
├── pages/
│ ├── index.astro # Homepage with create form
│ ├── about.astro # About page with security details
│ ├── view/
│ │ └── [id].astro # Secret viewing page
│ └── api/
│ ├── secrets.ts # POST endpoint - create secrets
│ └── secrets/
│ └── [id].ts # GET endpoint - retrieve & delete secrets
└── styles/
└── global.css # Design system and global styles
- Node.js 18+
- npm
- Clone the repository:
git clone https://github.com/shawnlehner/burnvelope.git
cd burnvelope- Install dependencies:
npm install- Start the development server:
npm run devThe app will be available at http://localhost:4321.
| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Build for production |
npm run preview |
Preview production build locally |
Burnvelope is designed to run on Cloudflare Workers with Cloudflare KV for storage.
Create a KV namespace to store encrypted secrets:
npx wrangler kv:namespace create "SECRETS"Copy the returned namespace ID and update wrangler.toml:
[[kv_namespaces]]
binding = "SECRETS"
id = "your-namespace-id-here"Generate a secure 32-byte encryption key and set it as a secret:
# Generate a random 32-byte key (base64 encoded)
openssl rand -base64 32
# Set the secret in Cloudflare
npx wrangler secret put ENCRYPTION_KEYPaste your generated key when prompted.
Deploy to Cloudflare Workers:
npm run build
npx wrangler deploywrangler.toml:
name = "burnvelope"
compatibility_date = "2024-12-01"
[[kv_namespaces]]
binding = "SECRETS"
id = "your-namespace-id"Environment Variables:
| Variable | Description | Required |
|---|---|---|
ENCRYPTION_KEY |
Server-side encryption key (32 bytes, base64) | Yes |
KV Bindings:
| Binding | Description |
|---|---|
SECRETS |
KV namespace for storing encrypted secrets |
POST /api/secrets
Request Body:
{
"encryptedData": "base64-encoded-ciphertext",
"expiresIn": 86400
}| Field | Type | Description |
|---|---|---|
encryptedData |
string | Client-encrypted secret (base64) |
expiresIn |
number | TTL in seconds (60-604800, default: 86400) |
Response:
{
"id": "abc12345",
"expiresAt": "2024-12-15T10:30:00.000Z"
}GET /api/secrets/:id
Response:
{
"encryptedData": "base64-encoded-ciphertext"
}The secret is immediately deleted after this request. Subsequent requests return 404.
Error Responses:
| Status | Description |
|---|---|
| 404 | Secret not found or already viewed |
| 400 | Invalid secret ID |
| 500 | Server error |
- Secrets persisting in email/chat history
- Database breaches (encrypted data is useless without client keys)
- Server compromise (zero-knowledge architecture)
- Man-in-the-middle attacks (HTTPS + client-side encryption)
- Compromised recipient device
- Screenshots or copy/paste by recipient
- Shoulder surfing
- Malware on sender or recipient device
- Link interception before viewing (if attacker gets the full URL)
- Share links through a different channel than you'd share the secret itself
- Use short expiration times when possible
- Verify the recipient received and viewed the secret
- For highly sensitive data, consider additional verification steps
Shawn Lehner
I built Burnvelope because I was tired of the friction and risk involved in sharing sensitive information. As a developer, I frequently need to share API keys, credentials, and other secrets with team members—and I wanted a solution I could actually trust.
This project is a labor of love, built with security and privacy as the core principles. It's free, open source, and always will be.
Contributions are welcome! Please feel free to submit issues and pull requests.
MIT License — see LICENSE for details.