fix(security): vault integration secrets instead of plaintext config (R1)
PayPal client_secret (and any secret-named integration field) was stored plaintext in integrations.config, unlike Stripe/door which use the AES-256-GCM vault. A DB read or backup leaked it in cleartext. - lib/secrets/vault.ts: canonical generic storeSecret/readSecret + isSecretConfigField (the proven Stripe vault logic, generalized). - saveIntegration: secret-named fields go to the vault, only a *_ref is kept in config; blank submit preserves the ref (write-only like Stripe); legacy plaintext auto-migrates into the vault on next save. - paypal/sync reads client_secret from the vault, transitional fallback to legacy plaintext until the next save. - Docs updated (API_REFERENCE, ARCHITECTURE 10.3). 502 tests.
This commit is contained in:
@@ -682,6 +682,7 @@ async function saveIntegration(
|
||||
**Behavior**:
|
||||
- Upserts on `(space_id, platform)`
|
||||
- Stores config with `_set` indicators for each field
|
||||
- Secret-named fields (`client_secret`, `*_secret`, `api_key`, `secret_key`) are NEVER stored in `integrations.config` in plaintext: they go to the AES-256-GCM secrets vault and only a `<field>_ref` id is kept (same vault Stripe/door use). A blank submit preserves the existing ref (write-only, like Stripe); any legacy plaintext secret already on file is auto-migrated into the vault on the next save. The PayPal sync route reads `client_secret` from the vault (with a transitional fallback to legacy plaintext until the next save).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -396,10 +396,10 @@ The admin client (`SUPABASE_SERVICE_ROLE_KEY`) is used ONLY for:
|
||||
|
||||
### 10.3 Secrets Storage
|
||||
|
||||
Integration credentials stored in `integrations.config` (JSONB):
|
||||
- Values stored with `_set` indicators
|
||||
- UI shows masked values
|
||||
- Consider encryption at rest for production
|
||||
Integration credentials:
|
||||
- Secret-named fields (`client_secret`, `*_secret`, `api_key`, `secret_key`) are encrypted at rest in the AES-256-GCM secrets vault (`lib/secrets/vault.ts`, same vault Stripe + door use); only a `<field>_ref` id is kept in `integrations.config`. Server-side decrypt only, never returned to the client.
|
||||
- Non-secret fields stored in `integrations.config` (JSONB) with `_set` indicators; UI shows connected state via the flags, never the value.
|
||||
- Legacy plaintext secrets (pre-vault) are auto-migrated into the vault on the next `saveIntegration`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user