Skip to content

Add ENCRYPTION_KEY rotation CLI to avoid manual export/wipe/re-import procedure #180

@keysersoft

Description

@keysersoft

Problem

`ENCRYPTION_KEY` (AES-256-GCM, 32 chars) encrypts every connector credential at rest — API keys, OAuth tokens, database passwords. There is currently no built-in way to rotate it.

Simply swapping the value in `.env` and restarting bricks every connector: existing ciphertext was sealed with the old key, decryption fails, and every tool invocation throws.

PR #178 documents a manual workaround in `SECURITY.md`:

  1. Take a database backup
  2. Export every connector config to JSON (decrypted in transit)
  3. Generate a new key
  4. Wipe the encrypted columns / restore from backup without secrets
  5. Re-import each connector with the new key

This is fine for installations with a handful of connectors but is operationally painful (and risky — step 2 puts decrypted secrets on disk) for production deployments with dozens of connectors.

Proposal

Ship a one-shot rotation command:

```bash
docker compose exec app node packages/backend/dist/scripts/rotate-encryption-key.js \
--old "$OLD_ENCRYPTION_KEY" --new "$NEW_ENCRYPTION_KEY"
```

The script should:

  • Run inside a single database transaction (so partial failures roll back, no half-rotated state)
  • Iterate over every row in tables that hold AES-256-GCM ciphertext (connector credentials, OAuth tokens, per-user MCP API keys if any are encrypted)
  • Decrypt with the old key, re-encrypt with the new key, update the row
  • Refuse to run if either key is the wrong length or if the old key fails to decrypt the first row (early-fail rather than silently corrupting the table)
  • Print a summary at the end: `X rows re-encrypted across Y tables`

The crypto primitive already exists at `packages/backend/src/common/crypto/encryption.util.ts` — the rotation command is a thin wrapper that walks the schema.

Acceptance criteria

  • Rotation script exists at `packages/backend/src/scripts/rotate-encryption-key.ts` and ships compiled in the Docker image
  • All-or-nothing semantics (single transaction)
  • Refuses to run with invalid keys
  • Documented in `SECURITY.md` (replacing the manual workaround currently in the rotation section)
  • Tested against a database with at least one connector per credential type (REST/SOAP/GraphQL/Database/MCP-bridge), confirming all decrypt cleanly after rotation

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions