If you discover a security vulnerability, please report it responsibly by emailing security@gruber.dev (or open a private security advisory on GitHub).
Please do not open a public issue for security vulnerabilities.
WorkLedger is a local-first application. How your data is handled depends on which features you enable:
- All data (entries, settings) stored in your browser's IndexedDB — unencrypted
- No data leaves the browser
- No authentication, no server communication
- Entries are end-to-end encrypted using AES-256-GCM with a key derived from your sync ID
- Key derivation: sync ID →
SHA-256("crypto:" + syncId)→ PBKDF2 (100,000 iterations, SHA-256, server-provided salt) → AES-256-GCM key - Authentication: sync ID →
SHA-256("auth:" + syncId)→ auth token sent asX-Auth-Tokenheader. Domain separation ensures the auth token reveals nothing about the encryption key - Encrypted (inside
encryptedPayload): entry content (blocks), day key, creation time, tags - Not encrypted (visible to server): entry IDs,
updatedAttimestamps,isArchivedflag,isDeletedflag, integrity hash - Each encryption uses a random 12-byte IV — identical content produces different ciphertext
- The integrity hash is
SHA-256of the plaintext JSON, a secondary check on top of AES-GCM's built-in authentication tag - Your sync ID is stored unencrypted in IndexedDB — treat it like a password
- The search index (a separate IndexedDB store) is not encrypted and remains local-only; it is never sent to the server
- Note content is sent to the configured LLM provider (Ollama, Hugging Face, or a custom server)
- API keys are stored unencrypted in IndexedDB
- Ollama runs locally by default; other providers send data over the network
- XSS via editor content or imported data
- Malicious content in imported JSON files
- Dependencies with known vulnerabilities
- Unencrypted local storage of API keys and sync IDs