Skip to content

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):

openssl rand -hex 32

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

  1. Generate a new key: openssl rand -hex 32
  2. Set the old key as CONNECTOR_ENCRYPTION_KEY_PREVIOUS.
  3. Set the new key as CONNECTOR_ENCRYPTION_KEY.
  4. Restart the server. Reads will transparently fall back to the previous key if decryption with the new key fails.
  5. 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:

{ "total": 5, "rotated": 4, "skipped": 1, "failed": [] }
  1. Once all connectors are re-encrypted, remove CONNECTOR_ENCRYPTION_KEY_PREVIOUS and 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:

  1. Set CONNECTOR_ENCRYPTION_KEY_PREVIOUS to the legacy fallback key: 0000000000000000000000000000000000000000000000000000000000000000.
  2. Set CONNECTOR_ENCRYPTION_KEY to your new stable production key.
  3. Restart, then run POST /api/connectors/rotate-keys (or use the Connectors -> Rotate Keys UI action).
  4. Confirm failed is empty, then remove CONNECTOR_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:

{
  "name": "apiKey",
  "type": "string",
  "required": true,
  "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.