Skip to content

izm1chael/goash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

goash

CI Release License: MIT Go Report Docker

A tiny self-hosted "burn after reading" secret-drop server. Paste a secret, get a one-time URL, send the URL via Teams / Signal / email — the secret is destroyed on first read (or after TTL).

  • Zero-knowledge. Encryption runs in the browser; the server only ever stores ciphertext. A memory dump of the server reveals nothing decryptable.
  • No disk. Secrets live in sync.Map. Restarting the container wipes everything — by design.
  • Opt-in gating. Optional passphrase, configurable TTL + view count, IP/geo allowlist, self-share passkey mode.

Run

go run ./cmd/goash
# open http://localhost:8080

Built and run inside Docker:

docker build -t goash .
docker run --rm -p 8080:8080 -e GOASH_BASE_URL=http://localhost:8080 goash

Configuration

All knobs are environment variables. Sensible defaults make the bare command work.

Variable Default Purpose
GOASH_LISTEN 127.0.0.1:8080 Bind address
GOASH_BASE_URL http://localhost:8080 Externally-visible URL — appears in share links and is the WebAuthn origin
GOASH_RP_ID hostname of GOASH_BASE_URL WebAuthn relying-party ID override
GOASH_TRUSTED_PROXY_CIDRS (empty) When set, X-Forwarded-For is honored for peers in these ranges
GOASH_GEOIP_PATH (empty) Path to a MaxMind GeoLite2-Country .mmdb — enables the country-code allowlist
GOASH_GEOIP_LICENSE_KEY (empty) MaxMind account license key. When set, goash fetches and refreshes the mmdb automatically
GOASH_GEOIP_EDITION GeoLite2-Country MaxMind edition to fetch
GOASH_GEOIP_REFRESH_INTERVAL 24h How often the background updater refreshes the mmdb
GOASH_MAX_SECRETS 10000 Hard cap on live in-memory secrets; new creates return 503 when full
GOASH_MAX_WEBAUTHN_SESSIONS 1000 Hard cap on in-flight WebAuthn ceremonies
GOASH_MAX_SECRET_BYTES 65536 Cap on ciphertext size
GOASH_RATE_LIMIT_PER_MIN 30 Token-bucket cap on /api/secrets

Security model

The server is the wrong place to attack. The threats it does defend against:

  • Server compromise reveals plaintext. It doesn't — every secret is AES-GCM-encrypted in the browser. In passphrase mode, the key is derived from the passphrase via PBKDF2 (600k iterations, two independent salts: one for the AES key, one for the server-side auth check). In the no-passphrase mode, the key is randomly generated and embedded in the URL fragment, which never reaches the server.
  • Brute-forcing the passphrase online. The secret burns after a configurable number of wrong attempts (default 3).
  • Probing for live secret IDs. UUIDv4 IDs (~122 bits of entropy) plus uniform 404 responses for missing, expired, off-network, and otherwise-blocked secrets.

The server cannot defend against:

  • The recipient screenshotting / copying the revealed plaintext.
  • A network observer between the client and the server when not using TLS. Always run behind Caddy / Traefik / a similar reverse proxy in production.

TLS / proxy setup

goash binds to 127.0.0.1 by default. Front it with Caddy:

secret.example.com {
    reverse_proxy 127.0.0.1:8080
}

If your reverse proxy adds X-Forwarded-For, set GOASH_TRUSTED_PROXY_CIDRS to its IP/CIDR so the IP allowlist sees real client addresses.

GeoIP database

Country-code allowlists need a MaxMind GeoLite2-Country mmdb. Two ways to provision it:

Self-managed. Download the mmdb (free, requires a MaxMind account) and mount it into the container:

docker run --rm -p 8080:8080 \
  -e GOASH_GEOIP_PATH=/data/GeoLite2-Country.mmdb \
  -v /host/path/GeoLite2-Country.mmdb:/data/GeoLite2-Country.mmdb:ro \
  goash

Auto-update. Set GOASH_GEOIP_LICENSE_KEY and goash will fetch the latest mmdb on startup (if missing or older than the refresh interval) and refresh it on a background loop:

docker run --rm -p 8080:8080 \
  -e GOASH_GEOIP_PATH=/data/GeoLite2-Country.mmdb \
  -e GOASH_GEOIP_LICENSE_KEY=xxxxxxxxxxxx \
  -e GOASH_GEOIP_REFRESH_INTERVAL=24h \
  -v geoip-data:/data \
  goash

The updater downloads the official tar.gz from download.maxmind.com, extracts the .mmdb, and atomically renames it into place. The running *ipcheck.Geo is hot-swapped behind a sync.RWMutex — no restart needed.

WebAuthn (self-share mode)

This mode is for ferrying secrets between your own devices (laptop → homelab) using a synced passkey provider (iCloud Keychain, Google Password Manager, 1Password, Bitwarden).

  1. On device A, create a secret and tick "Lock with passkey (self-share)". The browser registers a new passkey, which the synced provider replicates to your other devices automatically.
  2. Send the URL to device B by any means.
  3. On device B, open the URL and authenticate with the same synced passkey.

If your passkey isn't synced, you won't be able to read your own secret from a different device. This mode is not for sharing with other people — pre-coordinating their public key is incompatible with the "drop a URL in Teams" flow. Use a passphrase instead.

WebAuthn requires the relying-party ID to be a registrable domain, which is why GOASH_BASE_URL defaults to http://localhost:8080 rather than the bind address.

Deploy examples

Ready-to-use compose files and reverse proxy configs live in deploy/. Each subdirectory is a known-working, validated stack:

Proxy Config Notes
Caddy Caddyfile Automatic TLS — just set your domain
Traefik traefik.yml Let's Encrypt resolver block included (commented)
Nginx nginx.conf TLS cert mount points commented in
HAProxy haproxy.cfg PEM bundle mount commented in
Apache httpd httpd.conf Full standalone config with all LoadModule directives

For bare-metal installs, see deploy/goash.service (systemd) and deploy/goash.env (annotated environment template).

Project layout

cmd/goash/         entry point
internal/
  secret/          Secret struct, atomic claim/burn
  store/           sync.Map + TTL timers
  handler/         HTTP routes, middleware, page rendering
  ipcheck/         CIDR + geo allowlist evaluation
  webauthn/        thin wrapper over go-webauthn for self-share mode
web/
  templates/       html/template files (embedded)
  static/          crypto.js, passkey.js, styles.css (embedded)
deploy/
  caddy/           Caddy compose + Caddyfile
  traefik/         Traefik v3 compose + static config
  nginx/           Nginx compose + nginx.conf
  haproxy/         HAProxy compose + haproxy.cfg
  apache/          Apache httpd compose + httpd.conf
  goash.service    systemd unit (bare-metal)
  goash.env        environment variable template

Tests

go test ./...

Unit tests cover the store, secret, ipcheck, and the HTTP handler surface (including the IP gate via httptest.NewRequest + manual RemoteAddr). The WebAuthn ceremony is exercised by manual cross-device test — see the verification section of plans/1-the-burn-after-fuzzy-harp.md.

About

Self-hosted burn-after-reading secret drop server. Zero-knowledge, no database, one-time URLs.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors