Key Principles
- Trust the Math, Not Us: Heavy emphasis on client-side encryption and open-sourcing sensitive code paths.
- Minimize Sensitive Data Exposure: Backend endpoints handling decrypted data are minimal and open-source.
- User Control: The user's PIN is the master key to their data, and it never leaves their device.
This approach allows for powerful LLM-driven journaling and insight generation while maintaining a strong privacy and security posture by keeping the user's root_key and decrypted data strictly on the client-side.
Core Encryption & Data Handling
Client-Side Key Derivation:
- A user-defined 4-digit PIN (never transmitted) is combined with a server-provided
encryption_salt using PBKDF2 (many iterations) on the client to derive a root_key.
- This
root_key remains exclusively on the client and is never persisted to local storage (requiring PIN entry on each session/refresh).
- For each database row containing sensitive data, a unique
row_key is derived using BLAKE3 from the row's UUID and the root_key.
Data Encryption & Storage:
- All sensitive data columns (prefixed
encrypted_) are symmetrically encrypted using their respective row_key before being persisted to PostgreSQL as byteA.
- The UI clearly indicates encrypted fields (e.g., "🔒 [Encrypted]") and remains locked for mutation until the PIN is entered and data is decrypted in memory.
Secure Backend Operations:
- LLM Interactions: Decrypted message content is sent to a thin, open-source backend wrapper which relays to the LLM provider (e.g., OpenAI). Responses are streamed back, decrypted on the client, and then periodically encrypted by the client for persistence.
- Backend Jobs (e.g., Metadata Generation): For tasks requiring the backend to process sensitive data and persist new encrypted data, the client generates the new row's
row_key and supplies it along with the decrypted data to a dedicated, open-source backend endpoint. The endpoint uses this row_key to encrypt the results for that specific row and immediately discards the key. The root_key is never exposed to the backend.
Architecture & Stack Highlights