https://github.com/sulthonzh/envault
Zero-dep .env file encryption for teams — encrypt, decrypt, diff, merge secrets with a passphrase
https://github.com/sulthonzh/envault
aes decrypt dotenv encrypt env passphrase secrets security
Last synced: 28 days ago
JSON representation
Zero-dep .env file encryption for teams — encrypt, decrypt, diff, merge secrets with a passphrase
- Host: GitHub
- URL: https://github.com/sulthonzh/envault
- Owner: sulthonzh
- License: mit
- Created: 2026-06-01T00:52:39.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-21T15:04:55.000Z (about 1 month ago)
- Last Synced: 2026-06-21T17:05:36.341Z (about 1 month ago)
- Topics: aes, decrypt, dotenv, encrypt, env, passphrase, secrets, security
- Language: JavaScript
- Size: 24.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# envault
Zero-dep .env file encryption for teams. Encrypt, decrypt, diff, merge, and rotate secrets with a passphrase.
## Why?
You're committing `.env` files to... somewhere. Maybe a shared drive, maybe a private repo, maybe Slack (please stop). **envault** gives you a dead-simple way to encrypt `.env` files so you can safely share them without leaking secrets.
No vault server. No cloud dependency. No runtime deps. Just a passphrase and AES-256-GCM.
## Install
```bash
npm install -g envault
```
Or use without installing:
```bash
npx envault encrypt .env --out .env.encrypted --passphrase=my-secret
```
## Usage
### Encrypt
```bash
# Encrypt .env → prints encrypted blob to stdout
envault encrypt .env --passphrase my-secret
# Encrypt to file
envault encrypt .env --out .env.encrypted --passphrase my-secret
# Pipe stdin
echo "DATABASE_URL=postgres://localhost/db" | envault encrypt --passphrase my-secret
```
### Decrypt
```bash
# Decrypt to stdout
envault decrypt .env.encrypted --passphrase my-secret
# Decrypt to file
envault decrypt .env.encrypted --out .env --passphrase my-secret
```
### View keys & values
```bash
# Show key=value pairs
envault view .env.encrypted --passphrase my-secret
# List just the keys
envault keys .env.encrypted --passphrase my-secret
# JSON output (good for scripting)
envault keys .env.encrypted --passphrase my-secret --json
```
### Diff two encrypted files
```bash
# See what changed between staging and prod
envault diff .env.staging.enc .env.prod.enc --passphrase my-secret
```
Output:
```
Added:
+ NEW_FEATURE_FLAG=true
Changed:
~ LOG_LEVEL: debug → info
Removed:
- DEPRECATED_KEY=old-value
```
### Merge encrypted files
```bash
# Merge with strategy: ours | theirs | union (default: ours)
envault merge .env.base.enc .env.override.enc --strategy theirs --passphrase my-secret
```
### Rotate passphrase
```bash
envault rotate .env.encrypted
# Prompts for old + new passphrase
```
### Environment variable
Set `ENVAULT_PASSPHRASE` to avoid typing it every time (useful in CI):
```bash
export ENVAULT_PASSPHRASE=my-secret
envault encrypt .env --out .env.enc
envault decrypt .env.enc --out .env
```
## Programmatic API
```js
const { encrypt, decrypt, encryptEnv, decryptEnv, parseEnv, diffEnv, mergeEnv, rotatePassphrase } = require('envault');
// Basic encrypt/decrypt
const blob = encrypt('secret content', 'my-passphrase');
const plain = decrypt(blob, 'my-passphrase');
// Env-specific with metadata
const result = encryptEnv('DB_HOST=localhost\nDB_PORT=5432', 'passphrase');
// → { encrypted: '...', lineCount: 2, byteSize: 32 }
// Parse .env content
const parsed = parseEnv('KEY=value\n# comment\nKEY2="quoted value"');
// → [{ key: 'KEY', value: 'value' }, { key: 'KEY2', value: 'quoted value' }]
// Diff two parsed env arrays
const diff = diffEnv(parsed1, parsed2);
// → { added: [...], removed: [...], changed: [...] }
// Merge with strategy
const merged = mergeEnv(content1, content2, 'ours'); // ours | theirs | union
// Rotate passphrase
const newBlob = rotatePassphrase(oldBlob, oldPass, newPass);
```
## How It Works
1. **Key derivation**: PBKDF2 with SHA-256, 100k iterations, random 32-byte salt
2. **Encryption**: AES-256-GCM with random 16-byte IV
3. **Format**: `base64(salt + iv + authTag + ciphertext)`
4. **Deterministic?** No. Same input + passphrase → different ciphertext every time (random salt + IV)
## Security Notes
- This is **file-level encryption** for sharing secrets, not a replacement for a proper secrets manager
- Passphrase strength matters — use long, random passphrases
- The encrypted blob contains the salt and IV (that's fine, they're not secrets)
- AES-256-GCM provides authenticated encryption (integrity + confidentiality)
- No password is ever stored — if you forget it, your data is gone
## CLI Reference
```
envault encrypt [--out ] [--passphrase ] [--json]
envault decrypt [--out ] [--passphrase ] [--json]
envault view [--passphrase ] [--json]
envault keys [--passphrase ] [--json]
envault diff [--passphrase ] [--json]
envault merge [--strategy ours|theirs|union] [--passphrase ] [--out ]
envault rotate [--out ]
```
Flags:
- `--out, -o` — output file path
- `--passphrase` — passphrase (or set `ENVAULT_PASSPHRASE`)
- `--json` — JSON output
- `--pretty` — pretty-print JSON
- `--strategy` — merge strategy: `ours`, `theirs`, `union`
## License
MIT