A web-based password manager with zero-knowledge architecture. All cryptography happens client-side in the browser using OpenPGP.js. The server stores only encrypted blobs β it never sees plaintext passwords or private keys.
- Client-side encryption β PGP encryption/decryption in the browser with OpenPGP.js
- Zero-knowledge architecture β Server never sees plaintext passwords or private keys
- Multi-user support β Each user identified by their PGP key fingerprint
- Two-factor authentication β Optional TOTP (2FA) for server access
- Import/Export β Compatible with standard
.password-storedirectory format (pass CLI compatible) - Password generator β Configurable random password generation
- Session management β 5-minute JWT sessions with automatic expiry
- Git sync β Backup and sync encrypted entries to any Git repository (GitHub, GitLab, Gitea)
- TOTP codes β Store and generate TOTP codes using
otpauth://URI format (pass-otp compatible) - Theme toggle β Auto-switching light/dark themes based on time of day (8AM-10PM)
- Account management β Clear local data or permanently delete account with passphrase confirmation
- Auto-hide sensitive content β Password and notes auto-hide after 15 seconds with countdown timer
- PGP key auto-lock β Decrypted PGP key auto-locks after 30s of inactivity
- Rate limiting β Sliding window rate limiting on authentication endpoints (5 attempts / 15 min default)
- Registration protection β TOTP-based registration codes to prevent unauthorized account creation
βββββββββββββββββββββββββββββββββββββββ
β Browser (SPA) β
β + OpenPGP.js + IndexedDB β
β + PGP encrypt/decrypt β
β + TOTP code generation β
ββββββββββββββββββββββββββββββββββββββ
β HTTPS + CORS + JWT
ββββββββββββββββΌβββββββββββββββββββββββ
β Go API Server (SQLite backend) β
β + JWT Auth + bcrypt + TOTP β
β + Rate limiting β
β + Git sync (go-git) β
βββββββββββββββββββββββββββββββββββββββ
β HTTPS (PAT) / SSH (key)
ββββββββββββββββΌβββββββββββββββββββββββ
β Remote Git Repo (optional) β
β βββ *.gpg (encrypted blobs) β
βββββββββββββββββββββββββββββββββββββββ
| Layer | Technology |
|---|---|
| Frontend | TypeScript + Preact + Vite |
| Crypto | OpenPGP.js + Web Crypto API (PBKDF2) |
| Backend | Go 1.26 + SQLite (pure-Go, no CGO) |
| Git | go-git (pure Go, no Git CLI needed) |
| Auth | bcrypt + JWT (5-min) + TOTP (2FA) |
| Testing | Playwright (81 E2E tests) + Vitest |
| Deploy | Docker (single container) |
- Go 1.26+
- Node.js 24+
- npm
- Docker (for container deployment)
# Build backend
go build -o webpass-server ./cmd/srv
# Build frontend (optional, for static serving)
cd frontend && npm run build# Set environment variables
export JWT_SECRET=$(openssl rand -hex 32)
export DB_PATH=./db.sqlite3
# Run server (development)
go run ./cmd/srv
# Or run the compiled binary
./webpass-serverServer listens on :8080 by default.
# Build image
docker build -t webpass:latest .
# Run container
docker run -d \
--name webpass \
-p 8080:8080 \
-v webpass-data:/data \
-e JWT_SECRET="$(openssl rand -hex 32)" \
--read-only \
--security-opt no-new-privileges:true \
webpass:latest# Configure environment
cp .env.example .env
# Edit .env and set JWT_SECRET
# Start
docker compose up -dMigrations are applied automatically on startup via embedded SQL files in db/migrations/.
They are numbered (NNN-name.sql) and tracked in a migrations table β each runs exactly once.
Forward upgrade β
β Fully supported. All migrations only add columns/tables,
never remove or rename. Upgrading from older versions (e.g. v0.4.6) to latest is safe.
Rollback β β Not supported. Because sqlc generates SELECT * queries, the old
binary will fail to scan rows if new columns were added. To downgrade, restore both
the binary and the database from backup.
The container runs with:
- Non-root user (UID/GID 8080)
- Read-only filesystem (
--read-only) - No privilege escalation (
--security-opt no-new-privileges:true) - Writable path only
/data(SQLite + git repos)
# Backend tests
go test ./...
# Frontend unit tests
cd frontend && npm test81 browser-based integration tests across 5 phases:
# Run comprehensive test suite
./frontend/playwright-e2e-test.sh
# Or use npx directly
cd frontend && npx playwright test
# Run with interactive UI
npx playwright test --ui
# Run with visible browser
npx playwright test --headed
# View HTML report
npx playwright show-reportTest Coverage:
- Phase 1: Rate limit tests (3 tests)
- Phase 2: All tests in Protected mode (62 tests)
- Phase 3: Registration tests in Open mode (6 tests)
- Phase 4: Registration tests in Protected mode (8 tests)
- Phase 5: Registration tests in Disabled mode (2 tests)
Git sync E2E tests require a real Git repository. Set credentials via:
# Option 1: .env file (gitignored)
WEBPASS_REPO_URL="https://gitea.example.com/user/password-store.git"
WEBPASS_REPO_PAT="your-personal-access-token"
# Option 2: Environment variables
export WEBPASS_REPO_URL="..."
export WEBPASS_REPO_PAT="..."
# Option 3: GitHub Secrets (CI/CD)
# Add WEBPASS_REPO_URL and WEBPASS_REPO_PAT to repository secretsAll endpoints require JWT authentication except where noted.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api |
No | Create user (first-time setup) |
| GET | /api/{fingerprint} |
JWT | Get user info |
| POST | /api/{fingerprint}/login |
No | Login β returns JWT or 2FA challenge |
| POST | /api/{fingerprint}/login/2fa |
No | Complete 2FA login |
| DELETE | /api/{fingerprint}/account |
JWT | Delete account permanently |
| POST | /api/{fingerprint}/password |
JWT | Change password |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/registration/mode |
No | Get registration mode (open/protected/disabled) |
| POST | /api/registration/validate |
No | Validate registration TOTP code |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/{fingerprint}/totp/setup |
JWT | Begin TOTP 2FA setup |
| POST | /api/{fingerprint}/totp/confirm |
JWT | Confirm TOTP 2FA setup |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/{fingerprint}/entries |
JWT | List all entry paths |
| GET | /api/{fingerprint}/entries/* |
JWT | Download encrypted blob |
| PUT | /api/{fingerprint}/entries/* |
JWT | Upload encrypted blob |
| DELETE | /api/{fingerprint}/entries/* |
JWT | Delete entry |
| POST | /api/{fingerprint}/entries/move |
JWT | Rename/move entry |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/{fingerprint}/export |
JWT | Export all entries as .tar.gz |
| POST | /api/{fingerprint}/import |
JWT | Import password store (JSON or .tar.gz) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/{fingerprint}/git/status |
JWT | Get git sync status |
| GET | /api/{fingerprint}/git/config |
JWT | Get git configuration |
| POST | /api/{fingerprint}/git/config |
JWT | Configure git sync |
| POST | /api/{fingerprint}/git/session |
JWT | Set git token for current session |
| POST | /api/{fingerprint}/git/push |
JWT | Manual push to remote (force overwrite) |
| POST | /api/{fingerprint}/git/pull |
JWT | Manual pull from remote (fresh clone) |
| POST | /api/{fingerprint}/git/toggle-sync |
JWT | Enable/disable git sync |
| GET | /api/{fingerprint}/git/log |
JWT | Get sync operation history (last 50) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/health |
No | Health check |
| GET | /api/version |
No | Get server version |
- Private keys never leave the browser β Stored AES-wrapped in IndexedDB
- Server stores only PGP-encrypted blobs β Database leak reveals nothing
- Password validates locally first β Wrong password fails before network call
- 5-minute sessions β JWT expiry enforced server-side
- PGP key auto-lock β Private key automatically locked after 30s of inactivity (hardcoded in
frontend/src/lib/session.ts) - CORS locked to specific origins via
CORS_ORIGINSenv var - All traffic over HTTPS required in production
- Rate limiting on authentication endpoints (sliding window, 5 attempts / 15 min default)
- Registration protection with TOTP-based codes (optional, configurable)
Please do not open public issues for security vulnerabilities.
- Email: Send details to the maintainers privately
- GitHub: Use private vulnerability reporting
Response Timeline:
- Acknowledgment: Within 48 hours
- Initial Assessment: Within 1 week
- Fix Timeline: Depends on severity (Critical: 24-72h, High: 1-2 weeks, Medium: 2-4 weeks)
See .env.example for all available options with detailed comments.
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
Yes | 32-byte hex string for JWT signing |
DB_PATH |
No | Path to SQLite database (default: /data/db/db.sqlite3) |
STATIC_DIR |
No | Path to frontend dist/ directory |
CORS_ORIGINS |
No | Comma-separated allowed origins |
PORT |
No | HTTP listen port (default: 8080) |
GIT_REPO_ROOT |
No | Git repos directory (default: /data/git-repos) |
SESSION_HARDLIMIT_MINUTES |
No | JWT hard limit (max session time) in minutes (default: 30, range: 5-480) |
SESSION_SOFTLIMIT_MINUTES |
No | JWT soft limit (browser close detection) in minutes (default: 5, range: 1-60) |
DISABLE_FRONTEND |
No | Disable frontend (1 or true) |
BCRYPT_COST |
No | Password hashing cost factor (default: 12, range: 10-15) |
RATE_LIMIT_ATTEMPTS |
No | Max requests per window (default: 5) |
RATE_LIMIT_WINDOW_MINUTES |
No | Time window in minutes (default: 15) |
| Variable | Default | Description |
|---|---|---|
REGISTRATION_ENABLED |
false |
Enable registration (1 or true) |
REGISTRATION_TOTP_SECRET |
(empty) | Base32 TOTP secret (required for protected mode) |
REGISTRATION_TOTP_PERIOD |
3600 |
Code validity period in seconds (15-86400) |
REGISTRATION_TOTP_ALGO |
SHA1 |
Hash algorithm (SHA1/SHA256/SHA512) |
REGISTRATION_CODE_FILE |
/data/registration_code.txt |
Path to write current code |
Registration Modes:
REGISTRATION_ENABLED |
REGISTRATION_TOTP_SECRET |
Mode |
|---|---|---|
false |
(any) | Disabled β No registration allowed |
true |
(not set) | Open β No code required |
true |
(set) | Protected β 6-digit TOTP code required |
One-way overwrite sync with fresh clone/export:
- Pull: Remote β Local (clone remote, replace local DB)
- Push: Local β Remote (export local DB, force-push to remote)
- No merge conflicts β Last write wins
- Fresh operations β Local git repo is temporary, cleaned before/after each operation
- Per-user configuration β Each user has their own repo URL
- Two authentication methods:
- HTTPS β PGP-encrypted Personal Access Token
- SSH β PGP-encrypted SSH private key with TOFU host key verification
repo-root/
βββ .git/
βββ email.gpg
βββ work/
β βββ database.gpg
βββ social/
βββ twitter.gpg
- All files are
.gpgencrypted blobs (client-side PGP encryption) - Files at repo root (no
.password-store/subdirectory) - Compatible with standard
passCLI after cloning
Default branch auto-detection (branch = "HEAD"):
- Read remote HEAD symbolic reference
- If HEAD not found β try
main - If
mainnot found β trymaster
Auto-switching theme system with manual override:
| Theme | Description |
|---|---|
| Ocean | Dark blue professional theme (default for night) |
| Daylight | Clean white/blue light theme (default for day) |
Auto mode (default):
- 8:00 AM - 10:00 PM β Daylight theme
- 10:00 PM - 8:00 AM β Ocean theme
Manual override: Click theme toggle button to cycle: π Auto β π Ocean β βοΈ Daylight β Auto
Preference saved to localStorage and persists across sessions.
.
βββ .github/workflows/ # CI/CD pipelines
βββ cmd/srv/ # Main binary entrypoint
ββ srv/ # HTTP server + handlers
βββ db/ # Database migrations + sqlc queries
βββ frontend/ # Preact + TypeScript SPA
β βββ src/
β βββ index.html
β βββ package.json
ββ Dockerfile
βββ docker-compose.yml
βββ .github/ # GitHub Actions workflows
Contributions are welcome! Please read CONTRIBUTING.md for guidelines and AGENTS.md for the development guide.
# Clone repository
git clone https://github.com/johnwmail/webpass.git
cd webpass
# Backend
go mod download
go test ./...
# Frontend
cd frontend
npm install
npm run dev- Backend:
go test ./...must pass - Frontend unit:
npm testmust pass - E2E tests:
./frontend/playwright-e2e-test.shmust pass (81 tests) - Type check:
npm run typecheckmust pass
Go:
- Follow Effective Go
- Use
gofmtorgoimports - Add tests for new packages
TypeScript:
- Use TypeScript for all new code
- Follow existing code style
- Add types for function signatures
General:
- Write self-documenting code
- Keep PRs under 400 lines when possible
- Update tests with code changes
Never commit:
- Passwords or secrets
- API keys or tokens
- Private encryption keys
- Production database files
MIT License β see LICENSE for details.
- Inspired by pass
- Built with OpenPGP.js
- Backend powered by Go and SQLite
- Git operations via go-git