Sign inSign up

thodadev/shhh

By thodadev

Updated 6 days ago

Self-hostable zero-knowledge pastebin. AES-256-GCM in the browser; the server never sees the key.

Image
Security
Developer tools
0

1.8K

thodadev/shhh repository overview

shhh

Say it once. We'll forget.

A self-hostable, zero-knowledge pastebin for sharing secrets — text and files — that expire on their own. The server stores ciphertext and nothing else: the decryption key lives in the URL fragment and is never sent to it.

Try the demo · Documentation · Source and issues

The demo is a public instance with short retention and tight limits, reset without warning.

Why

Most "send me the password" moments end in a chat message that stays there forever. shhh is for the other path: paste it, get a link, and have it disappear after a set number of reads or a deadline — without trusting the server with the contents.

  • Zero-knowledge. AES-256-GCM in the browser. The key travels in the URL fragment (#key=…), which browsers never send to the server. Optional password protection derives a second key layer with Argon2id, combined with the fragment key.
  • Expiry that actually expires. Every paste has a deadline and, optionally, a maximum number of reads. Spending one takes proof of the key, so knowing a paste's id is not enough to burn it, and a mistyped password costs nothing.
  • Link previews don't burn your secret. Reading is a two-step flow: metadata first, ciphertext only on an explicit click. Chat unfurlers and antivirus scanners that follow the link don't consume it.
  • Yours to run. Every limit — retention, sizes, quotas, rate limits, who may register — is configured from the admin dashboard, not baked into the image.

Also in the box: two-factor authentication (TOTP) with backup codes, invitations, an IP allowlist and blocklist, email sharing at creation time, Cloudflare Turnstile, per-IP and per-account rate limiting, account deletion with full cascade, English and French.

Tags

TagMoves
latestnewest release, never a pre-release
1newest 1.x — fixes and features, never a breaking 2.0
1.0newest 1.0.x — fixes only
1.0.0exactly that release, immutable

Built for linux/amd64 and linux/arm64.

The documentation site ships as a separate image, thodadev/shhh-docs, tagged with the same version so the two always match. It needs no database and no configuration — run it if you want the docs on your own network, otherwise read them at the link above.

Quick start

# docker-compose.yml
name: shhh

services:
  app:
    image: thodadev/shhh:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://shhh:CHANGE_ME@db:5432/shhh
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?generate with openssl rand -base64 32}
      BETTER_AUTH_URL: https://shhh.example.com
      # Proxies you control in front of the app. 0 ignores X-Forwarded-For and uses the connection
      # address; set it to 1 behind a single reverse proxy, 2 behind Cloudflare plus your own.
      TRUSTED_PROXY_DEPTH: 1
      NUXT_PUBLIC_TURNSTILE_SITE_KEY: ${NUXT_PUBLIC_TURNSTILE_SITE_KEY:-}
      NUXT_TURNSTILE_SECRET_KEY: ${NUXT_TURNSTILE_SECRET_KEY:-}
      MAIL_PROVIDER: none
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: shhh
      POSTGRES_PASSWORD: CHANGE_ME
      POSTGRES_DB: shhh
    volumes:
      - db-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U shhh -d shhh"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  db-data:
docker compose up -d

Open your instance and the setup wizard creates the super admin account and the initial limits. Migrations are applied automatically on every boot.

An installer that generates the secrets and writes the .env for you lives in the repository:

curl -fsSLO https://raw.githubusercontent.com/thoda-dev/shhh/master/install.sh
less install.sh && sh install.sh

Configuration

Secrets and infrastructure live in the environment; everything else lives in the database and is edited from /admin/settings — retention, max reads, sizes, quotas, rate limits, public registration, anonymous pastes, enforced 2FA, invitation expiry.

Variable
DATABASE_URLRequired. PostgreSQL connection string.
BETTER_AUTH_SECRETRequired. Signs session cookies — openssl rand -base64 32. Changing it logs everybody out.
BETTER_AUTH_URLRequired. Public URL of the instance, no trailing slash. Also builds the links sent by email.
NUXT_PUBLIC_TURNSTILE_SITE_KEY NUXT_TURNSTILE_SECRET_KEYCloudflare Turnstile. Paste creation is rejected without a valid token, signed-in users included, so an instance without these can be read but not written to.
MAIL_PROVIDERnone, resend or smtp. none is supported: the instance works, but email verification, password resets, invitations and email sharing are unavailable.
MAIL_FROM, RESEND_API_KEY, MAIL_SMTP_*Depending on the provider.
PORTHost port. The container always listens on 3000.

Behind a reverse proxy

Terminate TLS at your proxy and set BETTER_AUTH_URL to the public HTTPS address. The proxy must overwrite X-Forwarded-For rather than append to a client-supplied value — rate limiting and IP banning both trust that header.

Monitoring

GET /api/health returns database status, mail provider and storage usage. Point Uptime Kuma at it. The image also declares a HEALTHCHECK against it, so docker ps reports an instance that has lost its database as unhealthy rather than merely running.

Security

The threat model, its boundaries, and the one place the decryption key deliberately reaches the server (email sharing at creation time) are documented in SECURITY.md.

Licence

MIT — © 2026 Thoda

Tag summary

Content type

Image

Digest

sha256:8ddc7a662

Size

62.5 MB

Last updated

6 days ago

docker pull thodadev/shhh