README
¶
vaultenv
Securely share .env files with your team. vaultenv lets your team securely sync environment variables using GitHub for authentication and client-side encryption. No servers to run, no cloud accounts to set up. Vaulenv is completely free, just install and go.
Why vaultenv?
Every dev team has the same problem: you need to share database URLs, API keys, and other secrets. People end up pasting secrets in Slack DMs, emailing .env files, or committing them to private repos in plaintext. Paid alternatives exist that require cloud accounts and per developer fees.
vaultenv offers:
- Encrypted end-to-end: Secrets are encrypted on your machine before they ever leave it. GitHub stores only encrypted blobs (inside of a private repo) so even GitHub admins can't read your secrets.
- Zero infrastructure: No servers, no cloud accounts, no databases. Everything runs through GitHub repos you already have.
- Team-friendly: The vault owner controls who has access. New team members request access, the owner approves, and they're in.
- Works with your workflow: Push and pull
.envfiles like you push and pull code. Optional git hooks auto-sync on everygit push. - CI/CD ready: Create scoped deployment keys so your pipelines can pull secrets without a human in the loop.
- Portable: Log in from any machine with your GitHub account and vault passphrase. Your encryption keys travel with you. Use
vaultenv passphrase rotatewhen you want to change that passphrase without rotating your keypair.
Supported Platforms
| OS | Architecture | Binary |
|---|---|---|
| Linux | x86_64 (amd64) | vaultenv-linux-amd64 |
| Linux | ARM64 (aarch64) | vaultenv-linux-arm64 |
| macOS | Intel (amd64) | vaultenv-darwin-amd64 |
| macOS | Apple Silicon (arm64) | vaultenv-darwin-arm64 |
| Windows | x86_64 (amd64) | vaultenv-windows-amd64.exe |
| Windows | ARM64 | vaultenv-windows-arm64.exe |
Install
One command (Linux and macOS):
curl -fsSL https://raw.githubusercontent.com/Barestack-io/vaultenv/main/install.sh | sh
You'll be prompted to choose between a system-wide install (/usr/local/bin, requires sudo) or a user-only install (~/.local/bin). To skip the prompt:
# System-wide (recommended, requires sudo)
curl -fsSL https://raw.githubusercontent.com/Barestack-io/vaultenv/main/install.sh | sh -s -- --global
# Current user only (no sudo)
curl -fsSL https://raw.githubusercontent.com/Barestack-io/vaultenv/main/install.sh | sh -s -- --user
Other install methods:
# Via Go (requires Go toolchain)
go install github.com/Barestack-io/vaultenv/cmd/vaultenv@latest
# Build from source
git clone https://github.com/Barestack-io/vaultenv.git
cd vaultenv
make build # builds for your current platform
make build-all # cross-compile for all 6 platforms
Windows: Download the .exe binary from the latest release and add it to your PATH. NOTE: We have not tested vaultenv on windows. YMMV!
Updating vaultenv
- Self-update (release binaries): Run
vaultenv update. It compares your embedded version to GitHub Releases, downloads the asset for your OS/arch (same names asinstall.sh), and replaces the executable. You need write permission on the directory that contains the binary (for/usr/local/binthat often means re-runninginstall.shwith sudo, or usinginstall.sh --userso the binary lives in~/.local/bin). - Background reminder: Most commands trigger an update check at most once per 24 hours (timestamp in
update-check.jsonunder your vaultenv config directory). If a newer release exists, a short message is printed to stderr after the command finishes (waits up to about 2.5s for the check). Checks are skipped forvaultenv update,vaultenv version,help, andcompletion. SetVAULTENV_NO_UPDATE_CHECK=1to disable entirely (e.g. in CI). - Check only:
vaultenv update --check(or-n) prints whether a newer release exists and exits with status 1 if an update is available (status 0 if you are already current). Useful in scripts. - Windows: Replacing the running
.exemay fail if the file is locked; if so, download the latest.exefrom the releases page or re-run your installer. - Go install:
go install github.com/Barestack-io/vaultenv/cmd/vaultenv@latestremains a simple upgrade path if you use the Go toolchain. - Homebrew / other package managers: Use the manager’s upgrade command, not
vaultenv update, unless the packager documents otherwise.
Quick Start
This is all it takes to get your team sharing secrets securely:
# Step 1: Log in with your GitHub account
vaultenv login
# Opens your browser -- authorize vaultenv, then set a vault passphrase
# Step 2: Link your project
cd ~/projects/my-api
vaultenv link
# Detects your git remote, finds or creates the org vault, and links
# Step 3: Push your staging env to the team
vaultenv push staging
# Reads .env.staging, encrypts it, and uploads to the vault
# OR Push your personal .env file to the vault (see Personal Environments below)
vaultenv push
# Step 4: A teammate pulls it on their machine
vaultenv pull staging
# Downloads, decrypts, and writes .env.staging locally
That's it. Your .env.staging is now encrypted in GitHub and only your authorized team members can decrypt it.
Personal Environments
Every developer has their own local overrides -- database ports, debug flags, personal API keys. vaultenv handles this too:
# Push your personal .env (only YOU can decrypt this, not even the vault owner)
vaultenv push
# Pull your personal .env on another machine
vaultenv pull
Personal environments are stored in the same org vault but encrypted exclusively for you.
How It Works Under the Hood
Authentication
vaultenv uses GitHub's device flow -- the same mechanism the official GitHub CLI (gh) uses. When you run vaultenv login:
- vaultenv requests a one-time code from GitHub
- You visit github.com/login/device and enter the code
- vaultenv polls GitHub until you approve
- You get an access token -- no client secret is needed (the device flow is designed for CLI tools)
The token is stored locally at ~/.config/vaultenv/config.json and is scoped to repo access (needed to read/write the private vault repo).
Storage
vaultenv uses private GitHub repos as its storage backend -- no external services:
-
Personal vault (
<your-username>/vaultenv-secrets): Created automatically on first login. Stores only your encrypted private key and public key. This is how vaultenv finds your keys when you log in from a new machine. -
Org vault (
<your-org>/vaultenv-secrets): One per GitHub organization. Stores encrypted.envfiles for all repos in the org, organized by project. Created automatically when the first project is linked.
<org>/vaultenv-secrets/ # Private GitHub repo
.vaultenv-repo.json # Marker identifying this as a vaultenv vault
my-api/ # One folder per project
vault.json # Access control: owner, approved users, deploy keys
environments/
shared/
staging.enc # Encrypted .env.staging
staging.json # Key envelopes (one per authorized user)
production.enc
production.json
personal/
alice.enc # Alice's personal .env (only she can decrypt)
alice.json
Encryption and Key Management
vaultenv uses hybrid NaCl envelope encryption -- the same cryptographic primitives used by tools like age and Signal.
Per-user keypair (X25519 / Curve25519):
- Generated on first login
- The private key is encrypted with your vault passphrase using Argon2id (a memory-hard key derivation function resistant to brute-force attacks) and stored in your personal vault repo
- The public key is shared with your team via the org vault
When you push an environment file:
- A random 32-byte symmetric key is generated
- The
.envfile content is encrypted with this key using NaClsecretbox(XSalsa20-Poly1305) - The symmetric key is then wrapped (encrypted) separately for each authorized user using NaCl
box(Curve25519 key exchange + XSalsa20-Poly1305) - The encrypted file and the per-user key envelopes are uploaded to the vault repo
When you pull an environment file:
- The encrypted file and your key envelope are downloaded
- Your private key unwraps the symmetric key from your envelope
- The symmetric key decrypts the file content
- The plaintext
.envfile is written locally
This means each user can only decrypt files they've been explicitly authorized for. Even if someone gets read access to the entire vault repo, the encrypted blobs are useless without the corresponding private keys.
Key portability: Your private key is encrypted with your vault passphrase (Argon2id + secretbox) and stored in your personal vault. When you vaultenv login on a new machine, it downloads the encrypted key and asks for your passphrase. The underlying X25519 keypair never changes, so changing your passphrase only requires re-encrypting the key blob -- no re-encryption of any environment files. Use vaultenv passphrase rotate (on a machine where you can unlock) to pick a new passphrase and update the blob in your personal repo.
Cross-platform cryptography: The same algorithms and Go implementations are used on every OS and CPU architecture vaultenv supports. Argon2id key derivation (golang.org/x/crypto/argon2), NaCl secretbox (XSalsa20-Poly1305), and X25519 (curve25519) are pure Go here and deterministic for a given passphrase and ciphertext. Passphrases are interpreted as UTF-8 strings after normalization (see below). Hidden terminal input is handled via golang.org/x/term on Unix and Windows. Raw key files under keys/ are 32-byte binary blobs (not text), so line endings do not matter.
Passphrase normalization: Before validation, Argon2, and storage, vaultenv applies NormalizePassphrase: trim leading/trailing Unicode whitespace, strip a leading UTF-8 BOM (U+FEFF) if present, then Unicode NFC. That keeps the same visually typed passphrase from diverging across OS/keyboard normalization (e.g. NFD vs NFC for composed characters). Pure ASCII passphrases are unchanged. Extremely rare passphrases that intentionally relied on a non-NFC byte sequence for non-ASCII characters may fail to unlock after upgrading until you vaultenv passphrase rotate (or recover) once with a known passphrase.
Passphrase fingerprint (stderr): When you first create a vault passphrase (vaultenv login), rotate, or recover, vaultenv prints a line to stderr: UTF-8 byte length and SHA-256 (hex) of the normalized passphrase—not the secret itself. Compare these values across machines if unlock misbehaves: they must match when you intend to use the same passphrase. Security note: fingerprints still aid offline guessing if captured; CI logs and terminal scrollback may record stderr—avoid in untrusted logging environments.
Security Considerations
Before using vaultenv, understand the security model:
What vaultenv protects against:
- Secrets exposed in plaintext in repos, chat messages, or emails
- Unauthorized team members accessing secrets they shouldn't see
- CI/CD pipelines accessing environments they shouldn't (deployment keys are scoped)
- Someone reading the vault repo without the encryption keys
What vaultenv does NOT protect against:
- Compromised GitHub account: If an attacker gains access to your GitHub account AND knows your vault passphrase, they can decrypt your secrets. Use a strong GitHub password, enable 2FA, and choose a strong vault passphrase.
- Compromised local machine: If an attacker has access to your machine, they can read your local
.envfiles and your decrypted private key at~/.config/vaultenv/keys/. This is true for any secret management tool. - GitHub platform compromise: While GitHub never sees your plaintext secrets (encryption is client-side), a sufficiently motivated attacker with access to GitHub's infrastructure could theoretically modify the vault repo contents. The encryption prevents reading, not tampering.
- Weak vault passphrase: Your vault passphrase protects your private key. vaultenv enforces minimum requirements (12+ characters, uppercase, digit, special character), but choose something strong.
Recommendations:
- Enable two-factor authentication on your GitHub account
- Choose a vault passphrase you don't use anywhere else
- Rotate deployment keys periodically and revoke any that are no longer needed
- Use environment-scoped deployment keys (don't give your staging CI pipeline access to production secrets)
- Keep
.env,.env.*, and.vaultenvin your.gitignore(thelinkcommand does this automatically)
Complete Command Reference
vaultenv version
Print build metadata (version tag, git commit, build time). Release builds set these via linker flags in CI; local make build uses git describe or dev.
vaultenv version
vaultenv --version # short form (version number only)
vaultenv update
Download the latest release binary from GitHub and replace the running vaultenv executable (TLS to GitHub only; same asset names as install.sh).
vaultenv update # upgrade if a newer semver release exists
vaultenv update --check # exit 1 if a newer release exists; no download
Permissions: The parent directory of the binary must be writable. If update fails with permission denied, install to a user-writable path (install.sh --user) or run your usual installer with elevated permissions.
vaultenv login
Authenticate with GitHub and set up your encryption keys.
vaultenv login
What it does:
- Starts the GitHub device flow -- displays a code and URL
- Opens your browser to github.com/login/device
- After you approve, stores the access token locally
- If this is your first time: generates an X25519 keypair, asks you to create a vault passphrase (then prints a passphrase fingerprint to stderr), encrypts the private key, creates your personal vault repo, and uploads the encrypted key
- If you've used vaultenv before: downloads your encrypted private key from your personal vault and asks for your passphrase to decrypt it
If decryption fails because of a mistyped passphrase, you get up to five attempts before the command exits, so you do not have to repeat the GitHub device flow for a single typo.
Passphrase requirements: Minimum 12 characters, at least one uppercase letter, one digit, and one special character.
Creating a passphrase: You enter it twice (confirmation) so a typo is less likely to lock you out.
Whitespace and normalization: Leading and trailing Unicode whitespace (including stray newlines from pasting) is stripped; a leading BOM is removed; then Unicode NFC is applied (see Passphrase normalization under Encryption above). Deliberate spaces inside the passphrase are kept. If you created an older passphrase that relied on leading or trailing spaces alone, or on a rare non-NFC byte sequence for non-ASCII characters, unlock may fail after upgrading; recover from a machine that still has a working vaultenv install or backup of keys/, then run vaultenv passphrase rotate or vaultenv passphrase recover to set a known passphrase.
vaultenv passphrase rotate
Change the vault passphrase (the one that encrypts your private key in your personal GitHub repo). This is not your GitHub account password.
vaultenv passphrase rotate
Requirements: You must already be logged in (vaultenv login). The command is interactive: it asks for your current passphrase, then for a new passphrase twice (with the same strength rules as first-time setup). After each is entered, a passphrase fingerprint (length + SHA-256) is printed to stderr for cross-machine verification.
What it does: Downloads your encrypted private key from <username>/vaultenv-secrets, decrypts it with the current passphrase, re-encrypts with the new passphrase, uploads the updated keys/<username>.key.enc, and refreshes your local keys/ files under the vaultenv config directory. Shared or personal .env ciphertext in org vaults is not re-encrypted; only the wrapper around your long-term private key changes.
If you cannot remember your current passphrase: Use vaultenv passphrase recover below if you still have the raw private.key on disk and a valid login token; otherwise recover private.key from a backup or work with your team if you need a new keypair.
vaultenv passphrase recover
Set a new vault passphrase when you forgot the old one but still have keys/private.key on this machine (32-byte raw key after a working pull / login) and you are still logged in (config.json with a valid GitHub token).
vaultenv passphrase recover
What it does: Reads keys/<username>.pub from your personal GitHub repo (<username>/vaultenv-secrets), checks that it matches your local private.key, then uploads a new keys/<username>.key.enc encrypted with the new passphrase. Shared .env ciphertext in org vaults is not re-encrypted. After you confirm the new passphrase, a passphrase fingerprint is printed to stderr (see Passphrase fingerprint under Encryption above).
Afterwards: Run vaultenv login on other machines with the new passphrase. Your keypair is unchanged, so access to existing vaults is preserved.
Config paths: Raw keys live under <config-dir>/keys/. Default config directory is ~/.config/vaultenv on Linux (XDG) and ~/Library/Application Support/vaultenv on macOS. Set VAULTENV_CONFIG_DIR to override.
vaultenv init <namespace>
Pre-create a vault repo for an organization. This is for org admins who want to set up the vault before team members start linking projects.
vaultenv init my-org
Most users don't need this -- vaultenv link creates the vault automatically if you have repo creation permissions in the org.
vaultenv link
Link the current git repository to vaultenv. Run this inside a cloned repo with a GitHub remote.
cd ~/projects/my-api
vaultenv link
What it does:
- Checks that you're logged in (runs
loginif not) - Reads the git remote URL to determine the org and repo name
- Finds or creates the org vault repo (
<org>/vaultenv-secrets) - If this project has no vault config yet: creates one and makes you the owner
- If a vault config exists and you're authorized: links your local project
- If you're not authorized: offers to submit an access request to the vault owner
- Adds
.env,.env.*, and.vaultenvto.gitignoreif missing - Offers to install a git pre-push hook that auto-syncs your personal
.env
Vault naming: The default vault repo name is <org>/vaultenv-secrets. If that name is already taken by a non-vaultenv repo, it falls back to <org>/vaultenv-vault. If both are taken, you'll be prompted to choose a custom name.
vaultenv push [environment]
Encrypt and upload a .env file to the vault.
# Push a shared environment (encrypted for all authorized users)
vaultenv push staging # reads .env.staging, encrypts, uploads
vaultenv push production # reads .env.production, encrypts, uploads
# Push your personal .env (encrypted for you only)
vaultenv push # reads .env, encrypts for you only, uploads
Shared environments are encrypted for every approved user and every deployment key scoped to that environment. When new users are authorized, the vault owner re-encrypts shared environments to include them.
Personal environments are encrypted with only your public key. No one else -- not even the vault owner -- can decrypt them.
vaultenv pull [environment]
Download and decrypt a .env file from the vault.
# Pull a shared environment
vaultenv pull staging # downloads, decrypts, writes .env.staging
# Pull your personal .env
vaultenv pull # downloads, decrypts, writes .env
Flags:
| Flag | Description |
|---|---|
--export |
Print export KEY=VALUE lines to stdout instead of writing a file. Use with eval $(vaultenv pull staging --export) to inject into the current shell. |
--format github-env |
Append KEY=VALUE lines to the file at $GITHUB_ENV (for GitHub Actions). |
--deploy-key <token> |
Use a deployment key token directly (overrides the VAULTENV_DEPLOY_KEY env var). |
CI/CD mode: When the VAULTENV_DEPLOY_KEY environment variable is set, pull operates non-interactively. It decodes the deployment key token, uses GITHUB_TOKEN or VAULTENV_GITHUB_TOKEN for vault repo access, and requires no local config files.
vaultenv authorize
Approve pending access requests. Only the vault owner can run this.
vaultenv authorize
Lists all pending access requests for the current project. You can approve all or select specific users by entering comma-separated usernames. When a user is approved, all shared environment symmetric keys are re-encrypted to include their public key.
vaultenv deploy-key create <name>
Create a deployment key for CI/CD pipelines. You must be an authorized user (owner or approved).
# Create a key scoped to staging only
vaultenv deploy-key create ci-staging --environments staging
# Create a key for multiple environments
vaultenv deploy-key create ci-all --environments staging,production
# Create a key for all shared environments (default)
vaultenv deploy-key create ci-everything
Flags:
| Flag | Description |
|---|---|
--environments <env1,env2> |
Comma-separated list of environments this key can decrypt. Defaults to all shared environments. |
Output: Prints a deployment key token (a long opaque string starting with vaultenv_dk_v1_). This token is shown once and cannot be retrieved again -- the private key is not stored anywhere in the vault. Store it immediately as a CI/CD secret.
vaultenv deploy-key list
List all deployment keys for the current project.
vaultenv deploy-key list
Shows each key's name, scoped environments, who created it, and when.
vaultenv deploy-key revoke <name>
Revoke a deployment key. Must be the vault owner or the user who created the key.
vaultenv deploy-key revoke ci-staging
This removes the key from the vault config and rotates the symmetric keys for all affected environments. The old token becomes permanently useless -- even if someone still has it, it can't decrypt anything.
vaultenv status
Show the current state of vaultenv for this project.
vaultenv status
Displays: login status, encryption key status, link status, vault details, your role (owner/approved/pending), available environments, and counts of approved users, pending requests, and deployment keys.
CI/CD Integration
Setup
- Create a deployment key scoped to the environments your pipeline needs:
vaultenv deploy-key create github-actions-staging --environments staging
-
Store the outputted token as a secret in your CI/CD platform:
- GitHub Actions: Settings > Secrets >
VAULTENV_DEPLOY_KEY - GitLab CI: Settings > CI/CD > Variables >
VAULTENV_DEPLOY_KEY - Other: Whatever secret management your CI platform provides
- GitHub Actions: Settings > Secrets >
-
Also create a GitHub personal access token (or fine-grained token) with read access to the
<org>/vaultenv-secretsrepo, and store it asVAULTENV_GITHUB_TOKEN.
GitHub Actions Example
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install vaultenv
run: curl -fsSL https://raw.githubusercontent.com/Barestack-io/vaultenv/main/install.sh | sh -s -- --user
- name: Pull staging secrets
env:
VAULTENV_DEPLOY_KEY: ${{ secrets.VAULTENV_DEPLOY_KEY }}
VAULTENV_GITHUB_TOKEN: ${{ secrets.VAULT_PAT }}
run: vaultenv pull staging
# .env.staging is now available for your app
# Alternative: inject directly into the GitHub Actions environment
- name: Inject secrets into environment
env:
VAULTENV_DEPLOY_KEY: ${{ secrets.VAULTENV_DEPLOY_KEY }}
VAULTENV_GITHUB_TOKEN: ${{ secrets.VAULT_PAT }}
run: vaultenv pull staging --format github-env
# All variables from .env.staging are now available as ${{ env.VAR_NAME }}
Environment Variables
| Variable | Description |
|---|---|
VAULTENV_DEPLOY_KEY |
Deployment key token for non-interactive decryption |
VAULTENV_GITHUB_TOKEN |
GitHub token for vault repo access (takes priority over GITHUB_TOKEN) |
GITHUB_TOKEN |
Fallback GitHub token if VAULTENV_GITHUB_TOKEN is not set |
VAULTENV_CONFIG_DIR |
Override the config directory (default: ~/.config/vaultenv on Linux; macOS: ~/Library/Application Support/vaultenv) |
Local File Layout
~/.config/vaultenv/ # Global config (all managed by vaultenv)
config.json # GitHub access token + username
keys/
private.key # X25519 private key (mode 0600)
public.key # X25519 public key
<project>/.vaultenv # Per-project link config (gitignored)