All notable changes to copypaste.fyi are documented here. The project follows Keep a Changelog and semantic versioning.
- First principles and packaging notes: docs/first-principles.md,
docs/packaging.md.
scripts/build-deb.shturns a release binary into a.debwith the systemd unit. copypaste clipreads the OS clipboard, stores it on your server, and puts the share URL back. iTerm2, Ghostty, and cmux snippets live in docs/terminals.md.- After Get link, Make short link mints a 10-character A-Z a-z 0-9 alias via
POST /api/pastes/{id}/alias. - Open-with uses Android
intent://and iOS app schemes for Grok, ChatGPT, Codex, Claude, Gemini, Copilot, and Perplexity./p/{id}?open=grokhands off the public URL only. - MCP tools
mint_aliasandhealth, pluscopypaste://discoveryand share/read prompts. Chrome and Edge can install the site and share text into the composer.
- Short is gone from the composer. The long id is the default.
- Stats stay admin-gated. Referrer, device, OS, and country stay on that page.
See git history for the 0.2.0 cut. Highlights below stay as the running log.
- Self-host helper on About now has a one-tap Copy for the recipe commands.
- Added
copypaste healthcheckso distroless images can probeGET /api/healthin Docker exec form without a shell or curl. - Added exact-ID, metadata-only moderation at
GET /api/admin/pastes/{id}and targeted deletion atDELETE /api/admin/pastes/{id}. The moderation response excludes paste content, ciphertext, keys, nonces, salts, raw workspace labels, attestation secrets, ownership tokens, and webhook targets. - Added a deployment quarantine through
COPYPASTE_BLOCKED_PASTE_IDS. The complete configured set blocks JSON, share, legacy HTML, raw, update, finalize, and anchor routes before public storage access while leaving administrator metadata triage available. - Added content-free moderation audit events containing only the admin key ID, action, and outcome. Raw paste IDs, derived targets, and access counts are not written to those audit records.
- Added the operator abuse-response runbook, including authoritative-list handling, credential retention, no-content triage, full-instance rollout, escalation, and cross-instance deletion limits.
- Added file-backed dynamic API keys through
COPYPASTE_SQLITE_PATH. On Unix, the parent directory must be owner-controlled mode0700; the SQLite file is mode0600, stores Argon2id hashes, and fails startup when an explicitly configured path is unsafe. Other platforms require equivalent operator-managed owner-only ACLs. - Added the canonical
/p/{id}share route and/raw/{id}raw route while retaining the legacy/{id}HTML route. - Added CLI
serve,send, andconfig initsubcommands.sendreads write credentials from--auth-token-fileorCOPYPASTE_AUTH_TOKENand encryption keys from--encryption-key-fileorCOPYPASTE_ENCRYPTION_KEY.
copypaste send --json/--agentreceipts now map a legacy/{id}create path toGET /api/pastes/{id}so thegetfield is the JSON read URL, not the HTML page.- Self-host cookbook now uses
--auth-token-file(the real CLI flag) instead of--write-token-file. - Docker and Compose now detect an unresponsive
copypasteprocess. The distroless image and Compose service probeGET /api/healththroughcopypaste healthcheck(exec form, no shell).
- Rate-limited responses now include
Retry-After: 60and expose that header to allowlisted CORS origins so clients can wait out the fixed 60-second window. - Introduced
X-CopyPaste-Write-Tokento separate service admission from optional signed-session identity inAuthorization. Creation retains a legacy service-credential fallback inAuthorization; current clients use the dedicated header. Live mutations require service admission and the paste ownership token in separate headers. - Enforced 43 to 128 base64url characters for non-empty production
COPYPASTE_AUTH_TOKENandCOPYPASTE_ADMIN_TOKENvalues. Invalid static credentials now stop startup; operators should generate at least 256 random bits before encoding. - Configured the supplied Fly deployment to require write admission, reject session-only writes, use strict supported-algorithm verification, and use the Redis persistence backend.
- Kept
COPYPASTE_ADMIN_TOKENas the bootstrap moderation credential. The current Fly configuration has no SQLite path, so dynamic key-management endpoints fail closed with503instead of creating process-only credentials. - Made issued login challenges short-lived, bounded, server-stored, single-use, and atomically consumed. Signed but unissued or replayed challenges are rejected.
- Replaced enumerable paste identifiers with CSPRNG-backed 24-character identifiers carrying 144 bits of entropy. Existing IDs remain readable.
- Added strict paste-ID validation for quarantine and moderation inputs. Invalid quarantine configuration stops startup.
- Changed configured persistence to fail startup when initialization fails. The server no longer silently falls back to memory after an explicit backend selection.
- Changed durable creates, updates, finalization, and deletes to complete the backing-store write
before publishing the matching cache change or acknowledging success. Storage load failures now
surface as
503rather than404. - Serialized update, finalize, delete, and cache-fill operations per paste ID within one process. This prevents local reorder, reopening after local finalization, and local in-flight save resurrection after delete.
- Changed the Upstash adapter to send
SET,SETEX,GET, andDELas JSON command arrays in POST bodies with bearer authentication. Serialized records and content never appear in URL paths. - Restricted the Upstash origin to clean HTTPS, with loopback HTTP only for local use. Redirects are disabled; connect/request timeouts and bounded response reads are enforced; provider response bodies are not reflected in public errors.
- Added exact Tor ingress authentication. Tor-only access requires both the configured onion
Hostand a constant-time match onX-Copypaste-Onion-Ingress. The onion host and token must be configured as a valid pair, or startup fails.X-Forwarded-Hostis never trusted. - Reduced public health responses to versioned liveness data. They do not expose dependency state, counts, internal URLs, or upstream errors.
- Sanitized server-rendered Markdown; tightened CORS, security headers, cache controls, crawler directives, request sizes, and process-local rate limits.
- Stopped loading the code editor from a third-party CDN and removed the third-party interactive API documentation runtime. The self-contained OpenAPI JSON remains available.
- Replaced frontend
Math.random()key generation with 256-bit Web Crypto keys. The frontend removes legacy query-string keys from navigation and uses URL fragments plusX-Paste-Key. The backend query parameter remains only for compatibility. - Made CLI requests reject redirects, require HTTPS for non-loopback origins, bound response sizes, and print key-free share links. The CLI does not accept credentials or encryption keys in argv.
- Added Rust and npm security-audit checks to CI.
- Raised the documented and CI Rust toolchain floor to 1.88 to match the resolved dependency set.
- Corrected the product security model: encryption runs in the Rust service. Clients must use TLS to a trusted edge, but plaintext and the supplied key may traverse the edge-to-Rocket hop and exist in memory. Keys are not intentionally persisted, and the service is not browser-only, end-to-end, or zero-knowledge.
- Limited the supported paste persistence backends to process memory and Upstash Redis REST. Redis
is selected with
COPYPASTE_PERSISTENCE_BACKEND=redisand configured withUPSTASH_REDIS_REST_URLplusUPSTASH_REDIS_REST_TOKEN. - Changed the JSON time-lock response to
423 Lockedwhile outside the configured access window. Public JSON, HTML, and raw reads treat retention expiry the same as a missing paste (404). Update, finalize, and admin routes return410while an expired record is still observable. Raw reads after the time-lock window also return410. - Restricted anchoring to administrators and removed plaintext content commitments. Plaintext manifests contain content-free metadata only. Encrypted manifests commit, with domain separation, to randomized encrypted storage fields rather than deterministic plaintext hashes.
- Clarified verifier coverage. The OCaml service supports AES-256-GCM, ChaCha20-Poly1305, and Ed25519 signatures. XChaCha20-Poly1305 and ML-KEM remain Rust-only. Strict mode fails supported encryption creation on verifier transport, HTTP, parsing, size, or validation failure.
- Documented that supported encryption verification sends plaintext, the supplied key, ciphertext, nonce, and salt to the verifier. On Fly, this application-layer HTTP request crosses the private network to a separate VM, which expands the plaintext trust boundary.
- Configured Fly
appandcrypto-verifieras separate process-group VMs connected through private DNS. They do not share localhost or a filesystem. - Documented
/healthand/api/healthas liveness only. Storage and verifier readiness require separate monitoring.
- Disabled bundles, attestations, webhooks, and steganography in the hardened build. New requests
for these features are rejected. Setting former allow flags to
trueis unsupported and stops startup; bundles have no enable flag. - Disabled client-selected external paste persistence. Storage is controlled by the deployment.
- Burn-after-reading is best-effort. Link previews can consume a paste, and concurrent app instances can read the same record before deletion. The current Redis path does not provide an atomic cross-instance consume.
- Per-ID mutation locks are process-local. A stale app instance can save an older live record after another instance deletes it. The service has no distributed version or tombstone.
- Administrator deletion confirms only the handling instance and its configured backing-store request. It cannot prove that another instance has no cached copy or in-flight save. Operators must publish the authoritative quarantine set, roll every app instance before deletion, and keep the target quarantined.
- Sessions and challenges are process-local and disappear on restart. User/workspace listings, statistics, and cached paste-ID indexes do not enumerate the full Redis dataset. Run one app instance until these states are shared.
- Per-IP limits are process-local. Public deployments still need shared edge quotas, bot controls, a monitored abuse channel, and qualified legal review.
- The experimental ML-KEM/passphrase envelope has not received independent cryptographic review or FIPS validation. Low-entropy passphrases remain vulnerable to offline guessing.
- End-to-end browser encryption is not implemented. The server sees plaintext and keys during encryption and decryption.