Encrypt, commit, and inject .env secrets — no cloud required.
xenvsync encrypts your .env files into a .env.vault using AES-256-GCM so you can safely commit secrets to Git — while the decryption key never leaves your machine.
.env (plaintext) .env.vault (encrypted)
┌──────────────────┐ push ──► ┌──────────────────────┐
│ DB_HOST=localhost │ │ #/---xenvsync vault--│
│ API_KEY=sk-secret │ ◄── pull │ aGVsbG8gd29ybGQ... │
└──────────────────┘ │ #/---end xenvsync----│
│ └──────────────────────┘
│ run │
▼ │
┌──────────────────┐ safe to commit to git
│ child process │
│ (secrets in RAM) │
└──────────────────┘
# Homebrew (macOS / Linux)
brew install nasimstg/tap/xenvsync
# Scoop (Windows)
scoop bucket add nasimstg https://github.com/nasimstg/scoop-bucket
scoop install xenvsync
# npm
npm install -g @nasimstg/xenvsync
# or run without installing
npx @nasimstg/xenvsync
# Go 1.25+
go install github.com/nasimstg/xenvsync@latest
# Nix
nix run github:nasimstg/xenvsyncOr download a prebuilt binary from Releases.
Build from source
git clone https://github.com/nasimstg/xenvsync.git
cd xenvsync
make buildArch Linux (AUR)
git clone https://aur.archlinux.org/xenvsync.git
cd xenvsync
makepkg -sixenvsync init # generate key + update .gitignore
xenvsync push # encrypt .env → .env.vault
git add .env.vault && git commit # safe to commit
xenvsync pull # decrypt .env.vault → .env
xenvsync run -- npm start # inject secrets into process (in-memory, no .env written)| Problem | xenvsync |
|---|---|
| Cloud secret managers are overkill for local dev | Works 100% offline. Key stays on your machine. |
.env files leak into Git history |
Encrypt first, commit the vault. |
| Secrets written to disk in CI/scripts | xenvsync run injects in-memory. Plaintext never touches disk. |
| Sharing secrets = Slack DMs and sticky notes | Commit .env.vault, share the key out-of-band. |
| Feature | xenvsync | dotenv-vault | git-crypt | sops |
|---|---|---|---|---|
| No cloud account required | Yes | No | Yes | Yes |
Encrypts only .env (not all files) |
Yes | Yes | No | Yes |
In-memory secret injection (run) |
Yes | No | No | No |
| Single binary, zero dependencies | Yes | No | No | No |
| Diff / status preview | Yes | No | No | Yes |
| Standard crypto (AES-256-GCM) | Yes | Yes | AES-256 | Various |
| Command | Description |
|---|---|
xenvsync init [--force] [--passphrase] |
Generate a 256-bit key, add it to .gitignore |
xenvsync push [--env NAME] |
Encrypt .env → .env.vault |
xenvsync pull [--env NAME] |
Decrypt .env.vault → .env |
xenvsync run [--env NAME] -- <cmd> |
Decrypt in-memory and inject into a child process |
xenvsync diff [--env NAME] [--show-values] |
Preview changes between .env and the vault |
xenvsync status [--env NAME] |
Show file presence, timestamps, and sync direction |
xenvsync keygen [--force] |
Generate an X25519 keypair for team vault encryption |
xenvsync whoami |
Display your public key and identity path |
xenvsync team add <name> <key> |
Register a team member's public key |
xenvsync team remove <name> |
Remove a team member from the roster |
xenvsync team list |
List all team members and their public keys |
xenvsync rotate [--env NAME] [--revoke NAME] |
Rotate encryption key and re-encrypt the vault |
xenvsync doctor [--env NAME] |
Audit local setup for security issues |
xenvsync verify [--env NAME] |
Verify vault integrity, detect duplicate keys |
xenvsync log [--env NAME] [-n N] |
Show vault change history from Git commits |
xenvsync envs |
List all discovered environments and their sync status |
xenvsync export [--format FMT] |
Decrypt vault and output as JSON, YAML, shell, tfvars, or dotenv |
xenvsync completion [SHELL] |
Generate shell completions (bash/zsh/fish/powershell) |
xenvsync version |
Print version, commit, and build date |
Aliases:
push=encrypt,pull=decrypt
Use --env to work with named environments. File paths follow the convention .env.<name> / .env.<name>.vault:
xenvsync push --env staging # .env.staging → .env.staging.vault
xenvsync pull --env production # .env.production.vault → .env.production
xenvsync run --env staging -- npm start
xenvsync diff --env staging
xenvsync envs # list all discovered environmentsYou can also set XENVSYNC_ENV to avoid passing --env every time.
When pushing, xenvsync automatically merges variables from fallback files if they exist:
.env.shared → base variables (lowest priority)
.env.staging → environment-specific overrides
.env.local → developer-local overrides (highest priority)
Use --no-fallback to disable merging and encrypt only the primary file.
With V2, each team member has their own X25519 keypair. Vaults are encrypted per-member — no shared symmetric key needed.
# Each member generates their identity (once)
xenvsync keygen
# Share your public key with the team
xenvsync whoami
# Add team members to the project roster
xenvsync team add alice <alice-public-key>
xenvsync team add bob <bob-public-key>
xenvsync team list
# Push now auto-encrypts for all team members (V2 format)
xenvsync push # creates V2 vault with per-member key slots
# Each member decrypts with their own private key
xenvsync pull # uses ~/.xenvsync/identity automaticallyV1 vaults (created without a team roster) are still fully readable.
Rotate encryption keys and re-encrypt the vault in one atomic step:
# V1 (symmetric key): generates new .xenvsync.key and re-encrypts
xenvsync rotate
# V2 (team mode): re-encrypts with fresh ephemeral keys for all members
xenvsync rotate
# Revoke a team member and rotate in one step
xenvsync rotate --revoke exmember
# Rotate a specific environment
xenvsync rotate --env stagingWhen revoking a member, the member is removed from the roster and the vault is re-encrypted so they can no longer decrypt it — even if they have a copy of the old vault file.
| Property | Detail |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Key | 32 random bytes via crypto/rand, hex-encoded, file mode 0600 |
| Nonce | Fresh 12-byte random nonce per encryption — same plaintext always produces different ciphertext |
| Vault layout | [nonce ‖ ciphertext ‖ GCM tag], base64-wrapped with header/footer |
| Key isolation | Never embedded in vault output. Auto-added to .gitignore on init |
| Identity | X25519 keypair at ~/.xenvsync/identity (mode 0600) for asymmetric team sharing |
| Passphrase | Optional key-encryption-key via scrypt (N=32768, r=8, p=1) + AES-256-GCM |
| Memory zeroing | Key material overwritten with zeros after use |
| Permission check | Warns at runtime if key file is readable by group/others |
xenvsync/
├── main.go # Entry point + version wiring
├── cmd/ # CLI commands (Cobra)
│ ├── root.go # Root command
│ ├── init.go # init (--force)
│ ├── push.go / pull.go # encrypt / decrypt
│ ├── run.go # in-memory injection
│ ├── diff.go / status.go # preview & sync state
│ ├── keygen.go / whoami.go # X25519 identity management
│ ├── version.go # version info
│ └── keyutil.go # shared key loading + permission check
├── internal/
│ ├── crypto/ # AES-256-GCM + X25519 key exchange
│ ├── env/ # .env parser (multiline, quotes, export)
│ └── vault/ # vault file format
├── examples/
│ ├── docker/ # Dockerfile, compose, entrypoint
│ ├── ci/ # GitHub Actions, GitLab, CircleCI, Bitbucket
│ └── hooks/ # Git pre-commit hook
├── packaging/
│ └── aur/ # Arch Linux AUR PKGBUILD
├── flake.nix # Nix flake for NixOS / nix users
├── Makefile # build, test, lint, install
├── .goreleaser.yml # cross-platform release builds
└── .github/workflows/ci.yml # CI: test matrix, lint, release
Contributions are welcome! See CONTRIBUTING.md for guidelines.
If you find xenvsync useful, consider giving it a star — it helps others discover the project.