Skip to content

Repository files navigation

ZeroTrust

Encrypted personal vault for stuff you probably shouldn't leave in a Google Doc.


ChatGPT Image Jul 29, 2026, 07_34_07 PM

Why this exists

Most of us store passwords and other sensitive info in Notion, Google Docs, Sheets, random wikis, whatever's handy. Bank details, AWS keys, Cloudflare logins, SMTP creds. We've always done it. It wasn't great before, but with AI in the picture it's worse.

Companies are hoovering up user data (sometimes with consent, sometimes not) and feeding it to employees or third parties for training, research, whatever. That doc you shared with the team? The sheet with prod credentials? More people (and systems) can get at that data than you think. You only need one person with bad intentions.

Why ZeroTrust

No server. The app runs in your browser and syncs to your Google Drive. Drive never sees the actual content, only encrypted blobs. Plaintext stays in memory while you're unlocked. We don't even put readable secrets in IndexedDB, just ciphertext.

It's open source. Don't trust my deployment. Fork it, read the code, host it yourself on Vercel/Cloudflare/S3/wherever. Use your own Google OAuth app if you want.

Deploy your own

Build is just static files:

npm run build
# output in dist/

Host dist/ anywhere that serves a SPA (Vercel, Cloudflare Pages, Netlify, S3 + CloudFront, etc.).

You'll need a Google OAuth client:

  1. Google Cloud Console → APIs & Services → Credentials → Create OAuth client ID → Web application
  2. Authorized JavaScript origins: your app URL(s), e.g. http://localhost:5173 for dev and https://vault.yourdomain.com for prod
  3. Copy the client ID into VITE_GOOGLE_CLIENT_ID at build time (.env.local for dev, env var in your host's build settings for prod)
  4. Enable the Google Drive API for that project

The app only asks for drive.file scope. It can read/write files it created in Drive, not your whole Drive.

Data lands in a folder called ZeroTrustVault on the signed-in user's Drive.

Run it locally

npm install
npm run dev

You need Google Drive. There's no local-only mode. Put this in .env.local:

VITE_GOOGLE_CLIENT_ID=your-oauth-client-id.apps.googleusercontent.com

How encryption works

Short version: everything is encrypted in the browser before it hits Drive or IndexedDB. Drive and the local cache only see ciphertext. Your master password never gets stored anywhere.

Key setup

master password  (never stored, never leaves the device)
       │  Argon2id (per-vault random salt)
       ▼
      KEK  (key-encryption key, in memory only)
       │  AES-256-GCM  wrap
       ▼
  encryptedVaultKey  (stored in vault/meta)
       │  AES-256-GCM  unwrap (needs KEK)
       ▼
   Vault Key  (DEK, in memory only; encrypts/decrypts everything else)

Your password doesn't encrypt the vault directly. Argon2id turns it into a KEK, which unwraps a separate random Vault Key (the thing that actually encrypts your data). That way changing the master password later is just re-wrapping one key, not re-encrypting the whole vault.

  • KDF: Argon2id, memorySize: 19456 KiB, iterations: 2, parallelism: 1, 16-byte salt per vault (src/crypto/kdf.ts)
  • Vault Key: 256 random bits, once per vault (src/crypto/keys.ts)
  • Everything else: AES-256-GCM via Web Crypto, fresh 12-byte nonce per encrypt (src/crypto/encrypt.ts). Tampered ciphertext just fails to decrypt.

What's encrypted vs not

vault/meta is the only thing stored without content encryption. You need to read it to unlock. It has:

  • formatVersion, KDF params (algorithm, salt, iterations, …)
  • encryptedVaultKey: wrapped vault key (already encrypted, useless without your password)

Folders, secrets, versions, attachment metadata: all encrypted before storage (EncryptedStorageProvider) as:

{ "formatVersion": 1, "nonce": "<base64>", "ciphertext": "<base64>" }

Attachments

Big files get split into 512 KiB chunks. Each chunk gets its own AES-GCM encrypt + nonce, bundled into one envelope:

{ "formatVersion": 1, "kind": "chunked", "chunks": [ { "nonce": "...", "ciphertext": "..." }, ... ] }

Download decrypts chunk by chunk and stitches them back together.

Create / unlock

Create: random salt → derive KEK from password → generate Vault Key → wrap it → write {formatVersion, kdf, encryptedVaultKey} to vault/meta → wipe KEK from memory.

Unlock: read vault/meta → derive KEK again → try to unwrap encryptedVaultKey. Wrong password = GCM auth failure = "Incorrect master password." Right password = Vault Key stays in memory until you hit Lock (vaultKey.fill(0)).

Where data goes

VaultEngine
   → EncryptedStorageProvider   (encrypt/decrypt with in-memory Vault Key)
      → MirrorStorageProvider   (write: Drive first, then local cache)
         → GoogleDriveProvider  (source of truth)
         → LocalStorageProvider (IndexedDB cache; reads come from here)

Reads prefer the local cache. Writes go to Drive first; the cache updates only after Drive confirms. Drive is the source of truth.

Before you push

npm run lint
npm run typecheck
npm test
npm run build

About

Sensitive data scattered across Docs, Sheets, and wikis can be exposed to more people, systems, and AI pipelines than you realise. ZeroTrust keeps your data encrypted, stores only ciphertext in Google Drive, and is fully open source and self-hostable.

Resources

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages