How TREK Handles Encryption Key Rotation for Production Deployments

TREK handles encryption key rotation through a Node.js migration script (server/scripts/migrate-encryption.ts) that decrypts all AES-256-GCM protected secrets with the old key and re-encrypts them with a new key while creating a timestamped database backup, allowing production operators to rotate keys without downtime.

TREK, an open-source travel management application maintained in the mauriceboe/TREK repository, protects sensitive configuration values—such as API keys, OAuth secrets, and MFA TOTP seeds—using AES-256-GCM encryption. Understanding how to perform encryption key rotation in production deployments is critical for maintaining security compliance, as the system relies on a 32-byte hex key derived from the ENCRYPTION_KEY environment variable or persistent file storage. The encryption architecture is designed to support zero-downtime rotation workflows that re-encrypt data in place without affecting unencrypted user records.

Understanding TREK's Encryption Key Resolution

Before rotating keys, you must understand how TREK resolves the active encryption key at startup. According to the source code in mauriceboe/TREK, the system checks four locations in strict precedence order:

Production Key Precedence Order

  1. ENCRYPTION_KEY environment variable – The highest priority source. When set, TREK also persists this value to ./data/.encryption_key to ensure container restarts survive the loss of the environment variable.
  2. ./data/.encryption_key file – Used when the environment variable is absent. Created automatically on first start if no other key source exists.
  3. ./data/.jwt_secret file – A legacy fallback for pre-v2 installations. On startup, this value is copied to .encryption_key and future JWT rotations no longer impact encryption.
  4. Auto-generated key – On fresh installations with no key source, TREK generates a random 32-byte hex key and writes it to ./data/.encryption_key.

How Encryption Key Rotation Works in TREK

TREK provides a dedicated migration utility rather than manual database edits. The script at server/scripts/migrate-encryption.ts performs an atomic re-encryption of every stored secret by decrypting with the old key and encrypting with the new key.

The Migration Script Process

When executed, the script performs the following operations:

  • Prompts for the old ENCRYPTION_KEY value (hidden input)
  • Prompts for the new ENCRYPTION_KEY value (hidden input)
  • Requires manual confirmation before proceeding
  • Creates a timestamped backup of the SQLite database named travel.db.backup-<epoch>
  • Iterates through all encrypted tables and re-encrypts values in place
  • Reports counts of migrated, skipped, already-migrated, and errored entries
  • Aborts safely if any value cannot be decrypted, leaving the original database backup untouched

Performing a Zero-Downtime Key Rotation

Use the following workflow to rotate keys in production without service interruption:

1. Generate a New Encryption Key

Create a cryptographically secure 32-byte hex key:

export NEW_ENCRYPTION_KEY=$(openssl rand -hex 32)
echo "New key generated: $NEW_ENCRYPTION_KEY"

2. Execute the Migration Script

Run the migration from the host or inside a Docker container:

Host execution:

cd server
node --import tsx scripts/migrate-encryption.ts

Docker execution:

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

The script will interactively prompt for the old key, new key, and confirmation. You can also pipe inputs for automation:

node --import tsx scripts/migrate-encryption.ts <<EOF
$OLD_ENCRYPTION_KEY
$NEW_ENCRYPTION_KEY
yes
EOF

3. Update the Runtime Environment

Persist the new key for subsequent restarts:


# Option A: Environment variable (recommended for containers)

export ENCRYPTION_KEY=$NEW_ENCRYPTION_KEY

# Option B: Persistent file

echo "$NEW_ENCRYPTION_KEY" > ./data/.encryption_key

4. Restart TREK

Restart the service to load the new key:

docker restart trek

Backup and Recovery Considerations

The regular backup ZIP created by TREK does not contain the encryption key. Production operators must store the key separately in a password manager or secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager) and synchronize it with backup restoration procedures.

If the encryption key is lost entirely, all encrypted settings become unreadable. TREK cannot decrypt them, and you must manually re-enter those secrets via the admin UI. Unencrypted data—including trips, users, and places—remains unaffected.

Summary

  • Key resolution follows a strict four-tier order: environment variable → ./data/.encryption_key → legacy ./data/.jwt_secret → auto-generation.
  • Zero-downtime rotation is achieved via server/scripts/migrate-encryption.ts, which creates a database backup before re-encrypting all secrets.
  • Backup isolation requires separate storage of the encryption key outside the TREK backup ZIP.
  • Legacy migration automatically upgrades old installations using .jwt_secret to the modern .encryption_key system on startup.

Frequently Asked Questions

Where does TREK store the production encryption key?

TREK stores the active encryption key in the ENCRYPTION_KEY environment variable, which takes precedence over all other sources. If the environment variable is not set, TREK falls back to the ./data/.encryption_key file. When the environment variable is present, TREK writes its value to this file to ensure persistence across container restarts.

What happens if I lose the encryption key?

If the encryption key is lost, TREK cannot decrypt any AES-256-GCM protected values, including API keys and OAuth secrets. You must re-enter these sensitive configuration values manually through the admin UI. Unencrypted database records—such as user accounts, trip data, and location information—remain fully accessible and unaffected by key loss.

Does TREK support zero-downtime encryption key rotation?

Yes. TREK supports zero-downtime rotation through the server/scripts/migrate-encryption.ts migration script. The script re-encrypts all stored secrets in place while the application is running, creates a timestamped backup of the SQLite database, and only requires a standard service restart to activate the new key after migration completes.

How do I rotate the encryption key when running TREK in Docker?

Execute the migration script inside the running container using docker exec -it trek node --import tsx scripts/migrate-encryption.ts, then update the ENCRYPTION_KEY environment variable in your docker-compose.yml or orchestration platform, and finally restart the container with docker restart trek. The script handles the re-encryption atomically before the new key is activated.

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 →