Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Repository files navigation

krypt.li — Zero-Knowledge Encrypted Pastebin

A self-hosted, end-to-end encrypted pastebin where the server never sees your data. All encryption and decryption happens entirely in your browser. Inspired by Mega.nz's key-in-fragment approach.


Security Model

How it works

  1. Client generates a random 256-bit master key using crypto.getRandomValues()
  2. Argon2id derives an AES-256-GCM key from the master key + random 128-bit salt (t=3, m=64MB, p=1)
  3. Content is encrypted client-side using AES-256-GCM with a random 96-bit IV
  4. Only ciphertext, IV, and salt are sent to the server — the key never leaves the browser
  5. The master key is placed in the URL fragment (/p/{id}#{key}). Per RFC 3986 §3.5, fragments are never transmitted in HTTP requests
  6. The recipient's browser decodes the key from the fragment and decrypts locally

Optional password protection adds a second factor: Argon2id derives the AES key from masterKey || utf8(password). Without the correct password, decryption fails with an AES-GCM authentication error — no partial information is revealed.

What the server stores

Field Content
id Random 128-bit URL-safe identifier
encrypted_data AES-256-GCM ciphertext (base64url)
iv Random 96-bit IV (base64url)
salt Random 128-bit Argon2id salt (base64url)
delete_token Random 192-bit token for paste deletion
has_password Boolean — whether a password is required
burn_after_read Boolean — delete on first access
created_at Unix timestamp
expires_at Optional expiry timestamp

The server has zero ability to decrypt any paste. Even with full database access, an attacker cannot recover plaintext without the key from the URL fragment.

Security measures

Measure Implementation
CSP with per-request nonce script-src 'nonce-{random}' — blocks inline script injection
Referrer-Policy: no-referrer Prevents key leakage via referrer headers
X-Frame-Options: DENY Clickjacking protection
X-Content-Type-Options: nosniff MIME sniffing protection
Permissions-Policy Camera, microphone, geolocation, payment all denied
Rate limiting 30 creates / 120 reads / 5 reports per 15-minute window, per IP
Timing-safe comparison crypto/hmac.Equal for all token verification
CSRF protection Origin vs Host check + Content-Type: application/json enforcement
Non-root container Runs as uid 65532 (distroless nonroot)
Read-only filesystem Container root is read-only; only data volume is writable
No URL fragment logging Server only logs r.URL.Path — query strings and fragments never appear in logs
Cache-Control: no-store Prevents caching of encrypted content

Features

  • AES-256-GCM encryption — authenticated, tamper-evident
  • Argon2id key derivation — GPU/ASIC resistant (t=3, m=64MB)
  • Optional password — second KDF factor, never sent to server
  • Burn after read — deleted server-side on first access
  • Custom expiry — 10 min to 1 year, or no expiry
  • Syntax highlighting — 25+ languages via highlight.js
  • Markdown rendering — with DOMPurify sanitization
  • Encrypted comments — E2E encrypted using the same derived key
  • Paste history — local browser history (localStorage), keys never leave the device
  • QR code — share pastes via QR on mobile
  • Delete link — destroy a paste early via a 192-bit token
  • Admin panel — report management, paste deletion
  • Prometheus metrics/metrics endpoint with Bearer token auth
  • Telegram notifications — abuse reports forwarded to a Telegram bot

Tech Stack

Layer Technology
Frontend React 18, Vite 6, React Router 6
Crypto (client) Web Crypto API (AES-256-GCM), Argon2id (WASM via argon2-browser)
Backend Go 1.23
HTTP Router chi v5
Rate Limiting go-chi/httprate
Database SQLite via modernc.org/sqlite (pure Go, no CGo)
Logging log/slog (structured JSON, stdlib)
Metrics prometheus/client_golang
Notifications Telegram Bot API (net/http, no external dependencies)
Container Multi-stage Docker build → gcr.io/distroless/static-debian12:nonroot
CI/CD GitHub Actions → GitHub Container Registry (GHCR)

The Go binary embeds the compiled frontend at build time (//go:embed). The production container contains a single binary — no Node.js runtime, no npm, no shell.


Quick Start

git clone <your-repo> kryptli && cd kryptli
cp .env.example .env
# Edit .env — set SITE_URL, ADMIN_TOKEN at minimum
docker compose up -d

The app is available at http://localhost:8881 by default.


Configuration

Variable Default Description
PORT 3001 Internal server port
DB_PATH /app/data/pastes.db SQLite database path
MAX_PASTE_SIZE 1048576 Maximum paste size in bytes (1 MB)
SITE_URL Public URL, used to build destroy links in notifications
ADMIN_TOKEN Bearer token for /api/admin/* endpoints
METRICS_TOKEN Bearer token for /metrics Prometheus endpoint
LOG_LEVEL info Log level: debug, info, warn, error
TELEGRAM_BOT_TOKEN Telegram Bot API token (optional)
TELEGRAM_CHAT_ID Telegram chat ID for abuse notifications (optional)

Generate strong tokens:

openssl rand -base64 32

Production Deployment with Caddy

HTTPS is required for crypto.subtle in browsers. Example Caddyfile:

paste.example.com {
    reverse_proxy krypt:3001
}
# docker-compose.yml (excerpt)
services:
  krypt:
    expose:
      - "3001"   # remove the ports: mapping when behind a reverse proxy

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
      - caddy-config:/config

volumes:
  caddy-data:
  caddy-config:

Development

Option A — test on port 3001 (Go serves the frontend directly)

# 1. Build the frontend
cd frontend && npm install && npm run build

# 2. Start the Go backend (separate terminal)
cd backend-go
FRONTEND_DIR=../frontend/dist DB_PATH=./data/pastes.db go run .

Open http://localhost:3001. FRONTEND_DIR is required here because go run . (without the prod build tag) compiles an empty embedded FS — the Go binary has no frontend of its own and reads it from the filesystem instead.

Option B — Vite dev server with hot reload

# 1. Start the Go backend
cd backend-go && DB_PATH=./data/pastes.db go run .

# 2. Start the Vite dev server (separate terminal)
cd frontend && npm install && npm run dev

Open http://localhost:5173. Vite proxies all /api requests to the Go backend on :3001. No FRONTEND_DIR is needed — Vite serves the frontend itself.


Docker Image

The production image is built via GitHub Actions and pushed to GHCR on every push to main:

docker pull ghcr.io/ae3ch/krypt.li:main

The image is built for linux/amd64 and linux/arm64. The Go compiler cross-compiles natively (CGO_ENABLED=0), so no QEMU emulation is used during the build.

Image contents:

  • Single Go binary (~16 MB, includes embedded frontend + migrations)
  • ca-certificates and tzdata (from distroless base)
  • No shell, no package manager, no build tools

Approximate image size: ~18 MB


API

The REST API is consumed by the frontend and the CLI tool. All endpoints return JSON.

Method Path Description
POST /api/paste Create a paste
GET /api/paste/:id Read a paste (increments read count, burns if set)
GET /api/paste/:id/stats Read count without incrementing
DELETE /api/paste/:id?token=... Delete a paste
POST /api/paste/:id/comments Add an encrypted comment
GET /api/paste/:id/comments Fetch encrypted comments
POST /api/report Report a paste for abuse
GET /api/health Health check
GET /metrics Prometheus metrics (Bearer token required)
GET /api/admin/reports List abuse reports (admin)
POST /api/admin/reports/:id/delete-paste Delete a reported paste (admin)
DELETE /api/admin/reports/:id Dismiss a report (admin)
GET /api/admin/stats Aggregate statistics (admin)

License

MIT

About

krypt.li — Zero-Knowledge Encrypted Pastebin

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

Contributors

Languages