A self-hosted, multi-user password and backup-code manager. Seald keeps each user's site credentials and 2FA recovery codes in one place, encrypted at rest, on infrastructure you control. Built with a FastAPI backend, a React single-page app based on the Material UI design system, and Supabase (PostgreSQL + Auth) β wired together with Docker Compose so it runs anywhere with two commands.
π Live demo: https://seald.vajapravin.me/
Seald separates concerns across clear layers, so each can evolve independently:
- React SPA (frontend) β a two-panel interface served by nginx: brand and vault health on the left, your sites on the right. Authentication is handled by the Supabase JS client; every API call carries the user's JWT.
- FastAPI backend β a versioned REST API (
/api/v1). It verifies the Supabase-issued JWT on every request, extracts the user, and scopes all data access to that user. Routes handle HTTP and encryption; storage sits behind a repository interface. - Authentication (Supabase Auth) β email/password with mandatory email confirmation, plus Google and GitHub social login. Sessions and token refresh are managed client-side; the backend verifies token signatures against the project's public keys (JWKS).
- Encryption layer β every password and backup code is encrypted with Fernet (authenticated encryption) before it leaves the backend. The database only ever stores ciphertext. Keys support rotation.
- Supabase (PostgreSQL) β per-user persistence. Every vault entry is linked
to its owner, protected by Row Level Security as a database-level
backstop in addition to backend filtering. Schema lives as SQL migrations in
supabase/migrations/.
- Multi-user accounts β register with email and password (email confirmation required), or sign in with Google or GitHub. Each user has a private vault.
- Per-user vault isolation β enforced both in the backend (every query scoped to the authenticated user) and in the database (Row Level Security).
- Vault dashboard β a searchable, sortable table of all saved sites with masked passwords, show/hide, and one-click copy for username and password.
- Vault health summary β at-a-glance score plus counts of weak passwords, reused passwords, and sites missing backup codes.
- Add / edit / remove sites β site, username, password, notes, and 2FA backup codes per entry; deletion asks for confirmation.
- Secure password generator β built on Python's
secretsCSPRNG with adjustable length, a symbols toggle, and a live strength meter (zxcvbn) showing estimated crack time as you type. - Backup-code generator β creates 2FA-style recovery codes in one click.
- Encryption at rest β Fernet authenticated encryption for every secret.
- Light & dark mode β full theme toggle.
- Self-contained deployment β one
docker compose upbrings up the stack.
- Authentication on every request. The backend rejects any request without a valid, signature-verified Supabase JWT. Nothing in the vault is reachable unauthenticated.
- Per-user isolation, defense in depth. The backend filters every query by the authenticated user, and Postgres Row Level Security independently prevents one user's rows from being returned to another β even a backend bug can't leak across users.
- Encrypted at rest. Secrets are encrypted in the backend before storage. A leaked database dump, a compromised dashboard, or a stolen backup yields only ciphertext.
- Authenticated encryption. Fernet includes an HMAC β tampered ciphertext is detected and rejected, never silently decrypted.
- Key custody stays with you. The encryption key lives in your backend
.envon your server, never in the database and never in the repository. - Least-privilege configuration. Only the backend holds database and signing credentials; the frontend holds only the public Supabase anon key.
- No telemetry, no third parties. Seald makes no outbound calls except to your own Supabase instance.
Commercial managers (1Password, LastPass, Dashlane, β¦) are excellent products β but self-hosting removes trade-offs they can't.
Pros of Seald
- Your data never leaves your infrastructure. Third-party managers aggregate millions of vaults, which makes them high-value targets β LastPass's 2022 breach exposed customer vault backups. Seald's blast radius is your own users.
- Zero cost. No subscription, no seat licenses, no feature paywalls. Supabase's free tier comfortably covers personal and small-team use.
- Full auditability. Every line is in this repository. You don't have to
trust claims about the encryption β you can read
backend/app/core/crypto.py. - No vendor lock-in. Your data sits in a PostgreSQL table you own.
- Backup codes as a first-class feature. Most managers treat 2FA recovery codes as an afterthought; Seald stores them alongside each site.
Cons (equally honest)
- You are the security team. Updates, server hardening, key backups, and database backups are your responsibility. A lost encryption key means permanently lost data β by design.
- No browser extension or autofill. Copy/paste workflow only, for now.
- No native mobile or sync clients β it's a web app.
- Community of one. No support hotline, no SOC 2 report, no bug bounty.
If you want zero-maintenance convenience, buy a commercial manager. If you want ownership, transparency, and zero cost β and you're comfortable running Docker β Seald is for you.
Prerequisites: Docker with Compose, a free Supabase project, and Python 3.12+ (only to generate the encryption key).
git clone https://github.com/vajapravin/seald.git
cd sealdIn your Supabase project's SQL Editor, run the migrations in
supabase/migrations/ in order. This creates the sites table, links it to
users, and enables Row Level Security.
- Authentication β Providers β Email: enable it and turn Confirm email ON.
- Authentication β URL Configuration: add
http://localhost:3002to the Site URL and the Redirect URLs allowlist (includehttp://localhost:3002/auth/callback). - Social login (optional): enable Google and/or GitHub under
Authentication β Providers, using OAuth apps whose callback URL is your
project's
https://<ref>.supabase.co/auth/v1/callback.
cp backend/.env.example backend/.envFill in backend/.env:
SUPABASE_URL=https://<your-project-ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<service role key> # Settings -> API
STORAGE_BACKEND=supabase
# Generate with:
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
ENCRYPTION_KEYS=<generated key>
# Legacy - only used if your project signs JWTs with the older HS256 secret.
# Projects using asymmetric (ES256) keys verify via JWKS and don't need this.
SUPABASE_JWT_SECRET=
cp frontend/.env.example frontend/.envFill in frontend/.env (both values are public-safe):
VITE_SUPABASE_URL=https://<your-project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<anon key> # Settings -> API
docker compose up --build -dThen open:
| URL | What |
|---|---|
| http://localhost:3002 | The Seald web app (register or sign in to start) |
| http://localhost:8002/docs | Interactive API documentation |
| http://localhost:8002/health | Backend health check |
π Back up your
ENCRYPTION_KEYSvalue somewhere safe and separate from the database. Losing it means losing every stored secret, permanently. That is the point of encryption β there is no recovery path.
# Backend tooling (lint, types, tests)
pip install -r backend/requirements-dev.txt
ruff check backend/ && ruff format --check backend/
cd backend && pytest
# Frontend with hot reload (backend must be running on :8000)
cd frontend && npm install && npm run devEvery push and pull request runs the full gate β ruff, mypy, pytest, and the
frontend build β in GitHub Actions. main is protected: changes land only
through pull requests with green checks.
- Account-level 2FA (TOTP) for the Seald login itself, toggleable in settings
- Dashboard filtering and column improvements
- Encrypted vault export & import
- Breach checking (HaveIBeenPwned, k-anonymity)
- End-to-end encryption (client-side, zero-knowledge)
MIT β see LICENSE. Use it, fork it, self-host it.