SafetyBox is a single-user, CLI-first secrets manager for *nix. It keeps named, versioned secrets in a SQLite vault. Every value is sealed in an age envelope before it touches disk. There is no server, no GUI, and no unencrypted storage.
safetybox 4.0 is the current major release. Every verb works end to end: init, set, get, reveal, show, list, stale, disable, delete, purge, exec, passwd, rekey, and migrate. 4.0 restyles the stderr diagnostics as leveled log records and changes nothing on disk. safetybox is feature complete for its single-user scope. Keep an offsite backup of the identity file, because no passphrase can recover the vault without it.
The vault stores your public key, called the recipient. Writing a secret only needs that public key. Reading one needs your private identity, which lives in a passphrase-encrypted file outside the vault. This split means write operations never touch key material.
Secrets are versioned. Updates append a new version instead of replacing the old one, so rotation has an overlap window by design. An expiry date marks a secret stale but never deletes it.
Each envelope is bound to its row in the vault. The plaintext carries its own address, and decryption fails if the ciphertext was moved or swapped.
Install the latest tagged release with Go 1.26 or later.
go install github.com/samuel-stidham/safetybox/v4@latestOr build from source.
git clone https://github.com/samuel-stidham/safetybox.git
cd safetybox
make buildThe binary lands in bin/safetybox. To install it on your PATH, run
make install instead. That places the binary in $GOPATH/bin, which
is ~/go/bin by default. Prebuilt binaries for Linux and macOS ship
with each GitHub release. Every install path reports the same
v-prefixed version through safetybox --version.
The 3.0 release changed the on-disk format. A vault created by safetybox 1.x or 2.x must be migrated once before 3.0 or any later release can open it. Update the binary, then run:
safetybox migrateIt prompts for your passphrase, re-seals every secret into the new format in one transaction, and leaves your secret names, values, and versions unchanged. Back up the vault file first. Until you migrate, every other command tells you the vault needs it.
Stop anything else that touches the vault before you migrate,
especially a script or cron job still running the old binary. The old
binary checks the format only when it opens the vault. A 2.x set
racing the migration can therefore slip a legacy envelope in just
after the upgrade commits, and the next read of that secret fails
with a tamper-shaped error. Re-set the secret to repair it.
Create your identity and vault.
safetybox initinit generates an X25519 identity, encrypts it with a passphrase you choose, and creates the vault. It logs your recipient and runs a seal-and-open self-test before reporting success.
Back up the identity file immediately. Without it, every secret in the vault is unrecoverable. The passphrase alone cannot recover them.
For scripted setups, pass --passphrase-file instead of typing at the
prompt. Passphrases are never accepted from arguments or environment
variables.
The full documentation lives in docs/.
- Getting started takes you from install to your first rotated secret.
- Tutorial is the guided tour, from install through every command in order, including moving a vault between machines.
- Command reference covers every verb, flag, and output shape.
- Configuration covers paths, precedence, and the global flags.
- Security model explains the layers, the invariants, and the limits.
- Architecture explains the packages, the data model, and the address binding.
- Development covers building, testing, and the release pipeline.
- Linting policy records every linter exception and its justification.
The vault lives at $XDG_DATA_HOME/safetybox/vault.db. The identity
lives at $XDG_CONFIG_HOME/safetybox/identity.age. Both paths can be
overridden. A flag beats an environment variable, and both beat the
default. The configuration guide has the
details.
safetybox --vault /path/to/vault.db --identity /path/to/identity.age ...The environment variables are SAFETYBOX_VAULT and SAFETYBOX_IDENTITY.
They are fine for paths. Secret values and passphrases never go through
the environment.
Plaintext secret bytes live in one Go type, in one package, and leave it
through one method. Formatting, JSON encoding, and logging all render
[REDACTED] instead of the value. reveal is the single verb that prints
plaintext, deliberately and only when you ask for it. Everything else
redacts. The vault file is created 0600 and the identity file is
0600 inside a 0700 directory.
There is no plaintext storage mode and none will be added. The security model documents the full set of invariants and what safetybox does not defend against.
make help lists every target. The usual loop is make dev,
make lint, and make test. make dev builds a binary into bin/
with a -dev version suffix, so a test build never masquerades as an
installed one. Linting runs gofumpt, gci, and
golangci-lint, and CI fails if they would change anything. Tests run
against real SQLite and real age keys, with no mocks. The
development guide covers the rest.
Commits follow the conventional commit format. CI tags each qualifying
push to main automatically. A feat commit bumps the minor version
and a fix commit bumps the patch. GoReleaser then builds static
binaries and publishes the GitHub release with checksums.
MIT. See LICENSE.