How TREK Handles Encryption at Rest and ENCRYPTION_KEY Rotation

TREK encrypts sensitive data using AES-256-GCM with keys derived from the ENCRYPTION_KEY environment variable, and provides a dedicated migration script to rotate encryption keys without service interruption or data loss.

TREK is an open-source application that stores API keys, MFA secrets, and SMTP credentials encrypted on disk rather than in plaintext. Understanding the encryption mechanism and the proper procedure for rotating the ENCRYPTION_KEY is essential for maintaining security compliance and responding to potential credential compromises.

How TREK Encrypts Data at Rest

TREK implements AES-256-GCM encryption for all sensitive data stored on disk. The system relies on a single master secret provided via the ENCRYPTION_KEY environment variable, which should be a cryptographically secure 32-byte hex string generated with openssl rand -hex 32 README.md#L77-L79.

When the server starts, it reads ENCRYPTION_KEY from process.env.ENCRYPTION_KEY and initializes purpose-specific encryption helpers. If the variable is unset, TREK falls back to a legacy .jwt_secret file or auto-generates a temporary key, but production deployments must always supply a strong, persistent key.

Purpose-Bound Key Derivation

Rather than using the raw ENCRYPTION_KEY directly, TREK derives separate keys for different data types using SHA-256 hashing with domain separation. This ensures that API keys and MFA secrets use cryptographically distinct keys even when derived from the same master secret.

In server/scripts/migrate-encryption.ts, the derivation functions are implemented as follows server/scripts/migrate-encryption.ts#L31-L37:

function apiKey(encryptionKey: string) {
  return crypto.createHash('sha256')
               .update(`${encryptionKey}:api_keys:v1`).digest();
}

function mfaKey(encryptionKey: string) {
  return crypto.createHash('sha256')
               .update(`${encryptionKey}:mfa:v1`).digest();
}

The production runtime uses identical logic in server/apiKeyCrypto.ts and server/mfaCrypto.ts to ensure consistent encryption across the application.

The Encryption Process

When encrypting data, TREK generates a random 12-byte IV (Initialization Vector) for each value and applies AES-256-GCM encryption using the derived purpose-specific key. Encrypted API key values are prefixed with enc:v1: to allow the runtime to distinguish encrypted from legacy plaintext values server/scripts/migrate-encryption.ts#L29-L47.

The decryption process reverses this workflow: the runtime detects the enc:v1: prefix, extracts the IV and ciphertext, and decrypts using the appropriate derived key. This transparent encryption/decryption occurs on every database read and write, ensuring data remains encrypted at rest while remaining accessible to the application.

How to Rotate the ENCRYPTION_KEY

Rotating encryption keys is a critical security practice when personnel leave, keys are potentially exposed, or as part of routine security policy updates. TREK provides a dedicated migration script that safely re-encrypts all secrets with a new key while maintaining data integrity.

Pre-Migration Safety Measures

The migration script prioritizes data safety by creating a timestamped backup of the SQLite database before modifying any records. This ensures you can recover immediately if the rotation process encounters unexpected errors server/scripts/migrate-encryption.ts#L82-L86.

Step-by-Step Key Rotation Process

The rotation workflow is interactive and designed to prevent accidental key exposure in shell history. Execute the migration inside your running container README.md#L20-L28:

docker exec -it trek node --import tsx scripts/migrate-encryption.ts

The script executes the following sequence server/scripts/migrate-encryption.ts#L58-L61:

  1. Prompts for the old ENCRYPTION_KEY – Input is masked to prevent display in terminal logs
  2. Prompts for the new ENCRYPTION_KEY – Similarly masked for security
  3. Requires explicit confirmation – You must type "yes" to proceed after reviewing the backup location
  4. Migrates all secrets – Decrypts each value with the old key and re-encrypts with the new key, handling API keys and MFA secrets separately
  5. Reports statistics – Displays counts of migrated, already-migrated, skipped, and errored items

After completion, restart the container with the new ENCRYPTION_KEY environment variable. All subsequent database operations will use the new key, and the old key can be securely deleted from your secrets manager.

Configuration Examples

Generate a production-grade encryption key and start TREK with proper environment configuration:


# Generate a fresh 32-byte hex key

ENCRYPTION_KEY=$(openssl rand -hex 32)

# Start TREK with the encryption key (Docker example)

docker run -d -p 3000:3000 \
  -e ENCRYPTION_KEY=$ENCRYPTION_KEY \
  -v ./data:/app/data \
  -v ./uploads:/app/uploads \
  mauriceboe/trek

For Docker Compose deployments, define the variable in your environment block docker-compose.yml#L36-L39:

services:
  trek:
    environment:
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}

Key Implementation Files

Summary

  • TREK uses AES-256-GCM encryption with per-purpose keys derived from the ENCRYPTION_KEY environment variable to secure API keys and MFA secrets at rest.
  • Key derivation uses SHA-256 with domain-specific strings (api_keys:v1, mfa:v1) to ensure cryptographic separation between different secret types.
  • Encrypted values are prefixed with enc:v1: to enable the runtime to distinguish between encrypted and legacy plaintext data.
  • Key rotation is performed using server/scripts/migrate-encryption.ts, which creates a database backup and interactively migrates all secrets from the old key to the new key.
  • Production deployments should always explicitly set ENCRYPTION_KEY to a 32-byte hex value generated via openssl rand -hex 32 rather than relying on auto-generated keys.

Frequently Asked Questions

What happens if I lose my ENCRYPTION_KEY?

If you lose the ENCRYPTION_KEY, encrypted data cannot be recovered. TREK cannot decrypt API keys or MFA secrets without the original key, as there is no backdoor or key escrow mechanism. Always store your encryption key in a secure secrets manager and maintain offline backups.

Can I rotate the ENCRYPTION_KEY without downtime?

While the migration script itself requires the application to be running to access the database, the rotation process does not require an extended maintenance window. The script completes quickly for typical workloads, and you only need to restart the container after migration to switch to the new key. For high-availability deployments, consider running the migration on a single instance while others remain active, then rolling the key update across all instances.

How do I verify the migration succeeded?

The migration script outputs statistics showing the number of records migrated, skipped, and errored. After restarting with the new ENCRYPTION_KEY, verify functionality by testing API key authentication and MFA login flows. If any issues occur, restore from the timestamped backup created by the script and verify you entered the correct old key during migration.

Is the legacy JWT_SECRET still supported?

TREK maintains backward compatibility with a legacy .jwt_secret file for environments that have not yet migrated to ENCRYPTION_KEY. However, this fallback is deprecated and should not be used in production. The ENCRYPTION_KEY variable provides superior security and explicit control over your encryption material.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →