kvlt

command module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2026 License: MIT Imports: 1 Imported by: 0

README ΒΆ

release go report card license build release powered by just conventional commits go reference github commit activity hovnokod

β–ˆβ–„β–€ β–ˆβ–‘β–ˆ β–ˆβ–‘β–‘ β–€β–ˆβ–€
β–ˆβ–‘β–ˆ β–€β–„β–€ β–ˆβ–„β–„ β–‘β–ˆβ–‘

kvlt /kʌlt/ noun β€” pronounced β€œcult”; the v stands in for u, the old-Norse-runic spelling popularized by black-metal aesthetics (cf. trve kvlt).

πŸ” Pluggable secrets vault. Local-first. No daemon.

A single-binary secrets vault for projects that don't have HashiCorp Vault and don't want one. Encrypts with age using your existing SSH keys; named vaults give you a stable call site (kvlt get prod API_KEY) regardless of whether the backend is local age files today, AWS Secrets Manager tomorrow.

✨ Features

  • πŸ” age + SSH keys β€” encrypts with ~/.ssh/id_ed25519.pub, decrypts with the matching private key. Borrows your existing protection chain (passphrase + ssh-agent + Touch ID via secretive); kvlt doesn't reinvent the lock.
  • πŸͺͺ Named vaults β€” kvlt get prod API_KEY, never kvlt get_aws(…); the backend is an implementation detail. Switching from local age files to AWS Secrets Manager later doesn't touch a single call site.
  • πŸ”Œ Pluggable backends β€” Provider interface + factory registry. AWS Secrets Manager (planned) sits behind a //go:build aws guard so the base binary stays dependency-light.
  • πŸ‘₯ Multi-recipient β€” encrypt to N SSH public keys, any one of those private keys can decrypt. The team-sharing escape hatch.
  • 🀫 Stdin / TTY input modes β€” echo $VAL | kvlt put keeps secrets out of shell history; bare kvlt put prompts with echo off.
  • 🐚 Shell-friendly β€” kvlt env vault for eval "$(…)" direnv integration; kvlt run vault -- cmd for scoped env injection like aws-vault exec / op run.
  • 🚫 No service, no daemon β€” pure CLI; nothing listening, nothing persistent.
  • πŸ“¦ Single static binary β€” Go, CGO off, darwin / linux / windows Γ— amd64 / arm64.

πŸ“¦ Install

curl -fsSL https://github.com/retr0h/kvlt/raw/main/install.sh | sh

Installs to ~/.local/bin (or /usr/local/bin as root) β€” SHA256 checksums verified. Override with KVLT_INSTALL_DIR=/some/path or pin a version with KVLT_VERSION=1.1.1.

πŸ”¨ Build from source

git clone https://github.com/retr0h/kvlt.git
cd kvlt
go build -o kvlt .
install -m 755 kvlt ~/.local/bin/kvlt

Cloud backends opt in via build tags: go build -tags aws -o kvlt .

πŸš€ Quick start

kvlt vault create --name dev                                    # bootstrap a vault (encrypts to ~/.ssh/id_ed25519.pub)
kvlt vault create --name prod -p ~/.ssh/team.pub                # encrypt to a non-default public key
kvlt secret put --vault dev --key API_KEY --value sk-1234       # store a secret (lands in shell history)
echo "$DB_PASS" | kvlt secret put --vault dev --key DB_PASS     # stdin β†’ no shell history
kvlt secret put --vault dev --key TOKEN                         # interactive, echo off
kvlt secret import --vault dev --env ~/.env                     # bulk-import a dotenv file
kvlt secret import --vault dev --file ~/kc.yaml --key KC        # one whole file stored as one secret
kvlt secret get --vault dev --key API_KEY                       # decrypt β€” prompts for SSH passphrase if not in agent
kvlt secret get --vault dev --key API_KEY -i ~/work/id_ed25519  # use a specific private key
kvlt secret list --vault dev                                    # names only, never values
kvlt secret delete --vault dev --key OLD_KEY                    # remove a secret (prompts unless --force)
kvlt env --vault dev                                            # all secrets as `export KEY=VALUE` for `eval`
kvlt run --vault dev -- npm start                               # exec child with vault secrets in env
kvlt vault delete --name dev                                    # delete the vault + every secret in it (prompts)

Override the default decrypt key globally with KVLT_PRIVATE_KEY=/path/to/key.

Full recipe collection in docs/recipes.md.

πŸ›‘οΈ Why SSH keys (and why this is meaningfully more secure)

Most secret stuff on a dev laptop is a plaintext file. ~/.aws/credentials, ~/.config/gh/hosts.yml (your GitHub PAT), .env files, npm tokens in ~/.npmrc, GitLab tokens in ~/.config/glab-cli/, every .kube/config β€” all sitting there in plaintext, readable by any process running as you. Once malware has user-level execution on your machine, every one of those is immediate, no friction.

The clever bit isn't kvlt β€” it's delegating the lock to the SSH protection chain, one of the few credential systems on a dev machine that actually has a human-in-the-loop step:

What's on disk Attacker w/ user code-exec does Result
~/.aws/credentials plaintext cat full AWS access, instantly
.env with STRIPE_KEY=… cat Stripe access, instantly
gh / glab / npm tokens cat git host access, instantly
Key-file vault (key.txt next to blobs) cat key.txt && cat blob decrypt, instantly β€” key file is the secret
kvlt + passphrase-locked SSH key reads .age + encrypted key file needs the passphrase
kvlt + ssh-agent (timed unlock) tries to decrypt needs the passphrase to (re-)unlock the agent
kvlt + Secretive on macOS tries to decrypt needs your fingerprint (Touch ID)

Every kvlt decrypt requires something that isn't on disk: your typed passphrase, ssh-agent's in-memory unlock state, or a Touch ID prompt routed through the Secure Enclave. Reading every file under $HOME gets the attacker .age blobs β€” useless without the key β€” and an encrypted private key file, useless without the passphrase. The credentials never exist as plaintext at rest.

This is why a .env -> kvlt swap is a real upgrade, not just a re-shuffle. Vault designs that store the encryption key as a sibling text file in the repo don't help either β€” an attacker grabbing the vault grabs the key. kvlt moves the key out of the filesystem entirely; what's left on disk is useless without something off-disk (your passphrase, the agent's unlocked state, your fingerprint).

Honest about the limits:

  • Cached ssh-agent unlock β€” once the agent is unlocked, anything running as you can sign with it. Mitigate with ssh-add -t 1h for time-limited caching, or skip the agent entirely on macOS by using Secretive (key lives in the Secure Enclave, every signature requires Touch ID).
  • Keylogger on the box captures the passphrase the next time you type it. Beyond software's job.
  • .age blobs are still copyable β€” an attacker with the blobs can sit on them waiting for a future key compromise. Rotate keys and the underlying secrets when threat-modeling demands it.

In short: kvlt is exactly as protective as your SSH private key is, which is far better than "as protective as a text file in $HOME."

Sharing a vault with teammates uses age's multi-recipient model β€” no shared keys, each person decrypts with their own SSH private key. Walkthrough in docs/recipes.md.

βš™οΈ How It Works

kvlt is a CLI; nothing runs between invocations. Each command opens the vault config, talks to the backend, and exits.

  1. πŸͺͺ Pick a vault by name β€” every verb takes a name (dev, prod, …); the name resolves to a backend through .kvlt/vaults/<type>/<id>.yaml
  2. πŸ” Default backend is local (age + SSH keys) β€” kvlt put encrypts to one or more SSH public-key recipients via age; blobs land at .kvlt/secrets/local_encryption/<vault>/<key>.age. Decrypt requires the matching SSH private key β€” passphrase prompt fires on /dev/tty if your key isn't in ssh-agent already.
  3. πŸ”Œ Backends are pluggable β€” Provider interface + factory registry. Adding AWS Secrets Manager is one new file behind a //go:build aws guard; the base binary stays dependency-light.
  4. πŸ” migrate is copy-then-swap β€” list keys, copy each value to the new backend, write the new config, delete the old one. Source stays functional until the very last step. (Planned; the backend abstraction supports it cleanly.)

The contract every backend implements is four methods (Get / Put / List / Name) β€” small on purpose. Anything fancier is layered on top by callers, not pushed into the backend.

πŸ’‘ Inspiration

  • age β€” pure-Go, audited, SSH-key-friendly encryption. kvlt is a vault wrapper around it; the crypto is age's.

πŸ”€ Alternatives

Tool Description
HashiCorp Vault Full-featured secret-management platform
OpenBao Open-source fork of Vault
1Password CLI If you already live in 1Password
pass GPG-encrypted files, the Unix way

kvlt is meant for the gap below "I need a Vault cluster" and above "I have a .env file."

πŸ—ΊοΈ Roadmap

Shipped:

  • πŸͺͺ Project scaffold + CLI tree
  • πŸ” local backend (age + SSH-key recipients)
  • πŸšͺ vault create / secret put / secret get / secret list
  • 🐚 kvlt env / kvlt run for shell + child-process integration
  • πŸ”Œ Pluggable backend registry (factory pattern)

Up next β€” only what earns its keep:

  • 🀝 ssh-agent integration β€” friction-free decrypt; Touch ID via Secretive on macOS without re-prompting per read.
  • πŸ” vault migrate β€” copy-then-swap, named-vault payoff: change backend type without touching call sites.
  • πŸ”Œ AWS Secrets Manager backend (-tags aws) β€” only if a real "dev local β†’ prod cloud" use case shows up. Other tools (Azure Key Vault, 1Password, HashiCorp Vault) are intentionally not on the roadmap; if you live in those, use them directly.

πŸ“š Docs

πŸ“„ License

The MIT License.

Documentation ΒΆ

Overview ΒΆ

Package main is the kvlt entry point. The CLI tree lives in the `cmd` package; the public, importable vault library lives under `pkg/kvlt` and is the same surface external Go programs consume.

Directories ΒΆ

Path Synopsis
Package cmd contains the kvlt cobra command tree.
Package cmd contains the kvlt cobra command tree.
internal
cli
Package cli holds CLI-only helpers β€” TTY prompts, output formatting, anything that only makes sense in the context of running kvlt on a terminal.
Package cli holds CLI-only helpers β€” TTY prompts, output formatting, anything that only makes sense in the context of running kvlt on a terminal.
version
Package version is the single source of build identity for the `kvlt version` cobra command.
Package version is the single source of build identity for the `kvlt version` cobra command.
pkg
kvlt
Package kvlt is the public vault library β€” importable by other Go projects (`import "github.com/retr0h/kvlt/pkg/kvlt"`) and used by kvlt's own CLI through the same surface.
Package kvlt is the public vault library β€” importable by other Go projects (`import "github.com/retr0h/kvlt/pkg/kvlt"`) and used by kvlt's own CLI through the same surface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL