Encryption Architecture
End-to-End Encryption for Sensitive Data
Overview
Snuggli implements AES-256-GCM application-level encryption for all sensitive user data. This ensures that even if the database is compromised, user content remains encrypted and unreadable.
Encrypted Data Types
- Araba Messages: All chat content between users and Araba
- Journal Entries: Title, content, and gratitude items
- Mood Notes: Personal notes attached to mood entries
- Status Posts: User-generated content in community
- Conversation Previews: Message summaries
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ │
│ │ User writes │───▶│ Encrypt with │───▶│ Send encrypted │ │
│ │ content │ │ derived key │ │ to server │ │
│ └──────────────┘ └───────────────┘ └──────────────────┘ │
│ ▲ │
│ │ Request key │
│ ▼ │
│ ┌───────────────────┐ │
│ │ Key Cache (5 min) │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ BACKEND FUNCTIONS │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ │
│ │ getEncryptionKey │◀─── Master Key (ENV/KMS) │
│ │ - Auth user │ │
│ │ - Derive key │───▶ Per-user key = HKDF(master, user_id) │
│ │ - Audit log │ │
│ └──────────────────┘ │
│ │
│ ┌────────────────────┐ │
│ │ adminDecryptData │◀─── SuperAdmin only │
│ │ - Require reason │ │
│ │ - Create audit │───▶ Immutable audit log │
│ │ - Decrypt │ │
│ └────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ rotateEncryptionKeys│◀─── Quarterly rotation │
│ │ - Admin only │ │
│ │ - Re-encrypt all │───▶ New key version │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DATABASE │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Message │ │
│ │ - content: "[ENCRYPTED]" │ │
│ │ - content_encrypted: "base64..." │ │
│ │ - content_iv: "base64..." │ │
│ │ - _encryption: { version: "v1", algorithm: "AES-GCM" } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SecurityLog (Immutable Audit Trail) │ │
│ │ - All key access logged │ │
│ │ - All admin decryption logged │ │
│ │ - Cannot be deleted │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Key Management
Master Key Storage
- Stored in environment variables (ENCRYPTION_MASTER_KEY)
- In production: Use AWS KMS, GCP KMS, or Azure Key Vault
- NEVER stored in database or code repository
- Access restricted to backend service role only
Per-User Key Derivation
- Each user gets a unique encryption key
- Derived using HKDF: key = HKDF(masterKey, userId + email)
- Keys are never stored, always derived on-demand
- Cached client-side for 5 minutes only
Key Rotation (Quarterly)
- New master key set as ENCRYPTION_MASTER_KEY_NEW
- Run rotation function to re-encrypt all data
- Old key versions maintained for backward compatibility
- Audit log created for all rotation events
Access Control
| Role | Can Decrypt Own Data | Can Decrypt Others' Data | Requirements |
|---|---|---|---|
| User | ✓ Yes | ✗ No | Authentication |
| Admin | ✓ Yes | ⚠ Limited | Justification + Audit Log |
| Support | ✓ Yes | ✗ No | Metadata only |
Admin Decryption Warning
When admins access encrypted user data, they must provide a written justification. All access is permanently logged and cannot be deleted. Users may request access logs under GDPR/POPIA.
Developer Guide
Creating Encrypted Records
import { createEncrypted } from '@/components/security/EncryptedEntityWrapper';
// Instead of:
// await base44.entities.Message.create({ content: text });
// Use:
await createEncrypted('Message', { content: text });Reading Encrypted Records
import { listDecrypted, filterDecrypted } from '@/components/security/EncryptedEntityWrapper';
// List with decryption
const messages = await listDecrypted('Message', '-created_date', 50);
// Filter with decryption
const myMessages = await filterDecrypted('Message',
{ conversation_id: convId },
'created_date',
100
);React Query Integration
import { useEncryptedEntity } from '@/components/security/EncryptedEntityWrapper';
function MyComponent() {
const messageEntity = useEncryptedEntity('Message');
const { data } = useQuery({
queryKey: ['messages'],
queryFn: () => messageEntity.filter({ conversation_id: id }),
});
const mutation = useMutation({
mutationFn: (content) => messageEntity.create({ content }),
});
}Data Deletion & Retention
Account Deletion
When a user deletes their account:
- All encrypted records are permanently deleted
- User's encryption key salt is invalidated
- Deletion event is logged (without content)
- No recovery is possible after deletion
Retention Policy
- Chat messages: Retained for 12 months, then deleted
- Journal entries: Retained indefinitely until user deletes
- Mood data: Retained indefinitely until user deletes
- Security logs: Retained for 7 years (compliance)
Key Rotation Procedure
- Generate new master key
openssl rand -base64 32
- Set as ENCRYPTION_MASTER_KEY_NEW
Add to environment variables in dashboard
- Run dry-run rotation
await base44.functions.invoke('rotateEncryptionKeys', { newKeyVersion: 'v2', dryRun: true }); - Execute rotation
await base44.functions.invoke('rotateEncryptionKeys', { newKeyVersion: 'v2', dryRun: false }); - Update primary key
Move ENCRYPTION_MASTER_KEY_NEW to ENCRYPTION_MASTER_KEY
Schedule: Key rotation should be performed quarterly (every 90 days) or immediately if a key compromise is suspected.
