Encryption Keys¶
Floh uses AES-256-GCM encryption for several categories of sensitive data. This document covers every key the application expects, how to generate them, and the procedures for rotating or migrating keys.
Key overview¶
| Environment variable | Purpose | Required in production? |
|---|---|---|
CONNECTOR_ENCRYPTION_KEY |
Encrypts connector secret fields at rest | Yes |
CONNECTOR_ENCRYPTION_KEY_PREVIOUS |
Allows decryption during key rotation | No (rotation only) |
SESSION_ENCRYPTION_KEY |
Encrypts OIDC access/refresh tokens in Redis | Yes |
Generating a key¶
All keys are 64-character hex strings (256 bits):
CONNECTOR_ENCRYPTION_KEY¶
Used to encrypt secret fields in connector_definition.connector_config
(fields marked secret: true in the connector's configSchema).
Development mode (NODE_ENV=development or test): if the variable
is unset, the application derives a deterministic dev-only key and logs
a warning. If the variable is set but invalid, startup fails so malformed
keys are caught early.
The derived key depends on SESSION_SECRET and JWT_SECRET. If either value
changes in a non-empty dev/test environment, existing connector secrets may no
longer decrypt. Reconfigure connectors or set an explicit
CONNECTOR_ENCRYPTION_KEY for stable local secret storage.
When both JWT_SECRET and SESSION_SECRET remain at defaults, the derived key
is also a deterministic default value and therefore globally predictable. Avoid
using default secrets for shared dev/staging data, or set an explicit
CONNECTOR_ENCRYPTION_KEY.
Production (NODE_ENV=production): the application will not start
if this key is missing or is not a valid 64-character hex string.
Key rotation¶
- Generate a new key:
openssl rand -hex 32 - Set the old key as
CONNECTOR_ENCRYPTION_KEY_PREVIOUS. - Set the new key as
CONNECTOR_ENCRYPTION_KEY. - Restart the server. Reads will transparently fall back to the previous key if decryption with the new key fails.
- Call the admin endpoint to re-encrypt all connectors:
curl -X POST https://your-server/api/connectors/rotate-keys \
-H "Authorization: Bearer <admin-token>"
The response includes a summary:
- Once all connectors are re-encrypted, remove
CONNECTOR_ENCRYPTION_KEY_PREVIOUSand restart.
Migrating from an empty key in older deployments¶
If your deployment previously ran with an empty CONNECTOR_ENCRYPTION_KEY,
existing connector secrets may still be encrypted with the legacy dev
fallback key used at that time.
When rotating:
- Set
CONNECTOR_ENCRYPTION_KEY_PREVIOUSto the legacy fallback key:0000000000000000000000000000000000000000000000000000000000000000. - Set
CONNECTOR_ENCRYPTION_KEYto your new stable production key. - Restart, then run
POST /api/connectors/rotate-keys(or use the Connectors -> Rotate Keys UI action). - Confirm
failedis empty, then removeCONNECTOR_ENCRYPTION_KEY_PREVIOUS.
SESSION_ENCRYPTION_KEY¶
Used to encrypt session data (OIDC access and refresh tokens) before
writing to Redis under floh:session:* keys.
Development mode: if unset, session data is stored as plaintext JSON in Redis.
Production: the application will not start without a valid key.
Rotation is not currently supported for session keys — sessions are short-lived (24h TTL) and will naturally expire under the old key. To rotate: set the new key and restart; all active sessions will be invalidated (users must re-authenticate).
Workflow & schedule variable encryption¶
Workflow variable definitions can include a secret: true flag. When
set, the corresponding variable values are encrypted at rest in
workflow_run.variables and scheduled_trigger.variables using the
CONNECTOR_ENCRYPTION_KEY.
In API responses, secret variable values are replaced with ********.
During workflow execution, secret values are decrypted for step
evaluation and re-encrypted before persisting.
Adding a secret variable¶
In the workflow definition's variables array, set secret: true:
Invitation token hashing¶
Invitation tokens (invitation_token.token) are stored as SHA-256 hashes
rather than plaintext. No encryption key is needed — the tokens have
sufficient entropy (32 random bytes) that a deterministic hash without
salt is secure.
When an invitation link is opened, the raw token from the URL is hashed before looking it up in the database. A database compromise therefore does not directly expose valid invitation tokens.
Migration 030_hash_invitation_tokens converts existing plaintext tokens
to hashes in place. This migration is irreversible.