ADR-004: PII Encryption at the Service Layer
Status: Accepted | Date: 2026-03-13
Context
The Student model contains two PII fields subject to Ghana Data Protection Act 2012 (Act 843):
passportNumber-- government-issued identity document numberguardianPhone-- guardian contact phone number
RDS at-rest encryption (via KMS) protects against physical media theft but not against a compromised database connection. Application-layer field-level encryption provides a second layer: even with full DB read access, an attacker sees only ciphertext.
Decision
encryptField and decryptField from src/lib/encryption.ts are called explicitly in service functions -- before Prisma writes and after Prisma reads. AES-256-GCM encryption uses KMS-derived data keys (sa3-student-pii-key). Encryption is never performed in Prisma middleware.
Rationale
- Explicitness prevents silent gaps -- raw queries or Prisma version changes cannot bypass it.
- Testable -- service functions with encryption can be unit-tested with mock KMS.
- Auditable -- CloudTrail logs every KMS
GenerateDataKeyandDecryptcall. - Separate KMS key --
sa3-student-pii-keykey policy restricts decryption to App Runner and Lambda roles only. - Base64 ciphertext fits the existing
StringPrisma type -- no schema change needed.
Consequences
Positive: Visible encryption in code, testable, CloudTrail audit trail, KMS key rotation handled by AWS, RDS credential compromise does not expose plaintext PII.
Negative: Engineers must remember to call encrypt/decrypt (mitigated by code review and Critical Rule 3). KMS API round-trip per field adds latency for bulk queries (mitigated by data key caching).