1 Security
Miko edited this page 2026-10-31 00:30:48 +00:00

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.