| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
This is a simple pastebin service implemented as a Cloudflare Worker using Hono framework. It supports storing text content in R2 bucket storage with API endpoints for creating, retrieving and managing pastes.
This project works as a Nix package:
cd ~/Documents/git/pastebin-r2
nix developyarn buildyarn devyarn deployBuild using Nix directly without entering shell:
nix build .#packages.defaultPastes expire automatically. The lifetime is stamped into the object's custom metadata when it is written, enforced on every read, and an hourly cron trigger ([triggers] in wrangler.toml) sweeps expired objects out of the bucket.
The default and the maximum are both 48 hours. A shorter lifetime can be requested with the ttl query parameter (or the X-Expires-In header) on create/update, as plain seconds or with an s/m/h/d suffix. Values above 48 hours are clamped down, and the minimum is 60 seconds.
curl --data-binary @- 'https://example.com/?ttl=2h' < file.txt
curl -H 'X-Expires-In: 900' --data-binary @- https://example.com < file.txtEncryption is optional and entirely client-side, like the original pastebin-worker's ChaCha20-Poly1305 mode but without sending the key to the server. Tick encrypt: on in the web UI (or add ?enc=1 to a POST /) and the browser encrypts the paste before upload. The worker only stores the ciphertext plus an enc marker in R2 custom metadata.
The key travels in the URL fragment:
https://example.com/view/Ab3xZ#k=<base64url-key>
For an attached file, the original filename is preserved client-side in the same fragment (&n=<url-encoded-name>), so the viewer can download the decrypted bytes under their original name:
https://example.com/view/Ab3xZ#k=<base64url-key>&n=report.pdf
Fragments are never sent in HTTP requests, so the server, Cloudflare logs, and R2 never see the key, filename, or plaintext.
Opening that link runs the viewer, which fetches /info/:id and /Ab3xZ, reads the fragment locally, and decrypts before rendering or downloading. Binary files show a "use download" notice instead of being forced through a text decoder. Without the key the viewer shows an unlock field instead.
The web UI encrypts attached files as raw bytes when encrypt: on; the textarea and any client that POSTs bytes directly work the same way. With encryption off the attach button keeps its original behavior (load file text into the editor).
Keep the full URL. If the part after # is lost the paste cannot be recovered. Anyone who has the link can read it.
Format v1 (stored in R2 verbatim):
| offset | size | field |
|---|---|---|
| 0 | 4 | magic PBR2 |
| 4 | 1 | format version 0x01 |
| 5 | 12 | random nonce |
| 17 | N | ChaCha20-Poly1305 ciphertext followed by the 16-byte tag |
The optional original filename is not part of the container; it stays in the URL fragment and is never uploaded.
The AAD is the 5-byte header (magic + version). The nonce is read from the container and is cryptographically bound because the Poly1305 tag is computed under it, so a changed header, nonce, or ciphertext byte fails authentication. There is no compression in v1; ciphertext is plaintext length plus 33 bytes.
CLI helper (useful for curl and non-browser clients):
KEY=$(node scripts/paste-crypt.mjs keygen)
node scripts/paste-crypt.mjs encrypt file.txt --key "$KEY" > /tmp/body.bin
URL=$(curl -s --data-binary @/tmp/body.bin 'https://example.com/?enc=1')
echo "$URL#k=$KEY"Decrypt an encrypted raw paste locally:
curl -s 'https://example.com/text/Ab3xZ' | node scripts/paste-crypt.mjs decrypt - --key "$KEY"What it protects: stored R2 bytes and request logs never contain the plaintext or key. What it does not protect: anyone holding the link can decrypt it; a compromised Worker deployment could serve modified viewer code; metadata (size, upload time, expiry) is still visible; there is no key recovery, password mode, or padding.
| Back | FazBrowse Home | New Git URL |