Skip to content

lukasschwarz/zeropass

Repository files navigation

ZeroPass

Self-hosted, zero-knowledge password manager. The server never sees your master password and never sees a plaintext secret. All encryption happens client-side; the API stores an opaque AES-256-GCM blob.

Status: experimental. Built and self-hosted by the author. Audit before trusting it with anything you can't afford to lose. Pull requests welcome.


Why another password manager?

Because the existing self-hosted options either trust the server with your master password, lock you into a single client, or stop being maintained.

ZeroPass is intentionally small:

  • One Postgres + one Node API + one static web app, behind any reverse proxy.
  • Master password → Argon2id → master key → HKDF → vault key + auth key. The auth key is what hits the server; the vault key never leaves the device.
  • Vault blob is encrypted before upload. The DB row is meaningless without the user's master password.

Architecture

┌──────────────┐   ┌──────────────┐   ┌────────────┐
│   Web (SPA)  │   │ Chrome Ext.  │   │  Android   │
│   React+Vite │   │ MV3 + React  │   │  Compose   │
└──────┬───────┘   └──────┬───────┘   └─────┬──────┘
       │                  │                 │
       │   HTTPS + JWT    │                 │
       └─────────────┬────┴─────────────────┘
                     ▼
              ┌─────────────┐       ┌────────────┐
              │  ZeroPass   │──────▶│ PostgreSQL │
              │ API (Node)  │       │   16       │
              │ Fastify+Zod │       └────────────┘
              └─────────────┘
                     ▲
                     │ E2EE blob
                     ▼
              ┌─────────────┐
              │  Importer   │   CLI - 1Password .1pux, Bitwarden CSV
              │  (Node CLI) │
              └─────────────┘
Component Path Tech
API apps/api Fastify, Prisma, Postgres, Argon2id-on-server (auth key bcrypt)
Web client apps/web React, Vite, Tailwind, Zustand, @noble/hashes
Chrome extension apps/extension MV3, side panel + autofill, hash-wasm
Android client apps/android Kotlin, Compose, Autofill Service, Argon2 via JNI
Importer CLI apps/importer TypeScript, parses 1Password .1pux + CSV
Shared types packages/shared-types Zod + TS interfaces

Crypto model

master password
      │
      ▼
   Argon2id  (params per-user, returned by /auth/kdf-params)
      │
      ▼
 master key (32 B)
      │
      ├── HKDF-SHA256(info="vault") → vault key  (AES-GCM, client only)
      └── HKDF-SHA256(info="auth")  → auth key   (sent over TLS, server bcrypt)
  • The server stores bcrypt(authKey) and per-user KDF params - nothing more.
  • The vault is one big Bytes column: AES-256-GCM(JSON, vaultKey).
  • A monotonically increasing vaultVersion lets clients detect concurrent edits.
  • Refresh tokens are stored as SHA-256 hashes; raw tokens only live on the device.

The same KDF logic is implemented three times - in apps/web/src/crypto, apps/extension/src/crypto, and apps/android/.../crypto - and they MUST produce byte-identical keys for the same inputs.


Quick start (Docker)

git clone https://github.com/lukasschwarz/zeropass.git
cd zeropass
cp .env.example .env
# edit .env: set POSTGRES_PASSWORD, JWT_ACCESS_SECRET, JWT_REFRESH_SECRET
docker compose up -d

That brings up:

Service Port Notes
zeropass-db internal Postgres 16, named volume zeropass-db-data
zeropass-api 8200 Fastify API on /api/v1
zeropass-web 8201 Nginx serving the static React build

Open http://localhost:8201, register, and you're in.

For production, put it behind your favourite reverse proxy (Traefik / Caddy / nginx) and terminate TLS there.


Development

pnpm install
pnpm --filter @zeropass/shared-types build
pnpm --filter @zeropass/api db:migrate:dev
pnpm dev:api    # http://localhost:3000
pnpm dev:web    # http://localhost:5173 (proxies /api to :3000)

Run the test suite with pnpm test.

Chrome extension

pnpm --filter @zeropass/extension build
# load apps/extension/dist as unpacked extension in chrome://extensions

Android

apps/android is a standard Gradle project. Open it in Android Studio, sync Gradle, then run on a device. The default API URL points to http://10.0.2.2:8200 (emulator → host) - change it in the unlock screen.

Importer (1Password → ZeroPass)

pnpm --filter @zeropass/importer build
zpass-import --help
zpass-import 1pux ~/Downloads/export.1pux \
  --email me@example.com \
  --api http://localhost:8200/api/v1

The importer decrypts client-side, re-encrypts under your ZeroPass vault key, and POSTs the resulting blob - the server never sees the legacy export.


Security notes

  • Master password is everything. Lose it and the vault is unrecoverable; there is no escrow.
  • HTTPS is your job. The default network_security_config.xml on Android allows cleartext for LAN testing - tighten it for production.
  • The API does not currently support TOTP / hardware keys. Master password
    • device-bound refresh token is the only factor today.
  • No public security audit yet. The crypto primitives are standard (Argon2id, AES-256-GCM, HKDF-SHA256 via @noble/hashes and hash-wasm), but this codebase has not been independently reviewed. Don't bet your life on it.

If you find a vulnerability, please open a private GitHub security advisory rather than a public issue.


Project layout

zeropass/
├── apps/
│   ├── api/          # Fastify backend + Prisma
│   ├── web/          # React SPA
│   ├── extension/    # Chrome MV3 extension
│   ├── android/      # Kotlin / Compose
│   └── importer/     # CLI: 1Password .1pux, Bitwarden CSV
├── packages/
│   └── shared-types/ # Zod schemas + TS types
├── scripts/
│   ├── deploy-web.sh
│   └── backup-postgres.sh
├── docker-compose.yml
└── .env.example

License

MIT

Contributing

PRs welcome. Useful first contributions:

  1. A TOTP / WebAuthn second factor on top of the auth key.
  2. A Safari / Firefox port of the extension.
  3. iOS client (re-using the KDF and crypto contracts).
  4. End-to-end browser tests covering the autofill flow.

When opening an issue, include the component (api, web, extension, android, importer) and steps to reproduce.

About

ZeroPass - self-hosted zero-knowledge password manager (API, Web, Chrome extension, Android, CLI importer).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors