CipherBox is a personal cloud storage system where privacy is structural rather than a policy promise. All encryption happens on the client β the server never sees plaintext data, file names, or usable keys. Encrypted content lives on IPFS and encrypted metadata on IPNS, so the network β not CipherBox's infrastructure β is the canonical store: clients verify every record cryptographically, and a vault remains readable and recoverable with the owner's key alone, without any CipherBox service. Keys derive deterministically from a Web3Auth login, so the same user gets the same vault on any device and login method.
You can store, organize, and share files and folders across a web app and a desktop app that mounts the vault as a native drive, with multi-device sync and cryptographic revocation of shared access.
The system is mid-rewrite. v2 is being built on main against the blueprint corpus in
blueprint/; v1 is frozen on branch v1 (tag v1-freeze) and receives no
changes. v1 was a working technology demonstrator (staging-only, never in production) whose
TypeScript/Rust twin-engine architecture and TEE-based republishing are replaced wholesale in
v2. main now carries the v2 workspace skeleton, filled in slice by slice against the
blueprints β when code and blueprint disagree, the blueprints win.
- One Rust core. All codec, crypto, and state logic lives in two crates:
crates/core(wire formats, sealing, key derivation) andcrates/engine(the one stateful brain β sync, rotation, grants, trust decisions). Desktop links it natively; the web app runs the same crates as WASM in a worker. One implementation, one set of known-answer tests. - Zero-knowledge API. The NestJS API is a thin residual surface: account registry, pin relay, and a grant mailbox. It stores only ciphertext and routes IPFS/IPNS operations; it never serves records and never holds material that could decrypt anything.
- Seeded key hierarchy with cheap revocation. Keys derive per scope (a derivation domain) from a single seed via a BLAKE3 tree KDF. Sharing a folder anchors a new scope; revoking is an O(1) root cut that mints a fresh seed and a new epoch, with descendants re-sealed lazily by ordinary writes rather than an eager cascade.
- Verified reads, fail-closed. Every resolved IPNS record passes an adoption gate β signature, epoch, and structure checks β before the engine trusts it. A gate failure is treated as a trust violation, never as mere staleness.
- Pull-only sync. Clients poll a focus window over IPNS, cache-first, with an offline operation queue. No push channel in v2.0 (the seam for one is in place).
- Keyless republishing. IPNS records expire; a republisher module inside the API re-PUTs client-signed records (90-day end-of-life) without ever holding a key β v1's hardware-enclave (TEE) republisher is gone.
| Component | Technology |
|---|---|
| Core | Rust β crates/core + crates/engine, compiled natively and to WASM |
| Crypto | XChaCha20-Poly1305 sealing, BLAKE3 tree KDF, X25519 + HPKE wrapping, Ed25519/secp256k1 signing |
| Web | React + TypeScript (apps/web), engine hosted in a worker via packages/client |
| Desktop | Tauri v2 + FUSE mount β FUSE-T (macOS), libfuse3 (Linux), WinFsp (Windows) |
| API | NestJS + PostgreSQL |
| Storage | IPFS (Kubo) + IPNS, with self-hosted delegated routing (someguy) |
| Auth | Web3Auth MPC Core Kit β email OTP, OAuth, external wallet |
The normative source of truth for the v2 build lives in this repo:
| Document | Purpose |
|---|---|
CONTEXT.md |
The v2 ubiquitous language β every domain term, defined once |
blueprint/core.md |
Wire formats, crypto, KDF catalog, known-answer tests |
blueprint/engine.md |
The engine β seam traits, sync, rotation, adoption gate |
blueprint/api.md |
API residual surface, registry, mailbox, republisher |
blueprint/web-client.md |
WASM hosting, tab leadership, web client |
blueprint/desktop.md |
FS projection, host adapters, Tauri shell |
blueprint/testing.md |
Suite map, CI gates, coverage policy |
blueprint/deploy.md |
Freeze mechanics, release management, staging pipeline |
For researchers: the as-built v1 specification corpus, ADRs, and the complete design-decision
history behind v2 live in FSM1/cipher-box-next β
its wayfinder map (issue 1) indexes every decision. The docs/ folder here is v1
legacy and is being rewritten during the build.
The v2 workspace (see the blueprints; apps/desktop and tests/ land with their build phases):
cipher-box/
βββ crates/
β βββ core/ # Wire formats, sealing, KDF catalog β all crypto lives here
β βββ engine/ # The one stateful engine: sync, rotation, grants, trust
β βββ wasm/ # wasm-bindgen bindings over the engine for the web
β βββ fuse/ # FS projection core + per-OS host adapters
βββ packages/
β βββ client/ # Worker hosting for the WASM engine, browser seams, tab leadership
βββ apps/
β βββ web/ # React UI β renders engine state, forwards intent
β βββ desktop/ # Tauri v2 shell around the natively linked engine
β βββ api/ # NestJS zero-knowledge API
βββ blueprint/ # The normative v2 design corpus
βββ tests/ # Cross-cutting suites, incl. the live clientβAPI contract suite
Prerequisites: Node.js 22+, pnpm 10+, Docker, and the Rust toolchain (pinned by
rust-toolchain.toml).
Both services read a .env copied from a checked-in template. Those templates are the
one place the local stack's configuration is written down; this page does not repeat
their contents.
docker compose -f docker/docker-compose.yml up -d --wait --wait-timeout 180
pnpm installThat brings up Postgres (5432), Kubo (5001 RPC, 8080 gateway), someguy (8190), and the mock record store (3001). Kubo's RPC is an unauthenticated admin API and the dev compose binds it to all interfaces, so run this stack on a network you trust.
--wait holds until every service's healthcheck passes and exits non-zero if one does
not within the timeout, so the migration below cannot race a Postgres still starting.
cp apps/api/.env.example apps/api/.env
pnpm --filter @cipherbox/api migration:run
pnpm --filter @cipherbox/api devThe template's defaults match the compose stack, so it runs as copied. Both the server
and the migration CLI read apps/api/.env from the package directory, which
pnpm --filter sets as the working directory.
Its two secrets are throwaway values for a loopback stack β never reuse them in a
deployed environment. JWT_SECRET signs access tokens, and anyone holding
TEST_LOGIN_SECRET can mint a session for any account outside production.
In a second shell:
cp apps/web/.env.example apps/web/.env
pnpm --filter @cipherbox/web dev- API: http://localhost:3000 (OpenAPI at
/api-docs) - Web: http://localhost:5173
Vite reads .env at build time, so rebuild after editing it. The template leaves
VITE_READ_ACCELERATOR_URL commented out on purpose: dormant is the content gateway's
fail-closed state, and a blank value must land there rather than configuring a gateway
whose every request fails.
Compose starts two /routing/v1 backends, and a local stack should use
mock-ipns-routing on port 3001 β what both templates ship, as ROUTING_V1_URL
(API republisher) and VITE_ROUTING_ENDPOINTS (web client). It is hermetic and
in-memory, so a record published locally resolves immediately and deterministically, and
no test vault's IPNS names reach the public network. CI and the web-e2e suite make the
same choice.
someguy on 8190 participates in the real accelerated DHT. It is there for staging
parity and for deliberately testing public-network propagation; point the two variables
above at http://localhost:8190 only when that is what you are testing. Both must name
the same backend, or the republisher re-PUTs into a store the client never reads.
The API's write path is live end to end: authenticate and POST /content/upload
returns 201 with bytes pinned in the local Kubo.
Interactive login through the web UI needs VITE_WEB3AUTH_CLIENT_ID,
VITE_WEB3AUTH_VERIFIER and VITE_GOOGLE_CLIENT_ID, which a clean checkout does not
carry β the UI boots and renders without them, but a Core Kit session cannot be created.
The suites that need an authenticated session use the build-time introspection hook
instead; see tests/web-e2e/README.md.
A first folder create does not yet publish, because nothing provisions a fresh account's first vault pointer, so its writes are accepted, rendered pending, and reach no endpoint.
- The server never holds plaintext data, file names, or unencrypted keys, and never serves IPNS records β clients resolve and verify them against the network.
- Private keys and seeds are never stored in browser storage or sent to the server.
- All cryptography is implemented once, in
crates/core, with a frozen catalog of key derivations; TypeScript contains no crypto of its own. - Sharing wraps scope seeds to recipients with X25519 + HPKE; revocation is cryptographic (key rotation), not access-list bookkeeping.
- Sensitive material is zeroized after use.
See blueprint/core.md for the primitives and
blueprint/engine.md for the trust model.
This project is inspired by discussions and planning while working on ChainSafe Files. A massive shout-out to all the colleagues who worked on the original ChainSafe Files project.
