Banking Data Encryption#
All provider-specific models in Sure use Rails ActiveRecord Encryption to protect sensitive data at rest. Encryption is applied uniformly across every banking and investment provider (Plaid, SimpleFin, SnapTrade, Binance, Kraken, Coinbase, EnableBanking, Akahu, Wise, Questrade, TrueLayer, and others) via a shared Encryptable concern.
The Encryptable Concern#
app/models/concerns/encryptable.rb is the single source of truth for conditional encryption. It exposes one class method, encryption_ready?, which delegates to ActiveRecordEncryptionConfig.explicitly_configured? . All provider models include Encryptable and gate their encrypts declarations behind this check .
If encryption is not configured (e.g., a dev environment without keys), the encrypts calls are skipped entirely and fields are stored in plaintext. Encryption is never silently partial.
What Gets Encrypted#
Two broad categories of fields are encrypted across every provider:
1. Credentials & Tokens (*Item models)#
Sensitive auth material on *Item (connection-level) models:
| Provider | Fields | Notes |
|---|---|---|
| Plaid | access_token | Deterministic |
| SimpleFin | access_url | Deterministic — URL embeds HTTP Basic Auth |
| SnapTrade | client_id, consumer_key | Deterministic; snaptrade_user_secret, oauth_access_token, oauth_refresh_token non-deterministic |
| Binance / Kraken / Coinbase | api_key | Deterministic; api_secret non-deterministic |
| EnableBanking | client_certificate, session_id | Both deterministic |
| Akahu | app_token, user_token | Both deterministic |
| Wise | token | Deterministic |
| Questrade | refresh_token | Deterministic |
Deterministic vs. non-deterministic: Deterministic encryption produces the same ciphertext for a given plaintext, enabling exact-match queries (e.g., looking up an item by access_token). Non-deterministic encryption is randomized and more secure but prevents querying. Credentials that may be used as lookup keys get deterministic: true; secrets that are only read after lookup (e.g., OAuth secrets) do not.
The unified provider framework introduced in PR #1717 extends this to Provider::Connection (OAuth tokens), Provider::Account (raw payloads), and Provider::FamilyConfig (per-family BYOK credentials).
2. Raw API Payloads (*Account models)#
Every *Account model encrypts its stored provider snapshots. Common fields:
raw_payload— the full per-account API responseraw_transactions_payload— accumulated transaction historyraw_holdings_payload— investment holdings (where applicable)- Provider-specific variants:
raw_liabilities_payload(Plaid, ),raw_activities_payload(SnapTrade, ),raw_balances_payload(SnapTrade/Questrade, ), etc.
Example from SimplefinAccount and PlaidAccount .
SimplefinItem also encrypts raw_payload and raw_institution_payload at the item level , as do PlaidItem , EnableBankingItem , and AkahuItem .
Encryption Configuration#
config/initializers/active_record_encryption.rb loads keys in this priority order :
- Environment variables —
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY,ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY,ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT. Works for both managed and self-hosted deployments. - Rails credentials —
config.active_record.encryptionblock incredentials.yml.enc. - Auto-generation from
SECRET_KEY_BASE(self-hosted only, no credentials present) — keys are derived via SHA-256 fromSECRET_KEY_BASEsuffixed with:primary_key,:deterministic_key, and:key_derivation_salt. This is stable across container restarts.
If only some (but not all) env vars are set, the initializer raises immediately rather than silently falling back .
lib/active_record_encryption_config.rb provides the helper methods. explicitly_configured? returns true if all three env vars are present or if credentials are configured ; this is what encryption_ready? checks. ready? is a broader check that includes runtime-configured state .
Key Source Files#
| File | Role |
|---|---|
app/models/concerns/encryptable.rb | Shared concern — encryption_ready? gating |
lib/active_record_encryption_config.rb | Config helpers and explicitly_configured? logic |
config/initializers/active_record_encryption.rb | Key loading and auto-generation at boot |
app/models/simplefin_account.rb | Representative *Account model with payload encryption |
app/models/simplefin_item.rb | Representative *Item model with credential + payload encryption |
app/models/plaid_item.rb | Plaid item — deterministic access_token |
app/models/snaptrade_item.rb | SnapTrade — both API credential modes |