Data Protection¶
Encryption, secret masking, and data handling policies for sensitive information.
SSN Handling¶
Social Security Numbers are never stored in full or returned via the API:
- Storage: Only
ssn_last_four(4-digit CharField) is stored on the Borrower model - Validation: Regex
^\d{4}$enforced at both model and schema levels - API response: Only
ssn_last_fourappears in output schemas - RBAC: Write access to
ssn_last_fouris denied for all roles via field-level restrictions:
FIELD_RESTRICTIONS = [
{"type": "deny", "action": "write", "resource": "borrowers.ssn_last_four"},
]
Provider Credential Encryption¶
External provider credentials (API keys, transaction keys, SFTP passwords) are encrypted at rest using EncryptedJSONField.
Encryption Scheme¶
- Algorithm: Fernet symmetric encryption (AES-128-CBC + HMAC-SHA256)
- Key derivation: HKDF-SHA256 derives per-tenant keys from a master key
- Key versioning: Ciphertext prefixed with a 1-byte version (1–254) enabling zero-downtime key rotation
Each tenant's data is encrypted with a unique derived key, so compromising one tenant's key does not expose other tenants' data.
Key Configuration¶
Two configuration approaches (in environment variables):
| Setting | Format | Notes |
|---|---|---|
PROVIDER_ENCRYPTION_KEY |
Single base64 key | Treated as version 1 |
PROVIDER_ENCRYPTION_KEYS |
JSON list of {"version": int, "key": str} |
Takes precedence; supports rotation |
Encrypted Fields in the Codebase¶
| Model | Field | Contents |
|---|---|---|
ProviderConfig |
config_secret |
Payment processor credentials, API keys |
Borrower |
ein_encrypted |
Employer Identification Number |
Borrower |
plaid_data |
Plaid integration data |
Portfolio (loan tape) |
sftp_credentials |
SFTP upload credentials |
Encryption Utilities¶
common/encryption.py provides:
| Function | Purpose |
|---|---|
encrypt_json(data, key) |
Encrypt a dict to Fernet token |
decrypt_json(token, key) |
Decrypt a Fernet token to dict |
hash_secret_values(data, key) |
HMAC-SHA256 hash for change detection without decryption |
mask_secret_value(value) |
Display masking (first 4 + last 3 chars, or ***) |
Secret Masking¶
API responses never return raw secrets. The mask_secret_value() function is applied to provider credentials before serialization:
- Strings > 10 chars:
"sk_live_abc...xyz"→"sk_l...xyz" - Shorter strings:
"***"
Provider config endpoints are write-only for secret fields — you can set credentials but never read them back.
CORS Configuration¶
Cross-Origin Resource Sharing is configured to allow only known frontend origins:
# Development
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173", # Admin dashboard dev server
"http://localhost:5174", # Borrower portal dev server
]
Production CORS origins are configured via environment variables to match deployed frontend domains.
Metadata Validation¶
The MetaDataField (used for extensible JSON data on models) enforces constraints to prevent abuse:
| Constraint | Limit |
|---|---|
| Maximum keys | 50 |
| Maximum key length | 64 characters |
| Maximum value length | 512 characters |
| Allowed value types | Scalars only (string, int, float, bool, null) |
Nested objects and arrays are rejected to maintain a flat key-value structure.
do_not_contact Flag¶
Borrowers flagged with do_not_contact = True receive zero automated communications:
- The flag is checked in
send_communication()before any message dispatch - Applies to all channels: email, SMS, letter, in-app
- Enforced at the service layer, not just the UI
- Related:
do_not_interactrules can suppress communications per-channel
Security Logging¶
A dedicated lms.security logger captures security-relevant events:
- Encryption operations (key rotation, decryption failures)
- Authentication events
- Permission denials
See Also¶
- Provider Pattern --- Provider architecture and credential management
- Authentication & Authorization --- RBAC and session security
- Input Validation --- Schema-level validation