Skip to content

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_four appears in output schemas
  • RBAC: Write access to ssn_last_four is 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
derive_tenant_key(master_key: bytes, schema_name: str) -> bytes

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_interact rules 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