Zero-knowledge .env sharing — encrypted in your browser, viewed once, then gone forever.
Sharing secrets over Slack, email, or sticky notes is a disaster waiting to happen. envs.rip gives you a one-time link that burns after reading — and the server never sees the plaintext.
- Zero knowledge — encryption and decryption happen entirely in the browser using AES-256-GCM
- One-time retrieval — the secret is atomically deleted (
GETDEL) the moment it's read - Key never leaves the URL fragment — the
#portion is never sent to the server - Auto-expiry — choose 1 hour, 24 hours, or 7 days; Redis TTL handles the rest
Sender Server (Redis) Recipient
────── ────────────── ─────────
│ │ │
│ 1. Paste .env content │ │
│ 2. Browser generates │ │
│ AES-256-GCM key │ │
│ 3. Encrypt in-browser │ │
│ │ │
│ ── POST /api/secret ────────► │ │
│ { ciphertext, iv } │ 4. Store blob with TTL │
│ ◄── { id } ───────────────── │ │
│ │ │
│ 5. Build link: │ │
│ /s/{id}#{key} │ │
│ ─────────────────────────────────────────────────────────►│
│ │ │
│ │ ◄── GET /api/secret/{id} ── │
│ │ GETDEL (atomic) │
│ │ ── { ciphertext, iv } ────► │
│ │ │
│ │ 6. Secret deleted from │ 7. Decrypt in-browser
│ │ Redis permanently │ using key from #fragment
│ │ │
The decryption key lives exclusively in the URL fragment (
#). Browsers never send fragments to the server — so even if the server is compromised, your secrets remain encrypted.
src/
├── app/
│ ├── page.tsx # Create page — paste, encrypt, generate link
│ ├── s/[id]/page.tsx # Receive page — fetch, decrypt, display
│ ├── burned/page.tsx # "Already viewed" state
│ ├── expired/page.tsx # "Link expired" state
│ ├── not-found.tsx # 404
│ ├── api/
│ │ └── secret/
│ │ ├── route.ts # POST — store encrypted blob with TTL
│ │ └── [id]/route.ts # GET — atomic fetch + delete (GETDEL)
│ ├── layout.tsx # Root layout, dark theme, Geist fonts
│ └── globals.css # Tailwind v4 + custom animations
├── components/
│ ├── CopyButton.tsx # One-click copy to clipboard
│ └── ExpirySelector.tsx # TTL picker (1h / 24h / 7d)
└── lib/
├── crypto.ts # AES-256-GCM encrypt/decrypt (Web Crypto API)
└── redis.ts # Upstash Redis client
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React Server Components) |
| Styling | Tailwind CSS v4 |
| Encryption | Web Crypto API — AES-GCM 256-bit |
| Storage | Upstash Redis (serverless, REST API) |
| Runtime | Node.js, React 19, TypeScript 5 |
| Deployment | Vercel (serverless) |
- Node.js 18+ (22 recommended)
- npm 9+
- An Upstash Redis database (free tier works great)
npm installCreate a .env.local file in the project root:
UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-herenpm run devOpen http://localhost:3000 — paste a secret, generate a link, and try opening it.
| Command | Description |
|---|---|
npm run dev |
Start development server (port 3000) |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Run ESLint |
| Property | Implementation |
|---|---|
| Encryption algorithm | AES-256-GCM via Web Crypto API |
| Key generation | crypto.subtle.generateKey() — 256-bit random key |
| IV | 12-byte random via crypto.getRandomValues() |
| Key transport | URL fragment (#) — never sent to server |
| Storage | Encrypted ciphertext + IV only; server never sees plaintext or key |
| Retrieval | Atomic GETDEL — read once, then permanently deleted |
| Expiry | Redis TTL — auto-deleted after 1 hour, 24 hours, or 7 days |
| Payload limit | 100 KB max ciphertext |
| CSP | Strict Content-Security-Policy headers on secret pages |
- Push this repo to GitHub
- Import the project in Vercel
- Add environment variables in Project → Settings → Environment Variables:
UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN
- Deploy
That's it. Vercel handles the rest.
MIT