Skip to content

Encrypt connector variables at rest - #837

Merged
keysersoft merged 3 commits into
mainfrom
keysersoft/encrypt-connector-env-vars
Oct 3, 2026
Merged

keysersoft merged 3 commits into
mainfrom
keysersoft/encrypt-connector-env-vars

Conversation

@keysersoft

Copy link
Copy Markdown
Contributor

connectors.env_vars holds every connector's API keys, passwords and tokens, and was stored in clear (only auth_config was encrypted). It is now stored as {"$enc": "<AES-256-GCM>"} with the existing ENCRYPTION_KEY (AAD connectors.env_vars) and decrypted on read.

Independent of #831/#832/#834; based on main.

How

  • One choke point. A Prisma extension on PrismaService seals envVars on connector writes (create, createMany(AndReturn), update, updateMany(AndReturn), upsert) and opens it on every read through a result override. Prisma applies that at every level: findMany, include from another model, select, tenantTx. None of the ~70 call sites changes. A test fails if a nested connector write is added (those would bypass the query hook).
  • Existing rows are read as they are and encrypted by an idempotent pass at boot (Encrypted the variables of N connectors). It replaces a row only if it is unchanged since it was read, so several instances can run it at once.
  • SQL patches keep working. Keys added next to $enc (env_vars || '{"X":"y"}') are read, win over the encrypted value, and are folded in at the next start.
  • Wrong key: the row reads as empty, so calls stop at the placeholder guard ("still empty") instead of sending ciphertext to an API.
  • Rollback hatch: ENV_VARS_AT_REST=plaintext decrypts every row at boot. Documented in docs/operations/backup-restore.md and passed through docker-compose.yml / docker-compose.cloud.yml. The quickstart compose is left as is (its file is snapshotted into the satellite repos); the doc says how to add it there.

Production check (read-only)

707 rows with an object, 202 JSON null, 136 SQL NULL; no $enc key anywhere; every value is a string. The boot pass only touches non-empty objects.

Tests

  • Unit: 26 (seal/open, legacy rows, SQL merge, Prisma null markers, foreign AAD, wrong key, plaintext mode, every write operation, no nested writes).
  • Integration against Postgres with the compiled PrismaService: legacy row readable, boot pass encrypts and is idempotent, writes sealed, include/select/tenantTx/findMany decrypt, SQL merge folded in, plaintext hatch and back, {} stays {}.
  • Full app booted on that database: rows encrypted at boot; an adapter whose base URL comes from an encrypted variable answered a real MCP call (OpenPLZ, Freiburg 79098), again after a restart; the API still masks secret values.
  • Backend: 6523 passed.

After deploy

Raw SQL on env_vars (analytics, ops scripts) now sees ciphertext; use the API or decrypt with the key.

connectors.env_vars holds every connector's API keys, passwords and
tokens, and was stored in clear. It is now stored as
{"$enc": "<AES-256-GCM>"} with ENCRYPTION_KEY and decrypted on read.

- One choke point: a Prisma extension on PrismaService seals envVars on
  connector writes and opens it on every read (findMany, include from
  another model, select, transactions), so none of the ~70 call sites
  changes. A test fails if a nested connector write is ever added.
- Rows written before are read as they are and encrypted by an
  idempotent pass at boot; only rows unchanged since read are replaced.
- Keys an operator adds next to $enc with SQL are read (and win) and
  are folded into the ciphertext at the next start.
- A row that cannot be decrypted reads as empty, so calls stop at the
  placeholder guard instead of sending ciphertext to an API.
- Rollback hatch: ENV_VARS_AT_REST=plaintext decrypts every row at boot.
Comment thread packages/backend/src/common/crypto/env-vars-at-rest.spec.ts Fixed
@keysersoft
keysersoft enabled auto-merge (squash) October 3, 2026 12:36
@keysersoft
keysersoft merged commit 877f2fe into main Oct 3, 2026
13 checks passed
@keysersoft
keysersoft deleted the keysersoft/encrypt-connector-env-vars branch October 3, 2026 12:39
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants