Privacy-focused tools for local-first computing.
NOTE: This is still a work in progress. Check any use of cryptography before using it.
smugrobot is a monorepo of tools built around one principle: your data is encrypted on your device before it goes anywhere. There is no backend, no account, and no sync service that can read your content. If you use Dropbox to sync across devices, Dropbox only ever receives opaque ciphertext — the encryption key never leaves the device that holds it.
echidna.js — encrypted document store
A portable TypeScript library for storing text documents encrypted end-to-end. Document bodies are encrypted with XSalsa20-Poly1305 (TweetNaCl) before being written to any adapter. Metadata (title, tags, timestamps) is stored as plaintext so you can list, sort, and filter without decrypting every document.
Storage adapters: memory · localStorage · IndexedDB · Node.js filesystem · Dropbox · AsyncStorage (React Native) · expo-file-system
npm install echidna.jsapps/notes — private notes
A Progressive Web App for writing Markdown notes that are encrypted at rest in IndexedDB. Three-pane layout with folder organisation, GitHub-Flavored Markdown editor (task lists, tables, strikethrough), passphrase-based vault, and a lock button that clears the session key immediately. No network requests after first load.
@smugrobot/ui — vault-ui design system
Web Components for building privacy-focused interfaces. Works in any framework or plain HTML. Components: vault-button, vault-card, vault-input, vault-textarea, vault-badge, vault-alert, vault-toggle, vault-select, vault-spinner, vault-avatar, vault-listbox, vault-popover.
@smugrobot/utils — shared utilities
Shared TypeScript utilities used across the monorepo.
- Cipher: XSalsa20-Poly1305 via
nacl.secretbox - Key derivation: scrypt (
N=131072, r=8, p=1) or PBKDF2 (600,000 iterations, SHA-256) - Nonce: 24 random bytes generated fresh on every write — same plaintext never produces the same ciphertext
- What is never stored: the passphrase, the derived key, or any hash that would allow offline verification
- Wrong-key detection: comes entirely from the AEAD tag failing; garbage is never returned
- Sync: the Dropbox adapter encrypts locally before upload — the provider only ever sees opaque blobs
- Session key: stored in
sessionStoragefor the lifetime of the tab, cleared on lock or close
Prerequisites: Node.js 20+, npm 10+
npm installWorkspace scripts (run from the root):
npm run build # build all packages
npm run test # test all packages
npm run lint # lint all packages
npm run typecheck # type-check all packagesRun the notes app:
cd apps/notes
npm run devMIT