2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00
2026-03-21 18:51:02 +08:00

Xbin

汉语

Codecov CI CodeQL Deploy

Xbin is a PrivateBin-style, end-to-end encrypted pastebin built on Cloudflare Workers. It includes a browser app for creating and reading encrypted pastes, a modern JSON API, and a legacy PrivateBin-compatible surface for migration and interoperability.

Everything is encrypted in the browser before upload. The Worker stores encrypted payloads in R2, lifecycle metadata in D1, and uses Durable Objects plus Queues to handle burn-after-reading claims and background cleanup.

Highlights

  • Browser-side encryption with fragment-based share keys
  • Optional passwords, expiration, burn-after-reading links, and per-paste delete tokens
  • Plain text, syntax-highlighted code, and Markdown rendering
  • Encrypted attachment upload with download and preview support
  • Discussion threads for compatible pastes
  • Modern REST API under /api/v1/*
  • Legacy PrivateBin-compatible API support and filesystem import tooling
  • Cloudflare-native runtime built from Workers, D1, R2, Durable Objects, Queues, and cron cleanup

Security model

  1. The browser generates a random secret key and encrypts the payload before upload.
  2. The Worker stores only encrypted envelopes in R2 and metadata in D1.
  3. Share links use the query string for the paste id and the URL fragment for the decryption key, for example https://paste.example.com/?abcdef1234567890#secretKey. The fragment is never sent to the server.
  4. Optional passwords are combined with the fragment key client-side with PBKDF2 before decryption.
  5. Anyone with the full share URL can decrypt the paste. Delete tokens are separate and are only returned when a paste is created or imported.

Architecture

Component Responsibility
Cloudflare Worker HTTP API, asset delivery, SEO metadata rewriting, config endpoint, and import auth
Durable Object Serializes burn-after-reading claim and consume operations
D1 Paste and comment metadata, lifecycle state, hashed delete tokens, burn claims
R2 Encrypted paste and comment payload blobs
Queue Async purge of expired, deleted, and burned content
Cron trigger Releases stale burn claims and finds expired pastes every minute
assets/ SPA Encrypts, decrypts, renders, and shares pastes in the browser

Getting started

Prerequisites

  • Bun
  • A Cloudflare account with access to Workers, D1, R2, Queues, and Durable Objects
  • Wrangler authenticated locally if you plan to deploy from your machine

Install dependencies

bun install

Provision Cloudflare resources

Create your own resources before the first deploy, then replace the names and IDs in wrangler.jsonc.

bunx wrangler d1 create xbin
bunx wrangler r2 bucket create xbin-pastes
bunx wrangler queues create xbin-gc

Notes:

  • This repository already contains concrete D1 and bucket identifiers in wrangler.jsonc. If you are forking or deploying your own copy, replace them with values from your account.
  • You only need to provision D1, R2, and Queues manually. The Durable Object binding and SQLite-backed class are declared in wrangler.jsonc and are created as part of deployment and migration.
  • This repository sets workers_dev = false. Before deploying your own fork, either configure your own routes or custom domain in wrangler.jsonc, or set workers_dev = true so the app has a reachable hostname.
  • After changing bindings or environment variables, regenerate Worker types:
bun run cf-typegen

Configure local secrets

Copy .dev.vars.example to .dev.vars and fill in any optional secrets you plan to use:

Copy-Item .dev.vars.example .dev.vars

Available secrets:

  • TURNSTILE_SITE_KEY
  • TURNSTILE_SECRET_KEY
  • IMPORT_TOKEN

Most non-secret runtime defaults live in the vars section of wrangler.jsonc.

Run locally

bun run dev

Wrangler serves the SPA and API from the same Worker entrypoint.

Deploy with GitHub Actions

The default deployment path is GitHub Actions. For your own instance, start from a fork of xixu-me/xbin.

The release flow is:

  1. Add CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN as Actions secrets in your fork.
  2. Push to main in your fork.
  3. CI runs formatting, type-checking, Wrangler type validation, tests with coverage, and a Wrangler deploy dry run.
  4. If CI succeeds for that main push, Deploy publishes the exact revision that passed.

Pull requests still run CI, but they do not auto-deploy. You can also trigger the deploy workflow manually through workflow_dispatch in your fork.

The deploy workflow ultimately runs:

bunx wrangler deploy --keep-vars --message "GitHub Actions deploy for ${GITHUB_SHA}"

--keep-vars means existing remote Worker variables are preserved unless you intentionally change them in Cloudflare or in your deployment setup.

Deploy locally

If you need a manual deployment outside GitHub Actions:

bun run deploy

Useful commands

Command Purpose
bun run dev Run the Worker locally with Wrangler
bun run start Alias for local Wrangler development
bun run check Type-check the TypeScript codebase
bun run test Run the Worker integration test suite with Vitest
bun run test:coverage Run tests with Istanbul coverage output
bun run format Format the repository with Prettier
bun run format:check Verify formatting without changing files
bun run cf-typegen Refresh Worker binding types after config changes
bun run deploy Publish the Worker

Configuration

Variable Default Purpose
XBIN_APP_NAME Xbin Brand name shown in the UI and metadata
XBIN_APP_VERSION 1.0.0 Version surfaced by the app config endpoint and footer
XBIN_PROJECT_PAGE_URL https://github.com/xixu-me/xbin Project link shown in the UI
XBIN_BASE_PATH / Mount path used when building share URLs, canonical URLs, and sitemap links
XBIN_MAX_PASTE_BYTES 10000000 Maximum encrypted paste payload size in bytes
XBIN_DEFAULT_EXPIRATION 1hour Default expiration key used by the UI and API
XBIN_SUPPORTED_EXPIRATIONS 5min,10min,30min,1hour,3hour,6hour,12hour,1day,3day,1week Comma-separated list of expiration keys exposed by the app
XBIN_ENABLE_LEGACY_API true Enables the PrivateBin-compatible JSON API surface
XBIN_REQUIRE_TURNSTILE false Requires turnstileToken when creating pastes and comments
XBIN_BURN_CLAIM_TTL_SECONDS 120 How long a burn-after-reading claim stays reserved before being released
TURNSTILE_SITE_KEY unset Site key exposed to the client when Turnstile is enabled
TURNSTILE_SECRET_KEY unset Secret used by the Worker to verify Turnstile tokens
IMPORT_TOKEN unset Enables and protects the PrivateBin import endpoint

The config parser also understands 1month, 1year, and never expiration keys if you choose to expose them.

API overview

The write API accepts encrypted PrivateBin-style envelopes, not plaintext content. A minimal create request looks like this:

{
	"v": 2,
	"adata": [["iv", "salt", 100000, 256, 128, "aes", "gcm", "none"], "plaintext", 0, 0],
	"ct": "ciphertext",
	"meta": { "expire": "1day" }
}

Core endpoints:

Method Path Purpose
GET /api/v1/config Returns runtime UI and feature config
POST /api/v1/pastes Creates a paste and returns { id, shareUrl, deleteToken }
GET /api/v1/pastes/:id Returns the encrypted paste envelope and comments
DELETE /api/v1/pastes/:id Deletes a paste when given { "deleteToken": "..." }
POST /api/v1/pastes/:id/comments Creates a comment for a discussion-enabled paste
POST /api/v1/pastes/:id/consume Finalizes a burn-after-reading read with { "claimToken": "..." }
POST /api/v1/admin/import/privatebin Imports a PrivateBin filesystem bundle when authorized

Notes:

  • GET /api/v1/pastes/:id returns a claimToken for burn-after-reading pastes. The client must call /consume after a successful decrypt.
  • When Turnstile is enabled, include turnstileToken in the create-paste and create-comment request bodies.
  • Comments are disabled for burn-after-reading pastes.

PrivateBin compatibility and import

Xbin supports two compatibility paths:

  • Legacy JSON API calls are detected via X-Requested-With: JSONHttpRequest.
  • Older browser share URLs such as /api/v1/pastes?<pasteId> are served the SPA shell so the client can recover the share locally.

To import a filesystem export from PrivateBin, first set IMPORT_TOKEN for the Worker, then run:

bun run import:privatebin:fs -- --source /path/to/privatebin/data --base-url https://paste.example.com --token your-import-token --report ./import-report.json

The importer:

  • Walks *.php paste files and sibling .discussion/ directories
  • Preserves creation and expiration metadata when present
  • Skips already expired pastes
  • Returns a fresh deleteToken for each imported paste because Xbin generates new deletion credentials during import

Repository layout

Path Purpose
src/index.ts Worker entrypoint for HTTP routes, asset serving, cron cleanup, and queue processing
src/lib/ Config parsing, validation, data access, schema, and shared types
assets/ Browser app, HTML shell, CSS, and vendored client-side libraries
scripts/ One-off tooling such as the PrivateBin filesystem importer
migrations/ D1 schema migrations
test/ Worker integration and repository tests

Testing and quality

The repository uses Vitest with the Cloudflare Workers pool, TypeScript type-checking, Wrangler type validation, and Prettier formatting. CI also runs a Wrangler deploy dry run before the production workflow is allowed to publish. Codecov is configured with 95% project coverage and 90% patch coverage targets, so documentation aside, code changes should usually come with tests.

A local verification pass that matches the main CI quality gates looks like this:

bun run format:check
bun run check
bunx wrangler types --check
bun run test:coverage

License

This project is licensed under the GNU Affero General Public License v3.0. See LICENSE for the full text.

S
Description
PrivateBin-style, end-to-end encrypted pastebin built on Cloudflare Workers
Readme AGPL-3.0
1.1 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 58.4%
JavaScript 26.6%
HTML 11.3%
CSS 3.7%