Encryption at rest
Sensitive payloads are sealed under a per-tenant envelope before being written to storage, rather than relying solely on a provider’s default storage-level encryption. The design goal: even with access to the raw table, one tenant’s plaintext should never be recoverable using another tenant’s key material or context.
Envelope model
- A shared root key (customer-managed) wraps a per-write data key.
- The tenant is bound into the envelope as encryption context/AAD: a ciphertext sealed for tenant A cryptographically cannot be opened using tenant B’s context, even under the same root key. This is a second, independent tenant boundary layered under the application-level checks described in Isolation model — a defense-in-depth property, not a replacement for them.
- Ciphertext is framed with a version byte so the format itself is versioned and can evolve without breaking old records.
What is covered today
- Connector configs (
config_enc) — a connector binding’s config, which may carry an API key, bearer token or basic-auth password, is sealed before being persisted. Onlyconfig(plaintext) exists for bindings created before encryption was enabled; readers accept both (config_encpreferred,configas a compatibility fallback) so older bindings keep working, and can be re-sealed on their next update. - Fail-closed behavior: if a stored value indicates it should be encrypted but the decrypting process has no key access configured, the read fails rather than silently returning ciphertext or skipping decryption.
What is not yet covered
Execution and human-task payloads (input/output/form data flowing
through workflow runs) currently rely on the storage provider’s own
default encryption rather than the per-tenant envelope above. Bringing
these under the same tenant-bound envelope — either at the storage layer or
per-attribute before write — is tracked as a follow-up; until then, treat
workflow input/output as not cryptographically tenant-isolated beyond
the application-level checks in Isolation model.
Practical implication for connector configs
Because a connector config is encrypted at rest, GET /connectors returns
the config as the worker will use it (decrypted, for the caller’s own
tenant) — the ciphertext form is never exposed over the API. A secret placed
in a connector config is protected from anyone with read access to the raw
database, but is still visible to any authenticated member of the tenant via
the API — connector config secrets are a tenant-shared credential, not a
per-user one.