Table of contents
Security
Token encryption
Each linked character's EVE access and refresh tokens are stored in the database. A refresh token is a standing credential, so anyone holding a copy of the database could read that character's data and use the write scopes the app requests (sending mail, saving fittings).
Set TOKEN_ENCRYPTION_KEY to any random value of at least 32
characters and both tokens are stored encrypted (AES-256-GCM),
each bound to its character and column:
openssl rand -hex 32 # put the output in .env as TOKEN_ENCRYPTION_KEY=...
The setup script generates a key for new installs. Add the line and restart, and tokens already stored are encrypted in place at that start.
Two things to know before switching it on:
- Keep the key. It lives in
.env, not in the database, so back the two up together. If the key is changed or removed, the app refuses to start rather than run with tokens it cannot read; the error explains how to clear the stored tokens if the key is truly lost, after which every character signs in once more. - Do not go back to a build from before token encryption. It would present the encrypted values to CCP as tokens, be refused, and mark every character as needing a fresh sign-in.
Tokens sealed by earlier builds still open, and the first start of a newer build re-seals them in the current format; that upgrade is one way, so take a database backup before upgrading if you might roll back.
Without a key, tokens are stored unencrypted and the app logs a warning at startup.
Keeping the key off the machine
The key in .env protects the tokens against a stolen copy of
the database, but it sits on the same machine, so a copy of the
machine's files (database and .env together) opens everything.
For any instance reachable from the web, the stronger arrangement
is a key service: the key is wrapped by a KMS, and the app
fetches it, unwrapped, at startup and keeps it in memory only.
TOKEN_KEY_PROVIDER=http
TOKEN_KEY_WRAPPED=... # the key, wrapped by the key service
TOKEN_KEY_UNWRAP_URL=... # its unwrap endpoint
TOKEN_KEY_UNWRAP_CREDENTIAL=... # the credential the app presents
TOKEN_KEY_PROVIDER is env (the TOKEN_ENCRYPTION_KEY above;
the default when it is set), http, oci, or none (no key).
Any HTTPS endpoint speaking the two-call protocol (unwrap:
POST {"wrapped": ...} returns {"secret": ...}; wrap:
POST {"secret": ...} returns {"wrapped": ...}) can serve,
so nothing ties this to one vendor's KMS. A complete reference
service ships as a Cloudflare Worker you deploy to your own
account, free, in a few minutes:
natemsz/cloudflare-kms.
On Oracle Cloud there is a second shape, TOKEN_KEY_PROVIDER=oci:
the key is wrapped by a master key in an OCI Vault (create it
with HSM protection, so the key cannot be exported) and unwrapped
at startup by a KMS Decrypt call authenticated by the instance's
own identity, an instance principal. Nothing cloud-shaped is
stored on the machine at all: no credential, no key, only the
ciphertext and the key's address. The README's "Keeping the key
off the machine" section has the IAM policy statements and the
one-time steps for both providers.
With either provider the app starts and serves at once; the unwrap is retried in the background until it succeeds, and a fetched key is only used after it has been shown to open the stored tokens.
The four working credentials (EVE_CLIENT_SECRET,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN, MODEL_API_KEY)
can come off the machine the same way: from OCI Secrets with the
oci provider, or wrapped by the key service with the http
provider, each fetched at startup and held in memory only. Each
credential moves independently; while a fetch is still to land,
the plain value (when one is set) serves.
Backups
Back the database up with pg_dump, and restore with psql:
pg_dump evesynapse > evesynapse-$(date +%F).sql
psql evesynapse < evesynapse-2026-10-05.sql # into a fresh database
The generated SQL file plus /opt/evesynapse/.env is a complete
backup of an install.