Skip to content

Repository files navigation

Ghostbit

Ghostbit

Self-hosted, end-to-end encrypted paste service.
The server stores ciphertext only — it can never read your content.

A modern, privacy-first alternative to Pastebin and PrivateBin.

Documentation · Demo · CLI on PyPI · Self-hosting

PythonFastAPILicensePyPI


Screenshots

Decrypted paste view — content is decrypted in the browser, with copy, QR, raw, download, edit and delete actions

A paste after client-side decryption — the server only ever stored ciphertext.

Create paste — dark theme, with expiration, max views, burn-after-read, compression, password and webhook optionsCreate paste — light theme
Create a paste — darkCreate a paste — light

How it works

Ghostbit encrypts your content in the browser using the Web Crypto API before sending anything to the server. The decryption key lives exclusively in the URL fragment — it is never transmitted over the network.

https://paste.example.com/aB3kZx9m#KEY~DELETE_TOKEN
↑
never sent to the server
Paste typeKey sourceWhere the key lives
No passwordcrypto.subtle.generateKey()URL #fragment
With passwordPBKDF2-SHA256 (600k iter)User's memory

Features

  • True E2E encryption — AES-256-GCM, server sees ciphertext only
  • Burn after read — deleted permanently after the first view
  • Max views — auto-deleted after N reads
  • Expiration — from 5 minutes to 1 year
  • Password protection — client-side key derivation, password never leaves the browser
  • Webhook — POST notification on each read
  • Language detection — auto-detected from content or file extension
  • Markdown preview — rendered in-browser
  • CLIgbit command, pipe anything from your terminal
  • REST API — full API for automation and integrations
  • SQLite / Redis — swap storage backends with a single env var

CLI

pip install ghostbit-cli
# Paste from stdin
cat main.py | gbit
# Paste a file (language auto-detected)
gbit secrets.env --burn --expires 3600
# Password-protected (secure prompt)echo"db_pass=s3cr3t"| gbit -p
# Scripting
URL=$(cat deploy.sh | gbit --quiet)# Point to your instance
gbit config set server https://paste.example.com
# Shell completion (bash / zsh / fish)eval"$(gbit completion bash)"eval"$(gbit completion zsh)"
gbit completion fish |source

Self-hosting

Docker

docker pull stackopshq/ghostbit # Docker Hub
docker pull ghcr.io/stackopshq/ghostbit # GHCR
git clone https://github.com/stackopshq/ghostbit
cd ghostbit
cp .env.example .env
docker compose up -d

With Redis

STORAGE_BACKEND=redis docker compose --profile redis up -d
# With a password
STORAGE_BACKEND=redis REDIS_PASSWORD=mysecret docker compose --profile redis up -d

Podman Quadlet

Create /etc/containers/systemd/ghostbit.container (system-wide) or ~/.config/containers/systemd/ghostbit.container (rootless):

[Unit]Description=Ghostbit paste service
After=network-online.target
[Container]Image=ghcr.io/stackopshq/ghostbit:latest
PublishPort=8000:8000
Volume=ghostbit_data:/data
Environment=STORAGE_BACKEND=sqlite
Environment=SQLITE_PATH=/data/ghostbit.db
Environment=MAX_PASTE_SIZE=524288
Environment=PORT=8000
HealthCmd=wget -qO- http://127.0.0.1:8000/healthz || exit 1
HealthInterval=30s
HealthTimeout=5s
HealthStartPeriod=15s
HealthRetries=3
[Service]Restart=always
[Install]WantedBy=default.target
# Reload systemd and start
systemctl --user daemon-reload
systemctl --user enable --now ghostbit

For Redis, add a ghostbit-redis.container alongside and use After=ghostbit-redis.service + Environment=STORAGE_BACKEND=redis + Environment=REDIS_URL=redis://ghostbit-redis:6379. Podman Quadlet handles the pod networking automatically.


Encrypted backups

scripts/backup.sh streams python -m app.admin export through age and writes one timestamped .jsonl.age file per run. The plaintext export never touches disk — a stolen backup file is useless without the age private key.

One-shot:

BACKUP_DIR=/var/backups/ghostbit \
AGE_RECIPIENT="age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
scripts/backup.sh

Recurring via systemd — copy the two unit templates and adjust paths + recipient:

sudo cp scripts/ghostbit-backup.service /etc/systemd/system/
sudo cp scripts/ghostbit-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ghostbit-backup.timer
sudo systemctl list-timers ghostbit-backup.timer

Restore:

age --decrypt -i ~/.config/age/keys.txt ghostbit-2026-05-24T03-17-00Z.jsonl.age \
| python -m app.admin import

app.admin import --overwrite replaces existing IDs; without it, conflicts are skipped (safe re-runs).


Environment variables

VariableDefaultDescription
STORAGE_BACKENDsqlitesqlite or redis
SQLITE_PATH./ghostbit.dbSQLite file path (Docker overrides to /data/ghostbit.db)
SQLITE_POOL_SIZE5Pooled SQLite connections (WAL enables parallel readers)
REDIS_URLredis://localhost:6379Redis connection URL
REDIS_PASSWORDRedis password (injected into REDIS_URL automatically)
MAX_PASTE_SIZE524288Max paste size in bytes (512 KB)
PORT8000Server port
RATE_LIMIT_CREATE30/minuteRate limit for paste creation
RATE_LIMIT_VIEW120/minuteRate limit for paste viewing
TRUST_PROXY_HEADERSfalseUse rightmost X-Forwarded-For for rate limiting (enable only behind a trusted proxy)
BASE_URLPublic base URL (e.g. https://paste.example.com) for the absolute links in social-preview meta tags. Derived from the request when unset.
WEBHOOK_SECRETHMAC-SHA256 secret for signing webhook payloads

API

All content is encrypted client-side — the API only handles ciphertext. Interactive docs are available at /docs (Swagger UI) and /redoc (ReDoc). Operators also get /healthz (liveness — always 200 if the process is alive), /readyz (readiness — 503 if the storage backend doesn't answer) and /metrics (Prometheus exposition).

# Create (content must be pre-encrypted — use the CLI or e2e.js)
curl -X POST https://paste.example.com/api/v1/pastes \
-H "Content-Type: application/json" \
-d '{"content":"<base64 ciphertext>","nonce":"<base64 nonce>","language":"python"}'# Retrieve (returns ciphertext — client decrypts)
curl https://paste.example.com/api/v1/pastes/{id}
# Delete
curl -X DELETE https://paste.example.com/api/v1/pastes/{id} \
-H "X-Delete-Token: <token>"# Detect language (plaintext)
curl -X POST https://paste.example.com/api/v1/detect \
-H "Content-Type: application/json" \
-d '{"content":"def hello():\n print(42)"}'

Interactive Swagger UI: /docs — ReDoc: /redoc.


Local development

git clone https://github.com/stackopshq/ghostbit
cd ghostbit
python3 -m venv .venv &&source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload --port 8000

Open http://localhost:8000. SQLite is used by default, no external service required.

Running tests

pip install -r requirements-dev.txt
pytest tests/ -v

Pre-commit hooks

Install once per clone to run ruff, gitleaks and a few hygiene checks on every commit:

pip install pre-commit
pre-commit install

The same checks run in CI — installing the hook just gives you the feedback locally before the push.


Security

Ghostbit follows a zero-knowledge architecture:

Server seesServer cannot see
Paste contentAES-256-GCM ciphertextPlaintext
Encryption keyNever (stays in URL #fragment)
PasswordNever (PBKDF2 runs in browser/CLI)
Delete tokenSHA-256 hash onlyPlaintext token
MetadataLanguage, timestamps, view count
  • The URL #fragment is never sent to the server by any browser.
  • A compromised server cannot decrypt any paste — past or future.
  • SSRF protection blocks webhooks to private/internal networks.
  • Rate limiting protects against abuse on all endpoints.

If you discover a security vulnerability, please report it responsibly via GitHub Security Advisories.


Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Commit with clear messages (git commit -m "feat: add X")
  4. Push and open a Pull Request

Please ensure:

  • All existing tests pass (pytest tests/ -v)
  • New features include tests when applicable
  • Code follows the existing style (no linter enforced, just be consistent)

License

MIT

About

Ghostbit — Secure, ephemeral, and encrypted code sharing for developers. Share snippets that vanish after use. 👻 💻

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages