https://github.com/nasimstg/xenvsync
Encrypt, commit, and inject .env secrets with AES-256-GCM. No cloud required.
https://github.com/nasimstg/xenvsync
aes-256 cli cross-platform cross-platform-development devtools dotenv encryption env env-management env-manager env-shared env-sync env-syncer environment-variables golang secret-manager secrets security syncer
Last synced: 2 months ago
JSON representation
Encrypt, commit, and inject .env secrets with AES-256-GCM. No cloud required.
- Host: GitHub
- URL: https://github.com/nasimstg/xenvsync
- Owner: nasimstg
- License: mit
- Created: 2026-03-21T04:56:44.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-03-29T17:16:17.000Z (3 months ago)
- Last Synced: 2026-03-29T18:58:36.976Z (3 months ago)
- Topics: aes-256, cli, cross-platform, cross-platform-development, devtools, dotenv, encryption, env, env-management, env-manager, env-shared, env-sync, env-syncer, environment-variables, golang, secret-manager, secrets, security, syncer
- Language: TypeScript
- Homepage: https://xenvsync.softexforge.io
- Size: 93.8 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Roadmap: docs/ROADMAP.md
Awesome Lists containing this project
README
xenvsync
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) │
└──────────────────┘
```
## Install
```bash
# 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.22+
go install github.com/nasimstg/xenvsync@latest
# Nix
nix run github:nasimstg/xenvsync
```
Or download a prebuilt binary from [Releases](https://github.com/nasimstg/xenvsync/releases).
Build from source
```bash
git clone https://github.com/nasimstg/xenvsync.git
cd xenvsync
make build
```
Arch Linux (AUR)
```bash
git clone https://aur.archlinux.org/xenvsync.git
cd xenvsync
makepkg -si
```
## Quick Start
```bash
xenvsync 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)
```
## Why xenvsync?
| 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. |
## How It Compares
| 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 |
## Commands
| 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] -- ` | 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 ` | Register a team member's public key |
| `xenvsync team remove ` | 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`
## Multi-Environment Support
Use `--env` to work with named environments. File paths follow the convention `.env.` / `.env..vault`:
```bash
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 environments
```
You can also set `XENVSYNC_ENV` to avoid passing `--env` every time.
### Fallback Merging
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.
## Team Sharing (V2 Vault)
With V2, each team member has their own X25519 keypair. Vaults are encrypted per-member — no shared symmetric key needed.
```bash
# 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
xenvsync team add bob
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 automatically
```
V1 vaults (created without a team roster) are still fully readable.
## Key Rotation
Rotate encryption keys and re-encrypt the vault in one atomic step:
```bash
# 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 staging
```
When 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.
## Security Model
| 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 |
## Project Structure
```
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
```
## Contributing
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
If you find xenvsync useful, consider giving it a star — it helps others discover the project.
## License
[MIT](LICENSE)