Security
Encryption
All encryption and decryption happens in the browser using the Web Crypto API.
Key generation uses RSA-OAEP 2048-bit key pairs. Encryption is hybrid: AES-256-GCM
encrypts the data, and RSA-OAEP encrypts the AES key. The payload sent to the server
is a Base64-encoded JSON object containing encryptedKey, encryptedData, and
iv.
Key storage
Each pod gets its own private/public key pair.
Private keys are stored in localStorage under privipod_key_<hash>.
They persist until:
The pod is set to self-destruct (the key is removed after the owner decrypts the secret); or
You clear your browser storage manually; or
The key expires and you visit the dashboard for it to be pruned.
Note
For the truly paranoid, use a dedicated private browsing session and clear storage afterwards, or download the key to a secure location and delete it from the browser.
Secret key
Privipod uses a secret key to sign sessions and CSRF tokens. If you do not set one, a random key is generated on every startup - this logs a warning and means all users are logged out whenever the process restarts.
Set a persistent key via the PRIVIPOD_SECRET_KEY environment variable:
export PRIVIPOD_SECRET_KEY="your-long-random-string"
The key should be at least 50 characters long, contain a mix of letters, digits, and symbols, and be generated randomly - never use a memorable phrase or reuse a key from another project.
In the Docker deployment, add it to docker-compose.yml:
environment:
- PRIVIPOD_SECRET_KEY=your-long-random-string-here
For systemd, set it in the [Service] section:
Environment=PRIVIPOD_SECRET_KEY=your-long-random-string-here
HTTPS requirement
Privipod must be served over HTTPS in production. The Web Crypto API requires a
secure context,
and without HTTPS the private key stored in localStorage is accessible to any
script on the same origin. See install for configuration examples.
Running Modes
Privipod operates in two modes depending on whether --hostname is provided.
Setting |
Untrusted-host (default) |
Deployed ( |
|---|---|---|
|
|
|
|
(empty) |
|
|
set |
set |
|
True |
True |
|
True |
True |
|
0 |
3600 (1 hour) |
Untrusted-host mode (default)
Suitable for local use or sharing via ngrok/Cloudflare Tunnel. The app port
is not internet-reachable directly, so X-Forwarded-Proto headers from the
tunnel are trusted.
Note
http://localhost is a secure context, but an ngrok or Cloudflare Tunnel
URL is a different browser origin - private keys stored in
localStorage are not shared between the two. Always use the same origin
consistently.
Deployed mode (--hostname example.com / PRIVIPOD_HOSTNAME=example.com)
For Docker/Caddy or systemd/nginx deployments. Providing a hostname:
Restricts
ALLOWED_HOSTSto the listed hostname(s), preventing Host-header link-poisoning.Sets
CSRF_TRUSTED_ORIGINS.Enables HSTS (1-hour max-age by default, no subdomains or preload). Increase
SECURE_HSTS_SECONDSin your deployment once everything is stable.
Important
Your reverse proxy must strip or overwrite inbound X-Forwarded-Proto and X-Forwarded-For headers before forwarding requests to Privipod. Caddy does this automatically; for nginx add:
proxy_set_header X-Forwarded-Proto $scheme;
Privipod’s app port must not be publicly reachable - only the proxy should connect to it.