xenvault
v1.2.16
Published
CLI to pull SOPS-encrypted secrets from a private GitHub repo
Readme
xenvault
CLI to pull SOPS-encrypted secrets from a private GitHub secrets repo into your projects.
How it works
Secrets live in a dedicated GitHub repo structured like this:
secrets-repo/
.sops.yaml
myapp/
.env.development
.env.production
keys/
private-key.pem
anotherapp/
.env.stagingEach secret file is encrypted with SOPS + age. xenvault fetches and decrypts them into your project directory. .env.* files are treated as dotenv secrets; keys, certs, and other files are decrypted as binary secrets.
Prerequisites
brew install sops ageYou also need an age private key with access to the secrets. Set one of:
export SOPS_AGE_KEY_FILE=~/.age/key.txt # path to key file
export SOPS_AGE_KEY=AGE-SECRET-KEY-1... # inline key (good for CI)If you don't have a key yet, generate one and share the public key with the repo owner:
age-keygen -o ~/.age/key.txt
# Share the "Public key: age1..." line with the repo ownerInstallation
npm install -g xenvaultCommands
xenvault init
Initialize either a new project inside the secrets repo, or a secrets.config.ts in a consumer project.
? What would you like to initialize?
> New project in secrets repo
secrets.config.ts (to pull secrets into this directory)New project in secrets repo — run from inside the secrets repo. Creates the project directory.
secrets.config.ts — run from a consumer project. Saves the secrets repo location so xenvault pull knows where to fetch from.
xenvault list
List all projects and environments available in the secrets repo.
xenvault list myapp development, production
anotherapp stagingWorks from inside the secrets repo (no PAT needed) or remotely via GitHub API.
xenvault pull
Fetch and decrypt secrets into your project directory.
# Picker — browse and multi-select any encrypted files
xenvault pull -i
# Interactive prompts (select project, then environment)
xenvault pull
# Direct env pull (skips all prompts)
xenvault pull --project myapp --env development --output .
# Direct arbitrary file pull (for keys/certs/etc.)
xenvault pull --file myapp/keys/private-key.pem --output .For .env.<environment> pulls, writes .env.<environment> into the output directory. For arbitrary files, preserves the path inside the project, e.g. myapp/keys/private-key.pem → ./keys/private-key.pem.
🔥 Built-in file picker (-i)
xenvault pull -i
xenvault pull -i --output ./my-projectBrowse a project and multi-select exactly what you need — env files, keys, certs, etc. No npx gitpick or extra dependency required.
Selected files are decrypted and written under the output directory, preserving the path inside the project:
myapp/.env.production -> ./.env.production
myapp/keys/private-key.pem -> ./keys/private-key.pemWorks from a local secrets repo or remotely via the GitHub API.
Flags:
| Flag | Description |
|------|-------------|
| -i, --interactive | Browse and pick files with the built-in selector |
| --project | Project name (skips interactive prompt) |
| --env | Environment name, e.g. development |
| --file | Repo-relative file path, e.g. myapp/keys/private-key.pem |
| --output | Output directory (default: .) |
xenvault view <file>
Decrypt and print an encrypted file to stdout. Must have a local clone of the secrets repo.
xenvault view path/to/.env.developmentxenvault edit <file>
Open an encrypted file in your editor via sops. Must have a local clone of the secrets repo.
xenvault edit path/to/.env.developmentUses the $EDITOR environment variable (SOPS default).
Configuration
Remote access (consumer projects)
Run xenvault init → choose secrets.config.ts to create this file in your project:
/// <reference path="./xenvault.config.d.ts" />
export default {
github: {
url: 'https://github.com/your-org/secrets-repo'
},
projects: ['myapp', 'anotherapp'],
defaultProject: 'myapp'
} satisfies SecretsConfigxenvault init also generates xenvault.config.d.ts. xenvault pull keeps it synced from the discovered projects/environments.
You also need a GitHub PAT with repo read scope. Set it via:
export SECRETS_GITHUB_PAT=ghp_...Local access (inside the secrets repo)
No PAT needed. xenvault detects the secrets repo by looking for .sops.yaml in the directory tree.
Secrets repo structure
Environment files follow the convention <project>/.env.<environment>. A project can also contain arbitrary encrypted files, such as keys and certs:
myapp/.env.development
myapp/.env.production
myapp/keys/private-key.pem
myapp/certs/app.p12
anotherapp/.env.stagingA minimal .sops.yaml for age encryption:
creation_rules:
# dotenv env files
- path_regex: .*\.env\..*
age: >-
age1abc...(team member 1),
age1xyz...(team member 2)
# binary keys/certs/etc.
- path_regex: .*\.(pem|key|crt|cer|p12)$
age: >-
age1abc...(team member 1),
age1xyz...(team member 2)Encrypt new files:
# dotenv secret
sops --encrypt --input-type dotenv --output-type dotenv .env.development > myapp/.env.development
# binary secret, e.g. PEM key
sops --encrypt --input-type binary --output-type binary private-key.pem > myapp/keys/private-key.pemWhen pulling, xenvault decrypts each selected file individually. Selecting or pulling a project/folder means expanding it to its encrypted files, then running SOPS per file.
Adding a new team member
The new member generates their own key — the private key never leaves their machine.
1. New member generates an age key pair:
age-keygen -o ~/.age/key.txt
export SOPS_AGE_KEY_FILE=~/.age/key.txt # add to ~/.zshrcThe command prints a public key like:
Public key: age1abc123...They send you just that age1abc123... line.
2. Repo owner adds the public key to .sops.yaml:
creation_rules:
- path_regex: .*\.env\.development.*
age: >-
age1existing...,
age1abc123...3. Repo owner re-encrypts affected files:
sops updatekeys myapp/.env.development
# repeat for each file they need access toThis adds a copy of the data key encrypted for their public key — no secret is ever shared.
4. New member sets up their consumer project:
brew install sops age
export SOPS_AGE_KEY_FILE=~/.age/key.txt
# in their project directory
xenvault init # choose "secrets.config.ts"
export SECRETS_GITHUB_PAT=ghp_...
xenvault pull