Skip to content

Repository files navigation

Cryple

cryple.io

Cryple is an encrypted personal drive: passwords, account details, private notes, long-form documents. You keep them encrypted. If you lose access, people you chose can help you back in.

Nobody at Cryple can read any of it. Not because of a policy, but because the servers never receive anything readable. This repository is the web client — the part that runs in your browser and does all the encryption and decryption. Everything else is storage.

The problem

Password managers protect you while you are alive and paying attention. They are bad at two things.

Losing access. Forget the master password on a properly encrypted vault and it is gone. The provider cannot help without holding a key, and if they hold a key they can read your data.

Cryple treats that as an encryption problem rather than a customer support problem. Nobody at Cryple holds a key that could help — or that could be compelled. The trade is stated plainly rather than hidden: what you hold is what you have, and the app's job is to make sure you know that before you need it, not after.

How it works

Your recovery phrase is the account

When you start, the app generates a phrase of 12 or 24 ordinary English words. That phrase is not a password you can reset. It is the account. Every key Cryple uses is calculated from it, the same way every time, on any device.

That has a good consequence and a hard one. The good one: type the phrase into a new browser and your vault is there, with no account recovery process and nothing to ask permission for. The hard one: the phrase cannot be reissued. Write it down and store it somewhere physical.

Your account name is a fingerprint of the phrase — a long string of hex characters. The math runs one way only, so the server can recognise your account without ever learning the words behind it.

What the server can and cannot see

Everything is encrypted in your browser before it is sent. The server receives sealed data and stores it.

It can see: that your account exists, how many items you have, roughly how large they are, and when they changed.

It cannot see: your recovery phrase, your PIN, or any item's contents. Even the labels you write are encrypted.

This is why "we cannot read your data" is checkable rather than a promise. The code that would do the reading is in this repository, and there is no key on the server side to do it with.

Your devices

Your recovery phrase is not stored anywhere, not even in your browser. Each browser you use becomes one of your devices, with keys of its own that never leave it. You type the phrase only to create the account, to add a device, and for account-level actions such as removing a lost device or deleting the account. While you type it, it is in the page's memory; the rest of the time the browser holds only its own keys. For the best protection, use Brave with a profile that has no extensions.

A device you lose can be removed from any other device. Removal changes every key it held, so it cannot read anything saved afterwards. It keeps whatever it had already copied: nothing can reach into a device that is gone.

Unlocking a device

Each browser has a 6-digit PIN. It unlocks that browser on a reload and after fifteen idle minutes. The PIN never leaves the device: the server helps check it without ever seeing it, and allows only a limited number of wrong tries. After the last one, the browser forgets your account and your recovery phrase adds it back. Your vault is untouched either way.

You also choose a mode:

  • Standard — your recovery phrase alone can add a device. A deliberate choice: nothing but losing your phrase can lock you out.
  • Paranoid — your phrase also needs an account PIN before it can add a device or delete the account. Someone who steals your phrase still cannot get in. Forgetting the account PIN ends the account. There is no reset, by anyone, ever.

You can move from Standard to Paranoid later, but never the reverse: a stolen phrase must not be able to switch protection off.

There is no account recovery, and that is the design

An earlier version of Cryple let people you nominated hold pieces of a key that could rebuild your phrase. It was removed on 2026-09-04, and the reasoning is worth stating rather than hiding:

  • A guardian had to already have a Cryple account, so the feature only worked for people whose friends had already installed an unfamiliar app and written down a seed phrase of their own.
  • "Your friends can restore your access" is the sentence that makes a privacy-minded reader ask who else can get in. For a product whose whole claim is that nobody can, the answer has to stay nobody.

So: your phrase and your PIN are yours to keep. In exchange, the answer to "who else could get into my vault" is nothing, with no asterisk. The app's obligation is to be honest about that up front — a printable kit at sign-up, plain words before you turn on Paranoid mode, and a later nudge to check that the copy you saved is the copy that works.

Why post-quantum

A vault runs on a timescale of decades. Encrypted data captured today can be stored and attacked later, and a sufficiently capable quantum computer would break the classical algorithms in common use now.

Your vault at rest does not depend on those algorithms: it is sealed with symmetric encryption, which a quantum computer weakens but does not break. The hybrid construction matters for the other case — encrypting something for another person, where the classical algorithms are what a future attacker would target. So when Cryple wraps a key for someone else, it uses two independent algorithms at once and combines them: one well-understood classical choice, one post-quantum standard. An attacker has to break both.

That machinery is built, frozen and tested against fixed vectors. It has no caller at the moment — private sharing, where you send one item to another person, is what will use it next.

Running it locally

Requires Node and a running instance of the Cryple API.

npm install
npm run dev

Then open http://localhost:3000.

Point the client at your API in .env.local:

NEXT_PUBLIC_BASE_API_URL=http://localhost:8080

It defaults to the value above.

The drive uploads and downloads straight from the object store, and the Content Security Policy only lets the page talk to hosts it names. Add the API's R2_ENDPOINT as well:

CSP_OBJECT_STORE_ORIGINS=https://<account-id>.r2.cloudflarestorage.com

Without it the development server blocks every drive transfer and says so in the console, and a production build refuses to finish — on Vercel, set it for every environment. See lib/security-headers.

npm test      # 469 tests
npm run lint
npm run build

For developers

Next.js 15 (App Router), React 19, TypeScript in strict mode, Tailwind v4. Cryptography is @noble plus the Web Crypto API.

Start with AGENTS.md. It carries the rules this codebase is built to and the precedence order when documents disagree: the frozen specifications win on byte layouts and constants, front-end-guide.md and front-end-endpoints.md win on the wire contract.

Module What it owns
lib/keys Recovery phrase to the full key tree
lib/encoding hex, base64 and key-format conversions
lib/pin The second factor, the local vault, the wipe policy
lib/session In-memory key custody
lib/api Transport, error codes, pagination, tokens
lib/signing Request signatures and the action table
lib/auth Sign-up, sign-in, restoring on a new device
lib/users Account, mode, public keys
lib/pqxdh Hybrid post-quantum encryption for another person
lib/sealed The versioned encrypted-blob format
lib/secrets Vault items
lib/app Product logic behind the interface
lib/security-headers The Content Security Policy and response headers
components The React screens

Dependency overrides

package.json carries three overrides, added on 2026-09-13 so that npm audit --omit=dev reports nothing:

Override Why
next → postcss: ^8.5.25 next@15.5.25 pins postcss at exactly 8.4.31, which has four advisories (XSS in stringify, source-map file reads). Only a minor version separates them, and the build is verified under it
nanoid: ^3.3.18 3.3.17, pulled in by postcss, can loop forever on a zero size
sharp: ^0.35.4 0.34.5 inherits libvips and libheif CVEs. 0.35.4 is inside the range next itself declares

Delete an override when its parent stops needing it: when next no longer pins an old postcss, which Next 16 already does not, or when npm ls shows the natural resolution is at or above the override. An override left behind silently holds a package back later.

After changing one, npm ls postcss sharp nanoid must show no invalid entry. A nested override does not replace a package the lockfile already records under that parent: delete that lock entry and reinstall.

The test suite includes a fixture that reproduces every key derivation against values generated by the backend. No backend test reads that file, so this suite is the only cross-client check that these derivations are correct anywhere in the project. Keep it green.

One note for anyone auditing: Cryple has no secp256k1 key and no Ethereum account. The key derivation path reserves that branch and never derives from it.

About

Zero-knowledge web client for Cryple — an end-to-end encrypted vault with BIP 39 authentication.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages