env-vault

module
v0.0.18 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT

README

env-vault

env-vault is a local Go CLI for running commands with environment variables resolved from OS-keychain-backed secret profiles. It stores profile mappings in YAML and secret values in the operating system keychain.

github.com/ildarbinanas-design/env-vault

env-vault is an independent project and is not affiliated with any employer, vendor, or similarly named vault product.

Threat Model

env-vault reduces accidental exposure from shell history, plaintext config, and repeated manual exports. It does not make a child process safe: a child command can leak a secret if it prints its environment or forwards it elsewhere.

On Linux, process environment variables may be visible to the same user through /proc in some environments. Use short-lived commands, trusted child processes, and a CI secret manager for headless automation.

Security Model

  • Secret values are never printed by env-vault.
  • Config files store only profile mappings, never secret values.
  • There is no secret get command.
  • There is no --value flag.
  • Secret input is accepted only through a hidden prompt or --stdin.
  • --stdin trims exactly one trailing newline byte.
  • Production storage uses github.com/99designs/keyring with OS keychain-style backends only: macOS Keychain, Linux Secret Service, Linux pass, KWallet, and Windows Credential Manager.
  • Secret and service identifiers may use safe slash-separated hierarchy, but absolute paths and empty, . or .. components are rejected before backend access.
  • Config mutations reject symlink targets and use a synced mode-0600 temporary sibling for same-directory replacement.
  • Environment target names are compared case-insensitively so a profile remains unambiguous when moved to Windows.
  • The file/plaintext keyring backend is not production-enabled.
  • The test backend is insecure and enabled only when all three env vars are set: ENV_VAULT_BACKEND=test, ENV_VAULT_ALLOW_INSECURE_TEST_BACKEND=1, and ENV_VAULT_TEST_STORE=/tmp/....
  • Tests and smoke checks use generated ephemeral fixtures; stable secret payload fixtures are not stored in the repo.

Install

Homebrew (macOS and Linux)
brew install ildarbinanas-design/tap/env-vault

Supported platforms: macOS 15+ arm64/amd64 and Linux arm64/amd64. Homebrew downloads do not receive the Gatekeeper quarantine attribute, so no xattr -d com.apple.quarantine step is needed on macOS. The formula lives in ildarbinanas-design/homebrew-tap and is generated and proposed through a pull request by the release workflow. The tap runs style, installation, and exact-version checks separately. The release workflow opens or reuses a version-specific tap pull request, waits for test-formula.yml on the exact pull-request head, squash-merges that exact head, and then waits for the workflow's successful push run on the resulting release merge commit. Health also records the current tap head separately, proving that it still contains that merge and the exact formula even if unrelated tap commits arrive later. The release is healthy only after that post-merge check succeeds. See RELEASING.md. Upgrade with brew upgrade env-vault.

Migrating a manual or go install installation to Homebrew

First inspect every executable that your shell can resolve and the current Homebrew-prefix entry. These commands do not change anything:

type -a env-vault
ls -l /opt/homebrew/bin/env-vault
go version -m /opt/homebrew/bin/env-vault
brew link --overwrite env-vault --dry-run

The /opt/homebrew path is the default on Apple Silicon. If Homebrew uses a different prefix, obtain it with brew --prefix and inspect that prefix's bin/env-vault instead. The dry run may mention files that Homebrew would replace, but it does not replace them.

If the dry run reports an unmanaged manual binary or symlink, move that exact conflicting file to a new backup path before linking. Never overwrite an existing backup:

backup_dir="$HOME/.local/share/env-vault/backups"
backup="$backup_dir/env-vault-pre-homebrew-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
test ! -e "$backup" && mv /opt/homebrew/bin/env-vault "$backup"
brew link env-vault
hash -r

If type -a env-vault shows a go install location such as $(go env GOPATH)/bin/env-vault before the Homebrew path, back up that file in the same way or remove its directory from the earlier part of PATH. Run type -a env-vault and env-vault --version again after linking. The formula does not opt into automatic overwriting of files it does not own.

Manual download

Download the archive for your platform from the latest release, verify its checksum, and unpack (substitute the version, OS, and architecture):

Current version: v0.0.18.

The line above is managed by Release Please. v0.0.8 through v0.0.11 are preserved failed immutable tags and intentionally have no GitHub Release; use the latest Release until the next version has completed automated publication and health checks.

VERSION=vX.Y.Z TARGET=darwin-arm64
curl -fsSLO "https://github.com/ildarbinanas-design/env-vault/releases/download/${VERSION}/env-vault-${TARGET}.tar.gz"
curl -fsSLO "https://github.com/ildarbinanas-design/env-vault/releases/download/${VERSION}/env-vault-${TARGET}.tar.gz.sha256"
shasum -a 256 -c "env-vault-${TARGET}.tar.gz.sha256"
tar xzf "env-vault-${TARGET}.tar.gz"
./env-vault-${TARGET}/env-vault --version

On Linux, use sha256sum -c if shasum is not available. With a manual download on macOS, the browser or curl-less tooling may quarantine the binary; xattr -d com.apple.quarantine env-vault removes the attribute. The Homebrew path above avoids this entirely.

Install From Source

Source builds require Go 1.26.5 or newer. CI and release artifacts use the exact stable patch declared in go.mod.

GOTOOLCHAIN=go1.26.5 go version
GOTOOLCHAIN=go1.26.5 go build -o env-vault ./cmd/env-vault
./env-vault version

GitHub Builds

Pull-request and main CI call reusable-quality.yml. One graph performs source tests/vet/race/smoke, three native license checks, five native build/package/E2E jobs, and one complete-matrix gate. The matrix gate verifies the checked-in durable baseline; it does not depend on an expiring historical workflow artifact. The exact E2E reporter is built once for all five targets from an isolated checksum-pinned tool module, then each native job consumes only its source-SHA- and attempt-qualified reporter artifact with network fallback disabled.

For an exact release merge, a bounded native probe verifies --version, version, and JSON version output on every native target with a scrubbed environment. The file-only checker binds those saved results and seals the five archives, five checksum sidecars, contract/coverage/leak results, and semantic suite identity in one promotion manifest. Release planning verifies that exact attempt before creating the immutable tag. The seven-job build-binaries publisher then promotes those checked bytes, creates provenance and SPDX SBOM attestations, publishes Homebrew through exact PR-head and post-merge CI gates, and performs release health checks. It does not rebuild the product or repeat source quality.

GitHub API transport, observation, and mutations use gh; the repository's releasecheck tool is offline and validates saved JSON, manifests, and artifacts. An incomplete attempt deterministically requests a full rerun_all_jobs, never a failed-jobs-only artifact mixture. Manual publisher dispatch can only resume release-assets, homebrew, or health for an exact existing tag. v0.0.1v0.0.7 rebuilds are diagnostic-only and can never be published; v0.0.8 through v0.0.11 remain failed tags without Releases.

The only routine human release checkpoint is semantic review plus an exact version/PR/head-SHA authorization. Automation records that exact line as a pre-merge generated-PR comment from the authorizing owner/member and binds its identity and body digest into machine evidence. Planning, publisher, supply-chain, Homebrew, health, machine evidence, and metrics then run automatically. Planning and publication share one non-cancelling global concurrency group.

Pull request titles follow Conventional Commits because the squash title is the input to version and changelog generation. See CONTRIBUTING.md for the accepted types and RELEASING.md for the complete planning, publication, and repair contracts.

Supported targets are Linux amd64/arm64, macOS 15+ amd64/arm64, and Windows amd64. Each release contains exactly five archives and five matching SHA-256 files. The combined SPDX SBOM is a 14-day workflow artifact, and GitHub build provenance and SBOM attestations are stored separately for all five archives; they are deliberately not added to the immutable ten-asset Release contract.

macOS 15+ release artifacts are built on macOS runners with CGO_ENABLED=1; the macOS Keychain backend requires darwin artifacts with CGO enabled. Linux and Windows release targets keep CGO_ENABLED=0.

Every native CI runner executes the same public CLI scenarios against the unpacked release-like artifact. The suite also builds a separate coverage-instrumented subprocess binary, performs shuffled full and locking burn-ins, scans all retained evidence for runtime-generated sentinel values, and uploads JUnit, raw JSONL, feature coverage, normalized CLI contracts, and HTML/text statement coverage for 30 days. See docs/e2e.md for the complete requirement matrix and local commands.

Basic Usage

env-vault secret set nexus-token
env-vault profile create dev
env-vault profile add dev nexus-token:NPM_TOKEN
env-vault exec dev -- make test
env-vault exec --secret nexus-token:NPM_TOKEN -- make test

exec does not launch a shell by default. This is direct argv execution:

env-vault exec dev -- make test

A shell is used only when you explicitly provide one:

env-vault exec dev -- bash -lc 'make test'

JSON And Dry Run

env-vault --json secret check nexus-token
env-vault --quiet --output env-vault-meta.json exec dev -- make test
env-vault --dry-run exec dev -- make test
env-vault --dry-run secret set nexus-token

Successful JSON output follows this shape:

{"ok":true,"command":"secret_check","timestamp":"2026-07-06T00:00:00Z","data":{"name":"nexus-token","fingerprint":"example"},"warnings":[],"error":null}

Errors are structured with code, message, and remediation.

For exec, child stdout and stderr are inherited by default and may break machine-readable stdout. Prefer --quiet --output file for exec metadata without an additional envelope on stdout.

Doctor

env-vault doctor
env-vault --json doctor

doctor reports config path and backend status without printing secret values.

Config

The local config file is .env-vault.yaml. If present, it has priority over the user config for profile definitions.

User config defaults:

  • Linux: $XDG_CONFIG_HOME/env-vault/config.yaml or ~/.config/env-vault/config.yaml
  • macOS: ~/Library/Application Support/env-vault/config.yaml

profile create, profile add, and profile remove serialize their complete read-modify-validate-save operation through a persistent adjacent <config>.lock file. The lock is private (0600 where POSIX modes apply) and is intentionally not removed, because replacing it would allow two processes to lock different inodes. Lock waits are bounded; a timeout returns CONFIG_LOCKED. Dry runs do not create either the config or lock file, and profile add --check-secret completes its backend existence check before entering the config transaction. The default local .env-vault.yaml.lock is gitignored alongside .env-vault.yaml.

Example:

version: 1
profiles:
  dev:
    description: local development
    secrets:
      - name: nexus-token
        env: NPM_TOKEN
        required: true

Platform Notes

macOS uses the system Keychain through the selected Go backend.

Debian/Linux systems may require a Secret Service-compatible keyring daemon depending on desktop or headless setup. Linux also supports pass when the pass command is installed and the password store is initialized. Headless environments should use a CI secret manager or an explicit supported backend, not plaintext config.

To force pass, set ENV_VAULT_BACKEND=pass. If pass is unavailable, commands return BACKEND_UNAVAILABLE with remediation to install pass or use another supported OS keychain backend.

Shell Init Warning

Do not put tokens into bashrc, zshrc, shell history, or shell init snippets. Aliases and completions are acceptable; secret values are not.

Limitations

  • Child processes can leak env if they print or forward it.
  • OS process environment caveats still apply.
  • env-vault does not rotate tokens by itself.

Rotation

Revoke and rotate credentials externally, then update the stored value:

env-vault secret set nexus-token

Remove stale mappings with:

env-vault profile remove dev NPM_TOKEN

Contributing

Keep changes small, tested, and security-focused. Do not add commands that print secret values. Do not add plaintext production storage. Include tests and update docs for behavior changes.

Security Reports

Do not include secret values in issues, pull requests, logs, screenshots, terminal transcripts, or reproduction data. Use GitHub private vulnerability reporting when available, or contact the maintainer privately before sharing sensitive details.

Directories

Path Synopsis
cmd
e2e-baseline command
Command e2e-baseline generates, updates, and verifies the durable E2E compatibility baseline using local files only.
Command e2e-baseline generates, updates, and verifies the durable E2E compatibility baseline using local files only.
env-vault command
release-extract command
release-version-probe command
release-version-probe executes the three native version surfaces and saves bounded evidence for the file-only releasecheck verifier.
release-version-probe executes the three native version surfaces and saves bounded evidence for the file-only releasecheck verifier.
releasecheck command
releasecheck validates saved release inputs without network or credentials.
releasecheck validates saved release inputs without network or credentials.
e2e
cmd/e2e-runner command
Command e2e-runner builds (or verifies) an env-vault binary, runs the black-box E2E suite, and emits deterministic CI reports.
Command e2e-runner builds (or verifies) an env-vault binary, runs the black-box E2E suite, and emits deterministic CI reports.
testhelper command
Command testhelper is a portable subprocess used by env-vault's black-box E2E tests.
Command testhelper is a portable subprocess used by env-vault's black-box E2E tests.
internal
cli
e2ebaseline
Package e2ebaseline stores and verifies durable E2E compatibility floors.
Package e2ebaseline stores and verifies durable E2E compatibility floors.
e2esuite
Package e2esuite defines a stable semantic identity for the black-box E2E suite.
Package e2esuite defines a stable semantic identity for the black-box E2E suite.
githubtransport
Package githubtransport is the release-only GitHub REST transport boundary.
Package githubtransport is the release-only GitHub REST transport boundary.
releasearchive
Package releasearchive safely extracts the fixed set of env-vault release archives for release-time inspection.
Package releasearchive safely extracts the fixed set of env-vault release archives for release-time inspection.
releasecontract
Package releasecontract loads and validates env-vault's offline release contract.
Package releasecontract loads and validates env-vault's offline release contract.
releaseevidence
Package releaseevidence assembles and verifies the durable, offline record of an env-vault release.
Package releaseevidence assembles and verifies the durable, offline record of an env-vault release.
releasepromotion
Package releasepromotion records and verifies offline, exact-attempt release promotion evidence.
Package releasepromotion records and verifies offline, exact-attempt release promotion evidence.
releasesettings
Package releasesettings seals and verifies saved, credential-free GitHub repository settings observations.
Package releasesettings seals and verifies saved, credential-free GitHub repository settings observations.
strictjson
Package strictjson decodes security-sensitive JSON using exact struct field names.
Package strictjson decodes security-sensitive JSON using exact struct field names.

Jump to

Keyboard shortcuts

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