Encrypted knowledge at vault-speed. Pages, boards, search β all sealed end-to-end. One codebase. iOS, Android, web, desktop. Built on Starfish.
Note
Reference app & proof of concept β shows what real E2EE sync can do. Not production-ready (yet).
π Real E2EE β BIP-39 seed β Ed25519 + Kyber keys in secure storage. Pages, blocks, boards sealed per-space. Server sees ciphertext only.
π Notion-grade editing β Nested blocks (text, headings, lists, todos, code, callouts). Drag to reorder. CRDT-backed live collaboration, conflict-free.
π― Kanban + boards β Columns, drag-drop cards, properties (assignee, status, due date). Same CRDT foundation as pages. Instant sync.
π One codebase, all platforms β Expo SDK 56 β iOS, Android, web, Electron. No "mobile vs. web" fork.
β‘ Live by default β REST pull + SSE firehose. Presence, activity, changes streamed via NATS.
π¨ Design-forward β Newsreader (editorial serif) + Spline Sans (clean body). Warm pearl, octopus-ink indigo. Light & dark, one source of truth.
π No passwords β Seed-based. Multi-device pairing via QR + PIN. NIP-07 Nostr sign-in. Hold multiple spaces, switch live.
π΄ Offline-first β Pages & boards served from local cache. App opens & reads instantly, no connection needed.
π Instant search β Full-text across pages, blocks, boards, documents. Real-time, no indexing lag.
Spaces & folders β organize by project, area, topic. Per-node access: space (E2EE, all members), invite (E2EE, invited users only), or public (readable by anyone).
Pages β Notion-style nested blocks. Text, headings, lists, todos, code, callouts, quotes. Drag to reorder, nest infinitely. Live sync across devices.
Boards β Kanban columns + cards. Drag between columns, add properties, filter. CRDT foundation = instant conflict-free sync.
Cross-linking β Mention pages, reference documents, @-mention members. Builds a knowledge graph inside your vault.
Rich formatting β bold, italic, code, strikethrough, links. All encrypted end-to-end.
Attachments β Images, files, media embedded & sealed client-side before upload. Storage path bound into the seal's AAD β no hostile swaps.
Real-time collab β Multiple devices edit the same page. Edits merge conflict-free. Full history preserved.
Offline + sync β WAL/CRDT means concurrent offline edits converge correctly when you reconnect. No merge conflicts.
Plaintext lives on your devices only. The server is untrusted β stores & relays opaque ciphertext, never holds a key.
Your seed is everything β 12-word BIP-39 (128 bits entropy) stretched via Argon2id into your root identity: Ed25519 signing key + Kyber/ML-KEM key-encapsulation pair.
Per-space keyrings β Each space has one keyring whose CEK seals all pages, blocks, boards via AEAD (AES-GCM).
Capabilities, not accounts β Every request signed by your device key & authorized via scoped cap-cert. No server-side passwords or sessions.
Sealed at rest everywhere β Native: keys in OS secure store. Web: seeds sealed under random Vault Master Key, wrapped by PIN + optional WebAuthn passkey.
Full technical model: Encryption model section below.
pnpm install
pnpm infra:up # NATS in Docker
pnpm dev # Starfish :8787 + Whistlers SSE :8080
pnpm web # App at localhost:8081Create your first vault. Done.
Native? pnpm ios Β· pnpm android Β· pnpm desktop
Everything below is the nuts-and-bolts reference: prerequisites, the full dev loop, ports, commands and project layout.
- Node.js β₯ 20 (tested on 24)
- pnpm 10 β
npm i -g pnpm - Docker (or OrbStack) β for NATS
pnpm install
Three services need to run. Open two terminals:
Terminal 1 β infrastructure + backend
pnpm infra:up # start NATS in Docker (detached)
pnpm dev # Starfish server :8787 + Whistlers SSE :8080
Terminal 2 β frontend
pnpm web # Expo web :8081
# or
pnpm ios # Expo iOS simulator
pnpm android # Expo Android emulator
pnpm desktop # Electron wrapper
Whistlers restart.
pnpm devstarts Whistlers once β it does not watch for config changes. If you editinfra/whistlers.config.jsonor bump the@drakkar.software/whistlerspackage, kill and re-runpnpm dev.
| Service | Port | What |
|---|---|---|
| Expo / Metro | 8081 | Mobile/web app (dev) |
| Starfish server | 8787 | Sync API + /events SSE proxy |
| Whistlers | 8080 | Internal NATSβSSE gateway |
| NATS | 4222 | Message bus (Docker) |
| Command | What |
|---|---|
pnpm infra:up |
Start NATS (Docker, detached) |
pnpm infra:down |
Stop all Docker services |
pnpm dev |
Starfish server + Whistlers (concurrently) |
pnpm web |
Expo web |
pnpm ios |
Expo iOS |
pnpm android |
Expo Android |
pnpm desktop |
Electron wrapper |
pnpm typecheck |
TypeScript check all workspaces |
pnpm test |
Run tests |
apps/
mobile/ β Expo SDK 56 universal app (@octovault/mobile)
server/ β Hono Starfish server (@octovault/server)
desktop/ β Electron wrapper (optional)
packages/
tsconfig/ β shared TypeScript base config
The app is built on Starfish, a WAL/CRDT sync engine:
- Pages & blocks (
src/lib/page-model.ts) are pure projections over aWalDocumentβ an RGA (Replicated Growable Array) for block order, per-block char-RGA for text, and LWW (Last-Writer-Wins) registers for properties. All edits are CRDT ops; they merge conflict-free even if concurrent. - Boards (
src/lib/board-model.ts) use RGA lists for columns and cards, with per-card LWW registers for properties. - Live sync happens via REST pull + SSE firehose: a
/pullfetches the WAL op-log since your last cursor, and/eventsstreamsoctovault.object.changednotifications in real time via NATS β Whistlers. - The object tree (
src/lib/use-objects) stays on Starfish's proven union-merge engine (spaces, pages, folders); WAL backs page and board content only. - Crypto is injected into Starfish's transport, signer, and encryptor
interfaces (
src/lib/starfish/wal/*).
src/lib/starfish/wal/β live wiring ofstarfish-wal: transport, encryptor, signer, snapshot store.src/lib/page-model.ts/board-model.tsβ pure CRDT projections + mutations.src/lib/use-page.ts/use-board.tsβ React hooks owning the openβpullβcommit lifecycle.src/components/work/PageView.tsx/BoardView.tsxβ the editors.src/theme.tsβ design tokens (single source of truth).
See apps/mobile/CLAUDE.md for full design rules and conventions.
The full encrypted sync layer lives in the Starfish SDK; the app supplies only
platform adapters under src/lib/starfish/*. Threat model: the server and transport
are untrusted β they see ciphertext, signed request envelopes and capability
scopes, never plaintext or private keys.
- A 12-word BIP-39 mnemonic (128-bit entropy) is the only master secret.
- Each device holds an Ed25519 keypair (request signing) and a Kyber/ML-KEM keypair (key encapsulation for space keyring recipients).
- No server-side passwords or sessions. Every request is signed by the device Ed25519 key.
- Access is gated by scoped cap-certs: a device cap and a member cap per joined space. The server authorizes a request only if a presented cap proves the scope.
- One keyring per space covers every page and board. Its CEK seals all content with AEAD (AES-GCM) before it leaves the client.
- Attachments are stored as opaque blobs; pages keep only small references. The blob's storage path is bound into the seal's AAD.
- Key rotation / epochs: inviting a member rotates the keyring epoch. Members see content from their epoch forward.
- The existing device seals the bundle with the user PIN (Argon2id β AES-GCM), and publishes it to a public rendezvous keyed by a 16-byte CSPRNG nonce.
- The QR payload is
nonce.rootEdPub. The new device opens the blob with the PIN and pins the bundle to the QR-supplied root pubkey.
- Native: keys persist in
expo-secure-store(Keychain / Keystore). - Web:
localStorageholds only an AEAD envelope. All accounts live in oneVaultsealed under a random Vault Master Key (VMK) via AES-GCM; the VMK is wrapped by the PIN (Argon2id-stretched) and optionally a WebAuthn passkey PRF secret.
OctoVault is MIT-licensed. Starfish and supporting infrastructure are licensed separately β refer to their repositories for terms.
Contributions welcome. Open an issue first to discuss, or read apps/mobile/CLAUDE.md for guidelines.
- π Open an issue
- π¬
paul@drakkar.software
Built with π by Drakkar Software.
