52 lines
2.4 KiB
Markdown
52 lines
2.4 KiB
Markdown
---
|
|
id: TASK-124
|
|
title: Encrypt all PII at rest in EncryptID database
|
|
status: Done
|
|
assignee: []
|
|
created_date: '2026-03-24 00:29'
|
|
updated_date: '2026-03-24 00:29'
|
|
labels:
|
|
- security
|
|
- encryptid
|
|
- database
|
|
dependencies: []
|
|
references:
|
|
- src/encryptid/server-crypto.ts
|
|
- src/encryptid/migrations/encrypt-pii.ts
|
|
priority: high
|
|
---
|
|
|
|
## Description
|
|
|
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
|
Server-side AES-256-GCM encryption for all PII fields stored in PostgreSQL. Keys derived from JWT_SECRET via HKDF with dedicated salts (`pii-v1` for encryption, `pii-hash-v1` for HMAC). HMAC-SHA256 hash indexes for equality lookups on email and UP address fields.
|
|
|
|
**Scope:** 18 fields across 6 tables (users, guardians, identity_invites, space_invites, notifications, fund_claims). Username and display_name excluded (public identifiers, needed for ILIKE search).
|
|
|
|
**Files:**
|
|
- `src/encryptid/server-crypto.ts` — NEW: encryptField(), decryptField(), hashForLookup()
|
|
- `src/encryptid/schema.sql` — 18 _enc/_hash columns + 4 indexes
|
|
- `src/encryptid/db.ts` — async row mappers with decrypt fallback, dual-write on inserts/updates, hash-based lookups
|
|
- `src/encryptid/server.ts` — replaced unkeyed hashEmail() with HMAC hashForLookup()
|
|
- `src/encryptid/migrations/encrypt-pii.ts` — NEW: idempotent backfill script
|
|
|
|
**Remaining:** Drop plaintext columns after extended verification period.
|
|
<!-- SECTION:DESCRIPTION:END -->
|
|
|
|
## Acceptance Criteria
|
|
<!-- AC:BEGIN -->
|
|
- [x] #1 All PII fields have corresponding _enc columns with AES-256-GCM ciphertext
|
|
- [x] #2 HMAC-SHA256 hash indexes enable email and UP address lookups without plaintext
|
|
- [x] #3 Row mappers decrypt transparently — callers receive plaintext
|
|
- [x] #4 Wrong encryption key cannot decrypt (verified with test)
|
|
- [x] #5 Same plaintext produces different ciphertext each time (random IV)
|
|
- [x] #6 Backfill migration encrypts all existing rows (0 remaining unencrypted)
|
|
- [x] #7 Legacy plaintext fallback works for pre-migration rows during transition
|
|
<!-- AC:END -->
|
|
|
|
## Final Summary
|
|
|
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
|
Deployed 2026-03-23. Commit `9695e95`. Backfill completed: 1 user, 2 guardians, 8 identity invites, 2 fund claims encrypted. 19/19 verification tests passed (ciphertext format, decryption, HMAC determinism, wrong-key rejection, random IV uniqueness). Plaintext columns retained for rollback safety — drop in follow-up task after extended verification.
|
|
<!-- SECTION:FINAL_SUMMARY:END -->
|