docs: design for optional client-side E2E encryption (issue #39) #75

Merged
poslop merged 2 commits from issue-39-e2e-design into main 2026-09-09 14:26:02 +00:00
Owner

Design-only PR for issue #39 — no implementation.

Doc: docs/design/e2e-encryption.md

Recommendation: build it, opt-in, passphrase mode only in v1.

  • Crypto: AES-256-GCM (WebCrypto) with a per-paste random DEK, wrapped by a key derived from the passphrase via PBKDF2-HMAC-SHA-256 (600k iterations, 16-byte salt). Argon2id deferred to v2 (no WebCrypto support; envelope v field reserves the migration path).
  • API: additive — optional encryption KDF metadata on create/get, has_encryption flag in list views, language: "encrypted"|"null", raw endpoint serves the envelope as an attachment. Server treats content as opaque bytes; 413 applies to envelope bytes.
  • Feature interplay: burn-after-read still works (charged on fetch, exactly as today — wrong-key viewers burn a read, retries are client-side); search, server-side language detection and highlighting are impossible on encrypted pastes and are skipped/done client-side; expiry/delete/tokens/cans unchanged.
  • Key sharing UX: passphrase out-of-band is the default; fragment-key sharing is opt-in with a warning; Referrer-Policy: no-referrer recommended.
  • Explicit threat model: protects against passive server compromise (DB/backup leaks), not against a malicious server swapping its own JS.

Server cost is small (pass-through, skip detection/indexing, list flag); all crypto is client-side WebCrypto with no new dependencies. Closes nothing yet — implementation to follow if the design is accepted.

Design-only PR for issue #39 — no implementation. **Doc:** `docs/design/e2e-encryption.md` Recommendation: build it, opt-in, passphrase mode only in v1. - **Crypto:** AES-256-GCM (WebCrypto) with a per-paste random DEK, wrapped by a key derived from the passphrase via PBKDF2-HMAC-SHA-256 (600k iterations, 16-byte salt). Argon2id deferred to v2 (no WebCrypto support; envelope `v` field reserves the migration path). - **API:** additive — optional `encryption` KDF metadata on create/get, `has_encryption` flag in list views, `language: "encrypted"|"null"`, raw endpoint serves the envelope as an attachment. Server treats content as opaque bytes; 413 applies to envelope bytes. - **Feature interplay:** burn-after-read still works (charged on fetch, exactly as today — wrong-key viewers burn a read, retries are client-side); search, server-side language detection and highlighting are impossible on encrypted pastes and are skipped/done client-side; expiry/delete/tokens/cans unchanged. - **Key sharing UX:** passphrase out-of-band is the default; fragment-key sharing is opt-in with a warning; `Referrer-Policy: no-referrer` recommended. - **Explicit threat model:** protects against passive server compromise (DB/backup leaks), not against a malicious server swapping its own JS. Server cost is small (pass-through, skip detection/indexing, list flag); all crypto is client-side WebCrypto with no new dependencies. Closes nothing yet — implementation to follow if the design is accepted.
poslop added 2 commits 2026-09-09 14:16:55 +00:00
Exclude password-protected pastes from public listings (#65)
CI / test (pull_request) Successful in 20s
CI / docker (pull_request) Skipped
f6c4342468
ListPublic and its COUNT query now filter password_hash IS NULL, so
/api/public (and any page backed by it) no longer leaks metadata
(title, slug, existence) of password-protected pastes. Unlisted
pastes were already excluded. Adds regression test covering both.
docs: design for optional client-side E2E encryption (issue #39)
CI / test (pull_request) Successful in 25s
CI / docker (pull_request) Skipped
2159982795
poslop merged commit b7d1119d6d into main 2026-09-09 14:26:02 +00:00
Sign in to join this conversation.