Sign inSign up

vineethnkrishnan/vaultctl

By vineethnkrishnan

•Updated 20 days ago

Zero-knowledge password vault. Self-hosted, E2E encrypted, open source.

Image
0

5.2K

vineethnkrishnan/vaultctl repository overview

⁠vaultctl

Self-hosted, zero-knowledge password vault. Single Go binary serves the API and the embedded React SPA. Browser extension and CLI talk to the same server. AGPL-3.0.

  • Zero-knowledge by construction. Argon2id key derivation and AES-256-GCM happen in the browser worker, the extension, or the CLI. The server has no code path to decrypt.
  • One binary, one image. The web bundle is embedded into the Go binary. Final container image is ~45MB on gcr.io/distroless/static-debian12:nonroot.
  • Multi-user from day 1. Organizations, invites, role-based vault sharing, RSA-OAEP wrap with Ed25519 signature pinning, member-removal triggers a vault rekey.
  • Supply-chain hardened. goreleaser builds for linux/darwin/windows × amd64/arm64; cosign keyless signing on every artifact; CycloneDX SBOM per archive; SLSA-L3 provenance attestation on public releases.

Docs: vaultctl.vinelab.in⁠

⁠Features

  • Zero-knowledge. Argon2id + AES-256-GCM run client-side; the server only ever stores ciphertext.
  • Multi-vault + sharing. Personal and shared vaults, role-based access, RSA-OAEP recipient key-wrap with Ed25519 signature pinning; removing a member rekeys the vault.
  • Browser extension (MV3). Autofill and save for logins, plus capture and fill of credit cards and identities; multi-vault switcher and cross-vault autofill.
  • TOTP codes. Store 2FA secrets and read live one-time codes in the app.
  • Password health. Weak/reused/old detection, with an optional Have I Been Pwned breach check (k-anonymity, client-side, off by default).
  • Import / export. Including CSV, alongside the encrypted JSON export.
  • Encrypted cloud backups. Per-user scheduled, double-sealed backups to local, S3, WebDAV, Google Drive, Dropbox, or OneDrive.
  • Email. Optional SMTP for signup verification, new-device login alerts, and activity digests.
  • Activity log. Self-audit trail of security-relevant events.
  • Admin / orgs. Organizations, invites, member roles.
  • i18n. English and German across the web app and extension.
  • 2FA. TOTP for every user, with optional server-wide enforcement.

⁠Configuration

vaultctl is configured entirely through VAULTCTL_ environment variables (see .env.example⁠). Full references:

⁠Quick start (self-host)

git clone https://github.com/vineethkrishnan/vaultctl.git
cd vaultctl
cp .env.example .env
# fill in every secret - server fail-closes if any prod secret is empty.
# generate values with: openssl rand -base64 32   (or 64 for JWT secrets)

docker compose up -d   # starts caddy + vaultctl + postgres; migrations run automatically on startup

Open https://${VAULTCTL_BASE_URL} and register the first user. Without a TLS-terminating proxy, use docker-compose.simple.yml and front it with your own reverse proxy on 127.0.0.1:8080.

Step-by-step screenshots of the registration -> recovery-kit -> first-item flow: docs/setup/walkthrough.md⁠.

The bundled compose sets VAULTCTL_DB_SSL_INSECURE_OK=true because Postgres lives on a private bridge network. For any deploy where the DB is reachable across hosts, leave this unset and configure VAULTCTL_DB_SSL_MODE=verify-full.

⁠CLI

# grab a signed `vaultctl` binary from the latest release, or build it:
git clone https://github.com/vineethkrishnan/vaultctl.git && cd vaultctl
make build && cp bin/vaultctl ~/go/bin/      # `go install .../cmd/server` would name the binary `server`

vaultctl login --server https://vault.example.com   # server is remembered in the config file
vaultctl unlock                 # keeps the vault open in a per-user agent (macOS/Linux)
vaultctl list
vaultctl get GitHub --field password
vaultctl totp GitHub
vaultctl create --name Reddit --username me --password ... --uri https://reddit.com
vaultctl backup --output /var/backups/vaultctl

The same binary runs the server (vaultctl server), applies migrations (vaultctl migrate up|down), and runs the client commands. --json is honored on every read command.

⁠Filling terminal logins

vaultctl run -- <command> runs an interactive program in a pseudo-terminal and answers its username, password and one-time-code prompts from the login item whose URI matches the host on the command line, the way the extension fills a web form. It does not need to know the program: a password prompt is recognised by the program turning terminal echo off, and a value you already passed on the command line is never filled because the program does not ask for it.

vaultctl run -- tsh login --proxy=teleport.example.com   # password + OTP from the item for that host
vaultctl run -- mysql -h db.example.com -p               # -u from the item, password at the prompt
vaultctl run -- ssh [email protected]
eval "$(vaultctl shell-init zsh)"                         # aliases tsh/mysql/ssh/psql through vaultctl run
git config --global credential.helper '!vaultctl git-credential'

With no matching item the command runs as usual and, on success, offers to save what you typed. Details, the threat model and what is remembered where: docs-site/pages/user-manual/cli.mdx⁠.

⁠Browser extension (MV3)

cd extension
npm ci
npm run build           # outputs .output/chrome-mv3

Load extension/.output/chrome-mv3 via chrome://extensions -> Developer mode -> Load unpacked. Firefox: about:debugging.

⁠Development

Toolchain: Go 1.22+, Node 22+, Docker (with docker compose), and GNU make. The make targets below are thin wrappers around go build / npm / golangci-lint / gosec / govulncheck - install any tool the target needs that you don't already have.

make web-build          # build the SPA (embedded into the Go binary)
make build              # build the vaultctl binary
make run                # run the server locally on :8080
make test               # go test ./... -race -count=1 -coverprofile=coverage.out
make lint               # golangci-lint run ./...
make sec                # gosec + govulncheck

Web dev server (proxies /api to http://localhost:8080):

cd web
npm ci
npm run dev             # http://localhost:5173
npm run typecheck       # tsc --noEmit
npm run test            # vitest
npm run test:e2e        # playwright (35 e2e tests)

⁠Verifying releases

vaultctl publishes four layers of supply-chain evidence. For credential-vault use you should verify at least the layer matching how you install. Step-by-step verification commands: docs/security/verifying-releases.md⁠.

LayerWhat it proves
cosign verify-blob against checksums.txtthe binary tarball matches what the release workflow built
cosign verify against the container manifestthe image digest you pulled was produced by the release workflow
SLSA build provenance attestationa named GitHub workflow at a specific tag produced the artifact
CycloneDX SBOM (*.sbom.cdx.json)complete dependency inventory for review and vuln scanning

⁠Project layout

cmd/server/                   # binary entry: server, migrate, backup, admin, client cmds
internal/domain/              # core types and invariants - no I/O
internal/application/         # use cases composed from ports
internal/infrastructure/      # postgres, JWT, crypto adapters
internal/presenters/api/      # chi router, handlers, middleware
internal/presenters/cli/      # cobra command tree
migrations/                   # *.sql, embedded into the binary
web/                          # React + Vite SPA, embedded into the binary
extension/                    # WXT + MV3 browser extension
deploy/caddy/                 # Caddyfile for the bundled stack
docs/security/                # release verification guide
docs-site/                    # public docs site (deployed to Cloudflare Pages)

⁠License

AGPL-3.0. See LICENSE⁠.

Tag summary

Content type

Image

Digest

sha256:201134735…

Size

13.3 MB

Last updated

20 days ago

docker pull vineethnkrishnan/vaultctl