Custodial Stellar wallet secrets in users.wallet_secret_encrypted must never be stored as plaintext.
- Registration encrypts each wallet secret before the row is inserted.
- The database stores a versioned envelope (
cpws:v1:...), not the raw Stellar seed. - Decryption happens only in application memory immediately before a signing operation such as trustline setup, contribution signing, or creator-side withdrawal approval.
- The logger redacts secret-like fields and Stellar secret seeds before structured metadata is emitted.
CrowdPay supports envelope encryption providers through backend/src/services/walletSecrets.js.
WALLET_SECRET_PROVIDER=aws-kmsProduction mode requirement. The app generates a one-time data key with AWS KMS, encrypts the wallet secret locally with AES-256-GCM, and stores the encrypted data key plus ciphertext envelope in Postgres.WALLET_SECRET_PROVIDER=localDevelopment and test fallback. Uses a 32-byteWALLET_SECRET_LOCAL_KEKto wrap per-secret data keys with AES-256-GCM.
Production startup is blocked unless:
WALLET_SECRET_PROVIDER=aws-kmsAWS_REGIONis setWALLET_SECRET_KMS_KEY_IDis set- AWS credentials are available through
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY - No legacy plaintext wallet secrets remain in the
userstable
Any environments that ever stored plaintext Stellar seeds must rotate them before deployment.
- Dry run:
cd backend
DRY_RUN=1 npm run rotate-wallet-secrets- Rotate in place:
cd backend
npm run rotate-wallet-secretsThe rotation script rewrites only legacy plaintext users.wallet_secret_encrypted values. Already-encrypted rows are left unchanged.
- Do not log request bodies or database rows containing
wallet_secret_encrypted. - Do not copy decrypted secrets into metrics, traces, or webhook payloads.
- Treat any failed production boot caused by plaintext-secret detection as a release blocker until rotation succeeds.