Skip to content

Repository files navigation

🔐 BlindPass

CINode.jspnpmLicense: MITDocker ServerDocker Webapp

Self-hostable, end-to-end encrypted password manager built around privacy by design. Your secrets never leave your device unencrypted — the server is a cryptographically dumb blob store that never sees plaintext. Sign up with a username only — no email address, no phone number, nothing that ties your account to your real-world identity.

Four containers (Web app, Server, PostgreSQL, Redis). MIT licensed. Small enough to audit in an afternoon.


Contents


🤔 Why BlindPass?

Use BlindPass when you want the operational shape of a password manager, but not the trust shape of a SaaS account. Most password managers make a reasonable trade: polished sync, recovery, sharing, support, and enterprise controls in exchange for trusting a vendor-operated identity and storage system. BlindPass chooses a narrower trade: fewer vendor conveniences, more user control.

BlindPass is for developers, sysadmins, and privacy-pragmatic operators who care about where secrets live, what identifiers exist, and how much code must be trusted. The server is a cryptographically dumb blob store. It stores ciphertext, public material, salts, and authentication state. It does not receive plaintext vault data, a master password, a master-password hash, an email address, a phone number, or billing identity.

Your master password never leaves your device. Sign-in uses your immutable username plus an authenticator-app code; your password is used only in the browser to derive a KEK via Argon2id. That KEK unwraps your MasterKey, which unwraps VaultKeys, which unwrap ItemKeys. Keys exist only in memory and are zeroed when you lock your vault.

BlindPass is MIT-licensed and self-hostable on four containers. Your data lives on your infrastructure, under your control. The codebase is intentionally small enough to audit in an afternoon: read the code, verify the crypto, run it yourself.

What BlindPass optimizes for

  • No identity exhaust — username-only accounts; no email, phone, billing profile, telemetry, or vendor account graph.
  • No server-side password verifier — the server never receives your master password or a reusable master-password hash.
  • Small trust surface — fewer product features than large suites, but less machinery between you and the crypto model.
  • Self-hosting as default posture — four containers, no managed cloud dependency, no vendor storage backend.
  • Operator control — export/import, delete, lock, revoke sessions, and run the whole system yourself.

Fair comparison

Research checked May 9, 2026. "Zero knowledge" below means the vendor says vault contents are encrypted/decrypted client-side and unavailable to employees. It does not mean every product has the same metadata exposure, recovery model, sharing model, or server compromise resistance.

ProductBest fitHosting modelSource transparencyAccount identityMaster password / verifier postureNotable strengthsTrade-offs vs BlindPass
BlindPassTechnical users who want self-hosted, low-identity password managementSelf-hosted by designMIT-licensed repo; small audit surfaceUsername onlyMaster password never leaves browser; no server-side master-password hashNo email identity, Argon2id browser key derivation, TOTP sign-in, encrypted import/export, simple deploymentYounger project; fewer native clients and enterprise controls; no browser extension by design (see ADR-0002)
1PasswordFamilies, teams, businesses needing polished apps and recovery/admin featuresVendor cloud membershipProprietary apps with published audits and certificationsEmail/account membershipAccount password plus device-generated Secret Key; 1Password says both are needed to decrypt dataMature apps, strong UX, Secret Key hardens weak account passwords, Watchtower, admin controlsNot self-hosted; 1Password 8 requires membership; email/account identity required
BitwardenUsers wanting mature open source, broad clients, optional self-hostingVendor cloud or supported self-hostOpen source/source available, third-party auditsEmail accountEmail + master password derive keys; Bitwarden says it never stores master password or crypto keysExcellent client coverage, strong free/premium tiers, org sharing, self-host options including Docker/KubernetesLarger system to audit; self-hosting more operationally complex; email identity required
Proton PassProton ecosystem users who want privacy features plus password managementVendor cloudOpen-source apps, public auditsProton account/emailProton says data is end-to-end encrypted, including metadataEmail aliases, Proton Sentinel, open-source clients, strong privacy brandNot self-hosted; tied to Proton account ecosystem; broader suite trust surface
KeePassXCLocal-first users who want no cloud account at allLocal encrypted file; user chooses sync methodGPLv3 open sourceNoneLocal database password/key file; no remote auth serviceCloud-free, subscription-free, mature desktop app, ANSSI security visaNo built-in managed sync/sharing/server; user owns backup/sync ergonomics
DashlaneConsumers and businesses wanting polished managed security featuresVendor cloudClient transparency and security docs; not fully open sourceEmail/accountDashlane says vaults encrypt/decrypt locally; Argon2d key derivation documentedStrong UX, passkeys/passwordless options, confidential computing for some enterprise flowsNot self-hosted; email/account identity required; broader managed-service dependency
NordPassUsers wanting simple cloud sync and modern managed UXVendor cloudProprietary with auditsEmail/accountNordPass says local encryption with Argon2id-derived key and zero-knowledge architectureXChaCha20 positioning, breach scanner, email masking, simple appsNot self-hosted; email/account identity required; product is vendor-cloud centered
KeeperBusinesses needing compliance, sharing, and admin controlVendor cloudProprietary with security documentation, certifications, auditsEmail/accountKeeper says encryption/decryption happen locally and master password is not transmittedEnterprise controls, record-level sharing, compliance posture, secrets managerNot self-hosted for normal vault use; larger enterprise platform; email/account identity required
LastPassUsers already in LastPass or businesses needing managed migration pathVendor cloudProprietary with security documentationEmail/accountLastPass says master password derives encryption/auth material; PBKDF2-SHA256 documentedFamiliar UX, business features, broad platform support2022 incident exposed backups containing customer vault data and metadata; not self-hosted; email/account identity required

BlindPass is not trying to beat every product on feature count. 1Password, Bitwarden, Keeper, Dashlane, Proton Pass, NordPass, LastPass, and KeePassXC all have valid use cases. BlindPass exists for a narrower reason: you want a password manager whose server cannot become an identity dossier, whose storage can live on your infrastructure, and whose implementation is small enough that trust can be replaced with inspection.

Sources: 1Password security model, 1Password Secret Key, 1Password standalone vault migration, 1Password audits, Bitwarden security white paper, Bitwarden self-hosting, Proton Pass security, KeePassXC, Dashlane security, Dashlane architecture, NordPass security, NordPass zero-knowledge architecture, Keeper encryption model, Keeper security, LastPass zero-knowledge model, LastPass 2022 incident notice.


📸 Screenshots

Sign inRecovery key reveal during registration
Sign inRecovery key reveal during registration
Vault — populatedItem detail
Vault list with items of every typeItem detail
New item type pickerSettings
New item type pickerSettings

Screenshots are captured by make screenshots (Playwright). The recovery key shown is the canonical all-zeros BIP39 placeholder phrase, not a real secret.


✨ Features

Core

  • 🔐 End-to-end encrypted — all encryption happens on your device; the server only stores ciphertext
  • 🏠 Self-hostable — own your data, run on your own infrastructure
  • 🗂 Multiple vaults — organize secrets into separate encrypted vaults

Item types

  • 🔑 Login items — username, password, URL, and notes
  • 📝 Secure notes — encrypted freeform text (title + content)
  • 💳 Payment cards — cardholder, number, expiry, CVV, and notes
  • 🪪 Identities — name, address, phone, email, and company for form-filling
  • TOTP authenticator — issuer, secret, algorithm, digits, and period
  • 🗝️ Developer credentials — API tokens, client/secret pairs, and SSH keypairs with structured metadata
  • 🪙 Crypto wallets — BIP39 mnemonic seed phrases with optional network, derivation path, and passphrase

Vault management

  • 🔗 Vault sharing — share vaults with others via asymmetric key sealing
  • 📤 Encrypted export/import — back up and restore your vault (BlindPass .json / .blindpass format)
  • 📥 Import from other managers — 1Password (.1pux), Bitwarden (JSON), Dashlane (ZIP), LastPass (CSV), Chrome/Firefox (CSV), KeePassXC (CSV), Apple Keychain (CSV), and Proton Pass (JSON)

Account

  • 🚫 No email required — accounts are identified by username only; no personal information is collected or stored
  • 🗝 Recovery key — BIP39 mnemonic to regain access if you forget your password
  • 🔢 Authenticator-based sign-in — username + TOTP for login and sensitive actions
  • 🫆 Biometric unlock (PWA) — opt-in per-device unlock via Touch ID, Face ID, Windows Hello, or Android biometric using WebAuthn PRF; wraps the master key locally and the server is uninvolved. On Android, only Google Password Manager currently supports the PRF extension — third-party passkey providers (Bitwarden, 1Password, …) cannot be used for biometric unlock yet (see ADR-0003 and the compatibility matrix)
  • 🕐 Version history — view and restore previous versions of any item
  • 🗑 Trash & restore — deleted items go to trash; restore or permanently purge them
  • 🔄 Password change — re-encrypt all key material under a new master password
  • 🖥 Session management — view all active sessions, revoke individual sessions remotely
  • Account deletion — permanently delete your account and all associated data

Administration

  • 🛡 Registration gate — admin can open or close sign-ups without affecting existing accounts
  • 👤 User management — list users, revoke sessions, or delete accounts from the admin panel
  • 📊 Vault & item quotas — default caps (10 vaults / 1 000 items) with per-user overrides
  • 💪 Password strength gate — registration rejects weak master passwords using zxcvbn entropy scoring; short or predictable passwords are blocked regardless of character composition

Clients

  • 📱 Web app (mobile-optimized PWA) — renders as a 430 px mobile shell on all screen sizes, optimized for the phone you are actually logging into things from; see ADR-0004
  • 🚫 No browser extension — by design; see ADR-0002

🔒 How Encryption Works

BlindPass is built on a zero-knowledge architecture — meaning we are technically incapable of reading your data, even if compelled to. The cryptography architecture was inspired by Ente's published architecture, then adapted to BlindPass's username-first account model, vault sharing flow, and self-hosted deployment target.

Username + authenticator authentication

Sign-in uses a permanent username plus a 6-digit TOTP authenticator code. Your master password is never sent to the server — not even as a hash. It exists only in your browser, used solely to derive encryption keys.

Login flow

Browser Server
│ │
│ POST /auth/login/start { username } │
├───────────────────────────────────────▶│ create login challenge state
│ │
│ POST /auth/login/complete │
│ { username, authenticatorCode } │
├───────────────────────────────────────▶│ verify TOTP · create session
│ │
│◀─── 200 { message: "Authenticated" }
│◀─── Set-Cookie: bp_session=…; HttpOnly; SameSite=Strict
│ │
│ GET /user/keys → encrypted key material
│ Argon2id(password, kekSalt) → KEK
│ decrypt(encryptedMasterKey) → masterKey [memory only]
│ decrypt(encryptedVaultKey) → vaultKey [memory only]

The session cookie is HttpOnly — JavaScript on the page cannot read it. The server stores only the SHA-256 hash of the token, never the token itself. On a page reload, you re-enter your master password to re-derive keys; no OTP is required.

Your password never leaves your device

When you log in, BlindPass uses Argon2id — a memory-hard key derivation function designed to resist GPU and ASIC brute-force attacks — to derive a Key Encryption Key (KEK) from your master password. This derivation happens entirely in your browser. Your password is never sent over the network.

A layered key hierarchy

Each layer of encryption uses a unique key, so compromising one layer doesn't expose another:

your password
└─ Argon2id(password, kekSalt) → keyEncryptionKey (KEK)
└─ decrypt(encryptedMasterKey) → masterKey
├─ decrypt(encryptedPrivateKey) → privateKey (used for vault sharing)
├─ decrypt(encryptedRecoveryKey) → recoveryKey (BIP39 mnemonic)
└─ decrypt(encryptedVaultKey) → vaultKey
└─ decrypt(encryptedItemKey) → itemKey
└─ decrypt(encryptedBlob) → your plaintext secret

All keys live in memory only and are zeroed out when you lock your vault or close the tab. The one exception is opt-in Biometric unlock (per device): when enabled, the master key is stored on the device's IndexedDB wrapped by a per-credential WebAuthn PRF secret that only the platform authenticator (Touch ID, Face ID, Windows Hello, Android biometric) can release. See "What lives in your browser" below.

What the server actually stores

Stored on server (plaintext)Never stored on server
UsernameYour master password
Public key (for vault sharing)Any plaintext secret
KDF parameters (salt, Argon2id params)Vault keys or item keys
Encrypted key material (ciphertext only)Your private key

Vault sharing

To share a vault, the sender seals the vault key with the recipient's X25519 public key. The server looks up the recipient's public key by username. Only the intended recipient — holding the corresponding private key — can unseal and access the vault. The server cannot read the vault key at any point.

Recovery key

At registration, BlindPass generates a BIP39 mnemonic phrase (256-bit entropy) and encrypts it under your master key. If you forget your password, use this phrase to regain access. It is shown once at account creation — store it somewhere safe and offline.

Cryptographic primitives

All crypto uses libsodium, a battle-tested, audited library:

  • Key derivation: Argon2id
  • Symmetric encryption: XSalsa20-Poly1305
  • Asymmetric encryption (vault sharing): X25519 + XSalsa20-Poly1305
  • Recovery key: BIP39 mnemonic (256-bit entropy)

🛡 Security

Threat model

ThreatStatusNotes
Server compromise✅ ProtectedServer stores only ciphertext — no keys, no plaintext
Network interception✅ ProtectedAll data encrypted client-side before transmission
Brute-force via server✅ ProtectedNo server-side password verifier or password hash to attack
Weak master password⚠️ PartialArgon2id hardens derivation; a weak password is still a weak password
Forgotten password✅ MitigatedBIP39 recovery key generated at registration
Malware on your device❌ Out of scopeClient-side malware can read in-memory keys
Phishing / social engineering❌ Out of scopeNo technical control can prevent this
Browser extension surface❌ Not shippedNo autofill or capture extension by design — see ADR-0002
Local device theft + biometric bypass⚠️ PartialWhen Biometric unlock is enabled, an attacker with physical device access who can defeat the chosen passkey provider's biometric gate (Touch ID / Face ID / Windows Hello / Google Password Manager on Android) can decrypt the wrapped master key on disk. Disable biometric unlock in Settings to remove this surface.

Session security

PropertyValueWhy it matters
Cookie flagsHttpOnly; Secure; SameSite=StrictJavaScript cannot read the session token; CSRF is blocked at the browser level
Server storageSHA-256 hash of token onlyA database breach does not expose valid tokens
CSRF defenseSameSite=Strict + required x-bp-client header on mutationsTwo independent layers; a weakened SameSite assumption alone is not enough
Session TTL14 days absolute, 7 days idleBoth must hold; idle-only activity cannot extend indefinitely

What lives in your browser

BlindPass follows a strict client storage policy — key material is never written to disk unless Biometric unlock is explicitly enabled on this device:

StorageWhat is storedWhen wiped
Memory onlymasterKey, vaultKey, itemKey, privateKeyLock, logout, or tab close
IndexedDB (bp:vault-cache)Encrypted vault items (ciphertext only, no keys)Lock and logout
IndexedDB (bp:biometric-unlock)Wrapped master key + WebAuthn credential ID + PRF salt (only when Biometric unlock is enabled on this device)Logout, session expiry, or explicit disenrollment in Settings (survives idle lock)
localStorageUsername pre-fill, theme, density preferenceNever (non-sensitive)

An XSS attack that can run JavaScript in the page cannot read the session cookie, the master key, or any vault key. The worst it can do is read the IndexedDB cache — which is ciphertext.


🚀 Self-Hosting

Requirements

  • Docker and Docker Compose

Setup

  1. Download the compose file and configure:

    curl -o docker-compose.yml https://raw.githubusercontent.com/blindpass/blindpass/main/docker-compose.prod.yml
    curl -o .env.example https://raw.githubusercontent.com/blindpass/blindpass/main/.env.example
    cp .env.example .env

    Edit .env with your values (see the full variable reference below).

    Security: The example DATABASE_URL uses the password blindpass. Change it before deploying to any non-local environment.

    Generate a base64 32-byte authenticator-secret key for the server:

    TOTP_SECRET_ENCRYPTION_KEY=$(openssl rand -base64 32)
    
  2. Pull images and start all services:

    docker compose up -d

    No build step required — prebuilt images are pulled automatically from Docker Hub (allisson/blindpass-server, allisson/blindpass-webapp).

The web app is served on port 8000 (HTTP). Point a reverse proxy at that port to terminate TLS — see docs/deployment/reverse-proxy.md for Caddy and nginx setup guides.

Verify the stack is running:

curl http://localhost:8000/health
# {"status":"ok","db":"ok"}

First-time setup

Open the web app and register an account. The first registration always becomes the Admin User, regardless of whether the registration gate is open or closed.

As admin, visit /admin to:

  • Open or close the registration gate — controls whether new users can sign up
  • View and revoke user access
  • Adjust default quotas (defaults: 10 vaults per user, 1 000 items per vault)

Updating

docker compose pull
docker compose up -d

Environment Variables

VariableRequiredDefaultDescription
DATABASE_URLYesPostgreSQL connection URL
POSTGRES_PASSWORDYes (compose)PostgreSQL superuser password for the db container; must match DATABASE_URL
TOTP_SECRET_ENCRYPTION_KEYYesBase64-encoded 32-byte key for encrypting stored authenticator secrets
REDIS_URLYes (prod)Redis connection URL for server-side session storage
NODE_ENVYesMust be production in production
CORS_ORIGINYes (prod)http://localhost:5173Allowed origin(s), comma-separated
PORTNo3000HTTP server port
LOG_LEVELNoinfoPino log level (trace/debug/info/warn/error)
COOKIE_DOMAINNoCookie domain (set when web app and API share a domain)
COOKIE_NAMENobp_sessionSession cookie name
COOKIE_SECURENotrueMust remain true in production
SESSION_TTL_MSNo1209600000 (14 days)Absolute session expiry
SESSION_IDLE_TTL_MSNo604800000 (7 days)Idle session expiry; must be ≤ SESSION_TTL_MS
BODY_LIMIT_BYTESNo524288 (512 KB)Maximum request body size
DB_POOL_MAXNo10Maximum PostgreSQL connection pool size
PENDING_TOTP_TTL_MSNo900000 (15 min)How long a TOTP setup challenge remains valid
RECOVERY_TOKEN_TTL_MSNo900000 (15 min)How long a recovery token remains valid
UNVERIFIED_ACCOUNT_TTL_MSNo86400000 (24 h)How long an unverified account is retained before cleanup
EXPOSE_DOCSNofalseEnable Swagger UI at /docs

Self-Hosting on GCP Cloud Run (free tier)

Run BlindPass on GCP Cloud Run with external managed services — no VM to operate.

ComponentProviderFree tier
Web + APIGCP Cloud Run2M req/month, 360K vCPU-seconds/month
DatabaseSupabase free tier500 MB, paused after 1 week idle
RedisUpstash free tier10K commands/day
TLS + DNSCloud Run domain mappingFree managed certificate

See terraform/README.md for the full quickstart.


🛠 Local Development

Requirements

  • Node.js 24
  • pnpm 10
  • Docker (for PostgreSQL)

Setup

pnpm install
make dev

make dev starts PostgreSQL via Docker Compose and runs all apps in watch mode.

ServiceURL
Web apphttp://localhost:5173
API serverhttp://localhost:3000

Commands

make dev # start all services and apps
make test# run all tests
make test:crypto # packages/crypto only (≥95% coverage gate)
make test:server:unit # server unit tests
make test:server:integration # server integration tests (requires Docker)
make test:e2e # e2e tests (requires make dev running)
make lint # eslint via turbo
make format # prettier --write
make ci # lint + tsc + format check + test
make db:migrate # run pending migrations
make db:studio # open Drizzle Studio
make screenshots # capture UI screenshots to docs/screenshots/
make prod:build # build production Docker images (contributors only)
make prod:up # start production stack
make prod:down # stop production stack
make prod:logs # tail production logs

🏗 Architecture

BlindPass is a pnpm monorepo with separate web, server, and shared package boundaries. Its zero-knowledge cryptography architecture was inspired by Ente's published architecture.

┌──────────────────────────────────────────────┐
│ Browser │
│ ┌──────────────┐ │
│ │ apps/web │ │
│ └──────┬───────┘ │
│ ┌──────▼──────────┐ │
│ │ packages/vault │◄── packages/ │
│ │ (domain logic) │ crypto │
│ └──────┬──────────┘ │
└──────────────────┼───────────────────────────┘
│ HTTPS (encrypted blobs only)
┌──────▼──────┐
│ apps/server │ Fastify REST API
└──────┬──────┘
┌───────────┴───────────┐
┌────▼────┐ ┌──────▼──────┐
│ Redis │ sessions │ PostgreSQL │ ciphertext
└─────────┘ └─────────────┘

Packages

PackageRole
packages/cryptoPure libsodium primitives — Argon2id, XSalsa20-Poly1305, X25519, BIP39. No I/O, no state, no side effects.
packages/vaultDomain logic — encrypts/decrypts items, manages the keychain lifecycle (unlock/lock), handles vault sharing. Imports only from packages/crypto.
packages/api-schemaZod schemas for every API endpoint, shared between server and clients so validation stays in sync.

Tech Stack

LayerTechnology
RuntimeNode.js 24, pnpm 10
BackendFastify, Drizzle ORM, PostgreSQL 16
FrontendReact 19, TanStack Router, Vite, Tailwind, shadcn/ui
Cryptolibsodium-wrappers-sumo, @scure/bip39
TestingVitest (unit + integration), real PostgreSQL for integration tests
BuildTurborepo, Vite, esbuild

🛟 Troubleshooting

make dev fails immediately Docker must be running before starting. Start Docker Desktop (or your Docker daemon) and retry.

Port 5432 already in use A local PostgreSQL instance is occupying the port. Stop it (brew services stop postgresql on macOS) or change the mapped port in docker-compose.yml.

make test:server:integration fails Integration tests require Docker to be running (they spin up a real PostgreSQL container). Ensure Docker is available and no other process holds port 5432.

libsodium import errors in testspackages/crypto must never be mocked. Always run tests against the real libsodium binding. If you see import errors, verify libsodium-wrappers-sumo is installed (pnpm install).


🤝 Contributing

  1. Fork the repo and create a branch from main
  2. Run make ci before opening a PR (lint + type check + tests must pass)
  3. All new code requires tests written alongside or before implementation (TDD)
  4. packages/crypto enforces ≥95% coverage — do not drop below it
  5. Never mock packages/crypto in tests — always test against real libsodium

📄 License

MIT — see LICENSE.

About

Self-hostable, end-to-end encrypted password manager, your secrets never leave your device unencrypted.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages