envlope 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,735 @@
1
+ # envlope
2
+
3
+ [![npm version](https://img.shields.io/npm/v/envlope.svg?style=flat-square)](https://www.npmjs.com/package/envlope)
4
+ [![npm downloads](https://img.shields.io/npm/dm/envlope.svg?style=flat-square)](https://www.npmjs.com/package/envlope)
5
+ [![license](https://img.shields.io/npm/l/envlope.svg?style=flat-square)](./LICENSE)
6
+ [![node](https://img.shields.io/node/v/envlope.svg?style=flat-square)](https://nodejs.org)
7
+
8
+ > Encrypt your `.env` files with a key, push them to git safely, unlock with the same key.
9
+
10
+ No server. No accounts. No telemetry. Just a CLI that turns your `.env` into an AES-256-GCM ciphertext blob safe to commit alongside your code — and back again, with a key only your team knows.
11
+
12
+ ---
13
+
14
+ ## TL;DR
15
+
16
+ ```bash
17
+ npx envlope init
18
+ ```
19
+
20
+ That's it. The command encrypts `.env` → `.env.encrypted`, adds `.env` to `.gitignore`, and prints a key. Save the key. Commit the encrypted file. A teammate runs `npx envlope decrypt`, pastes the key, and they have your `.env`.
21
+
22
+ ---
23
+
24
+ ## Table of contents
25
+
26
+ - [Why envlope](#why-envlope)
27
+ - [How envlope compares](#how-envlope-compares)
28
+ - [Install](#install)
29
+ - [Quick start](#quick-start)
30
+ - [Commands](#commands)
31
+ - [`envlope init`](#envlope-init)
32
+ - [`envlope encrypt`](#envlope-encrypt)
33
+ - [`envlope decrypt`](#envlope-decrypt)
34
+ - [`envlope status`](#envlope-status)
35
+ - [`envlope view`](#envlope-view)
36
+ - [Common workflows](#common-workflows)
37
+ - [Sharing one key across multiple repos](#sharing-one-key-across-multiple-repos)
38
+ - [Multiple env files (dev/staging/prod)](#multiple-env-files-devstagingprod)
39
+ - [Using `ENVLOPE_KEY` for CI/scripts](#using-envlope_key-for-ciscripts)
40
+ - [`--json` for automation](#--json-for-automation)
41
+ - [Updating `.env` values](#updating-env-values)
42
+ - [Rotating the key](#rotating-the-key)
43
+ - [Recovering from a lost key](#recovering-from-a-lost-key)
44
+ - [Security model](#security-model)
45
+ - [Troubleshooting](#troubleshooting)
46
+ - [How it works (technical)](#how-it-works-technical)
47
+ - [Development](#development)
48
+ - [Changelog](#changelog)
49
+ - [License](#license)
50
+
51
+ ---
52
+
53
+ ## Why envlope
54
+
55
+ - **Zero infrastructure.** No vault, no service to pay for, no account to set up. The key lives wherever you put it.
56
+ - **Git-native.** The encrypted file is a single line of text. Diff-friendly, branch-friendly, PR-friendly.
57
+ - **Modern crypto.** AES-256-GCM with a fresh random IV per encryption. The ciphertext is computationally indistinguishable from random.
58
+ - **Designed for teams.** One shared key unlocks every repo your team uses. New teammate? Share one key, they're in.
59
+ - **CI-friendly.** `ENVLOPE_KEY` env var, `--json` output mode, and `--strict` exit codes mean it slots into pipelines cleanly.
60
+ - **Trivially auditable.** ~400 lines of TypeScript, four small dependencies, all of which you've seen before.
61
+
62
+ ---
63
+
64
+ ## How envlope compares
65
+
66
+ | Feature | **envlope** | dotenvx | sops | git-crypt |
67
+ | -------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ |
68
+ | Setup time | seconds | ~1 min | ~5 min | ~10 min |
69
+ | Server required | No | No | No | No |
70
+ | Symmetric key | ✓ | ✓ | ✓ | ✓ |
71
+ | Asymmetric / multi-recipient | ✗ | ✓ | ✓ | ✓ |
72
+ | Multi-file (`.env.staging` etc.) | ✓ | ✓ | ✓ | ✓ |
73
+ | CI integration | env var, `--json` | ✓ | ✓ | ✗ |
74
+ | Built-in status / drift checks | ✓ | ✗ | ✗ | ✗ |
75
+ | Built-in update notifier | ✓ | ✗ | ✗ | ✗ |
76
+ | Lines of code (rough) | ~400 | ~10k | ~50k | ~5k |
77
+ | Dependency footprint | 4 deps | 30+ | huge | none (Go binary) |
78
+
79
+ **Pick envlope when:** you want the minimum-viable encrypted-env workflow, you trust one shared key, your team is small-to-medium, and you value being able to audit the whole tool in 20 minutes.
80
+
81
+ **Pick something else when:** you need per-teammate keys (sops, dotenvx), enterprise compliance with audit logs (sops), or transparent file-level encryption inside git itself (git-crypt).
82
+
83
+ ---
84
+
85
+ ## Install
86
+
87
+ You have three options, ordered from least to most setup:
88
+
89
+ ### 1. No install — use `npx` (recommended for occasional use)
90
+
91
+ ```bash
92
+ npx envlope <command>
93
+ ```
94
+
95
+ `npx` fetches envlope from npm the first time and caches it locally. Subsequent runs are instant. No global state, no `node_modules` clutter.
96
+
97
+ ### 2. Global install — bare `envlope` command everywhere
98
+
99
+ ```bash
100
+ npm install -g envlope
101
+ ```
102
+
103
+ Now you can run `envlope <command>` in any directory without the `npx` prefix.
104
+
105
+ ### 3. Project dev dependency
106
+
107
+ ```bash
108
+ npm install --save-dev envlope
109
+ ```
110
+
111
+ Use inside the project via `npx envlope` or add it to your `package.json` scripts:
112
+
113
+ ```json
114
+ {
115
+ "scripts": {
116
+ "env:status": "envlope status",
117
+ "env:encrypt": "envlope encrypt",
118
+ "env:decrypt": "envlope decrypt"
119
+ }
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Quick start
126
+
127
+ ### Step 1 — encrypt your `.env`
128
+
129
+ In a project with a `.env` file you want to share with your team:
130
+
131
+ ```bash
132
+ npx envlope init
133
+ ```
134
+
135
+ Output:
136
+
137
+ ```
138
+ ✓ Encrypted .env → .env.encrypted
139
+ ✓ Created .gitignore (added .env, .env.local, .env.encrypted.bak)
140
+
141
+ SAVE THIS KEY — it cannot be recovered:
142
+ ────────────────────────────────────────────────────────────────
143
+ envlope_key_IiE85vhkdQLrNxksL9JWrWYOPHW35+gmJX366q8bapU=
144
+ ────────────────────────────────────────────────────────────────
145
+ Save it in your password manager and share with teammates over a secure channel.
146
+
147
+ Next:
148
+ $ git add .env.encrypted .gitignore
149
+ $ git commit -m "Add encrypted env"
150
+ ```
151
+
152
+ ### Step 2 — commit and push the encrypted file
153
+
154
+ ```bash
155
+ git add .env.encrypted .gitignore
156
+ git commit -m "Add encrypted env"
157
+ git push
158
+ ```
159
+
160
+ Your plaintext `.env` stays on your machine (it's gitignored automatically). Only `.env.encrypted` goes to the remote.
161
+
162
+ ### Step 3 — your teammate clones and decrypts
163
+
164
+ ```bash
165
+ git clone <your-repo>
166
+ cd <your-repo>
167
+ npx envlope decrypt
168
+ # Enter your envlope key: ****************************************
169
+ ```
170
+
171
+ They paste the key (you sent it via 1Password, Signal, or another secure channel), and `.env` appears locally.
172
+
173
+ ---
174
+
175
+ ## Commands
176
+
177
+ Every command accepts an optional positional `[file]` argument (default: `.env`), and every command supports `--json` for structured output.
178
+
179
+ ### `envlope init`
180
+
181
+ Encrypt an env file for the first time. Generates a fresh random key (or accepts an existing one via `--key`), writes `<file>.encrypted`, and ensures the plaintext file is in `.gitignore`.
182
+
183
+ ```bash
184
+ # Generate a new random key
185
+ npx envlope init
186
+
187
+ # Reuse an existing key (multi-repo flow)
188
+ npx envlope init --key envlope_key_<paste-here>
189
+
190
+ # Init for a specific env file
191
+ npx envlope init .env.production
192
+
193
+ # Re-init without the confirmation prompt (for scripts / CI)
194
+ npx envlope init --yes
195
+
196
+ # JSON output for scripts
197
+ npx envlope init --json
198
+ ```
199
+
200
+ **Options:**
201
+
202
+ | Flag | Description |
203
+ | ----------------- | ---------------------------------------------------------------------------------------------------- |
204
+ | `[file]` | Positional. The env file to encrypt. Default: `.env`. |
205
+ | `-k, --key <key>` | Use a specific key instead of generating a new one. Useful for sharing one key across multiple repos. |
206
+ | `-y, --yes` | Skip the confirmation prompt when `<file>.encrypted` already exists. |
207
+ | `--json` | Emit a single JSON object instead of human-readable text. |
208
+
209
+ **Behavior notes:**
210
+
211
+ - If `<file>.encrypted` already exists, the command warns you, asks for confirmation, and **backs up the old ciphertext to `<file>.encrypted.bak`** before replacing it. That backup is automatically gitignored.
212
+ - The generated key prints **once** to stdout. The tool does not save it anywhere. If you lose it, the encrypted file is unrecoverable.
213
+
214
+ ---
215
+
216
+ ### `envlope encrypt`
217
+
218
+ Re-encrypt an env file after editing values locally. Requires the existing key — refuses keys that don't match the current encrypted file, so accidental key rotation can't happen here.
219
+
220
+ ```bash
221
+ # Prompts for the key (hidden input)
222
+ npx envlope encrypt
223
+
224
+ # Pass the key inline
225
+ npx envlope encrypt --key envlope_key_<paste-here>
226
+
227
+ # Encrypt a specific env file
228
+ npx envlope encrypt .env.production --key ...
229
+
230
+ # Use ENVLOPE_KEY env var instead of --key
231
+ export ENVLOPE_KEY=envlope_key_<paste-here>
232
+ npx envlope encrypt
233
+
234
+ # JSON output
235
+ npx envlope encrypt --json
236
+ ```
237
+
238
+ **Options:**
239
+
240
+ | Flag | Description |
241
+ | ----------------- | ---------------------------------------------------------------------------------------- |
242
+ | `[file]` | Positional. The env file to re-encrypt. Default: `.env`. |
243
+ | `-k, --key <key>` | The envlope key (otherwise read from `ENVLOPE_KEY` env var, otherwise prompted). |
244
+ | `--json` | Emit a single JSON object instead of human-readable text. |
245
+
246
+ **Behavior notes:**
247
+
248
+ - Before re-encrypting, the command verifies the provided key actually matches the current `<file>.encrypted`. If the key has been rotated (via `envlope init`), the old key is rejected with a clear error.
249
+ - Errors cleanly if no `<file>.encrypted` exists yet (use `init` first).
250
+
251
+ ---
252
+
253
+ ### `envlope decrypt`
254
+
255
+ Decrypt `<file>.encrypted` back to `<file>` using the team's shared key.
256
+
257
+ ```bash
258
+ # Prompts for the key (hidden input)
259
+ npx envlope decrypt
260
+
261
+ # Pass the key inline
262
+ npx envlope decrypt --key envlope_key_<paste-here>
263
+
264
+ # Decrypt a specific file
265
+ npx envlope decrypt .env.production --key ...
266
+
267
+ # Use ENVLOPE_KEY env var
268
+ export ENVLOPE_KEY=envlope_key_<paste-here>
269
+ npx envlope decrypt
270
+
271
+ # Skip the "overwrite existing .env?" prompt
272
+ npx envlope decrypt --key ... --yes
273
+
274
+ # JSON output
275
+ npx envlope decrypt --json --key ... --yes
276
+ ```
277
+
278
+ **Options:**
279
+
280
+ | Flag | Description |
281
+ | ----------------- | ---------------------------------------------------------------------------------- |
282
+ | `[file]` | Positional. The env file to decrypt. Default: `.env`. |
283
+ | `-k, --key <key>` | The envlope key (otherwise read from `ENVLOPE_KEY` env var, otherwise prompted). |
284
+ | `-y, --yes` | Overwrite an existing decrypted file without prompting. |
285
+ | `--json` | Emit a single JSON object instead of human-readable text. |
286
+
287
+ **Behavior notes:**
288
+
289
+ - If the plaintext file already exists, the command prompts before overwriting (unless `--yes`).
290
+ - A wrong key fails fast with `Invalid key — decryption failed.` — no stack trace, no leaked information.
291
+
292
+ ---
293
+
294
+ ### `envlope status`
295
+
296
+ Quick health check for a project's env file. Reports whether the plaintext and encrypted files exist, whether they're in sync, when the ciphertext was last updated, and whether `.gitignore` is protecting the plaintext.
297
+
298
+ ```bash
299
+ npx envlope status
300
+
301
+ # Output:
302
+ # File: .env
303
+ # ✓ .env exists
304
+ # ✓ .env.encrypted exists (last encrypted 2h ago)
305
+ # ✓ .env and .env.encrypted are in sync
306
+ # ✓ .gitignore protects .env
307
+
308
+ # Check a specific file
309
+ npx envlope status .env.production
310
+
311
+ # JSON output for CI
312
+ npx envlope status --json
313
+
314
+ # Exit code 1 if out of sync (good for CI guards)
315
+ npx envlope status --strict
316
+ ```
317
+
318
+ **Options:**
319
+
320
+ | Flag | Description |
321
+ | ---------- | -------------------------------------------------------------------------------------- |
322
+ | `[file]` | Positional. The env file to check. Default: `.env`. |
323
+ | `--json` | Emit a single JSON object instead of human-readable text. |
324
+ | `--strict` | Exit with code 1 if the plaintext file is newer than the encrypted file (drift check). |
325
+
326
+ No key is required — `status` only inspects file metadata, never reads the encrypted contents.
327
+
328
+ ---
329
+
330
+ ### `envlope view`
331
+
332
+ Print the decrypted value of a single variable to stdout, without ever writing the plaintext env file to disk. Perfect for shell scripts that need exactly one secret.
333
+
334
+ ```bash
335
+ # Print one value
336
+ npx envlope view DATABASE_URL
337
+
338
+ # In a script — assign without touching disk
339
+ DATABASE_URL=$(npx envlope view DATABASE_URL)
340
+
341
+ # Pass the key inline (or via ENVLOPE_KEY env var)
342
+ npx envlope view DATABASE_URL --key envlope_key_<paste-here>
343
+
344
+ # View a variable from a specific env file
345
+ npx envlope view STRIPE_KEY .env.production --key ...
346
+
347
+ # JSON output: {"variable":"DATABASE_URL","value":"postgres://..."}
348
+ npx envlope view DATABASE_URL --json
349
+ ```
350
+
351
+ **Options:**
352
+
353
+ | Flag | Description |
354
+ | ----------------- | ------------------------------------------------------------------------------------------ |
355
+ | `<variable>` | **Required.** The variable name to look up. |
356
+ | `[file]` | Positional. The env file to read from. Default: `.env`. |
357
+ | `-k, --key <key>` | The envlope key (otherwise read from `ENVLOPE_KEY` env var, otherwise prompted). |
358
+ | `--json` | Emit `{"variable":"...", "value":"..."}` instead of just the value. |
359
+
360
+ **Behavior notes:**
361
+
362
+ - Returns exit code 1 if the variable isn't found in the env file, with a clear error message.
363
+ - Reads the encrypted file in memory only — the plaintext env never touches disk.
364
+ - Parsing is intentionally simple: `KEY=VALUE` per line. Comments (`#`) and blank lines are skipped. Quoted values and multi-line values are not interpreted specially; the entire string after the first `=` is returned verbatim.
365
+
366
+ ---
367
+
368
+ ## Common workflows
369
+
370
+ ### Sharing one key across multiple repos
371
+
372
+ If your team works across many projects, you usually don't want a separate key for every `.env`. Generate one key the first time, then reuse it everywhere with `--key`:
373
+
374
+ ```bash
375
+ # First project — generate and save the key
376
+ cd ~/projects/repo-1
377
+ npx envlope init
378
+
379
+ # Every other project — paste the same key
380
+ cd ~/projects/repo-2
381
+ npx envlope init --key envlope_key_<paste-here>
382
+
383
+ cd ~/projects/repo-3
384
+ npx envlope init --key envlope_key_<paste-here>
385
+ ```
386
+
387
+ Now one shared key unlocks every repo's secrets. Onboarding a new teammate becomes **one** key, not twenty.
388
+
389
+ ---
390
+
391
+ ### Multiple env files (dev/staging/prod)
392
+
393
+ Each env file is encrypted independently. You can use the same key across all of them, or rotate per environment.
394
+
395
+ ```bash
396
+ # Encrypt each environment independently
397
+ npx envlope init .env.development
398
+ npx envlope init .env.staging
399
+ npx envlope init .env.production
400
+
401
+ # Or share one key across them
402
+ KEY=envlope_key_<paste-here>
403
+ npx envlope init .env.development --key $KEY
404
+ npx envlope init .env.staging --key $KEY
405
+ npx envlope init .env.production --key $KEY
406
+
407
+ # View a single secret from production
408
+ npx envlope view DATABASE_URL .env.production --key $KEY
409
+
410
+ # Check sync state of one file
411
+ npx envlope status .env.production
412
+ ```
413
+
414
+ Each `<file>.encrypted` is its own ciphertext; teammates only need the key for the environments they have access to.
415
+
416
+ ---
417
+
418
+ ### Using `ENVLOPE_KEY` (set the key once, every command picks it up)
419
+
420
+ Once `ENVLOPE_KEY` is set, every command works without `--key`.
421
+
422
+ > ⚠️ **`ENVLOPE_KEY` is an OS-level environment variable. It does NOT go inside your `.env` file.**
423
+ >
424
+ > Your `.env` is what envlope *encrypts* — it never reads it. Putting the key inside `.env` would be circular (you'd need to decrypt `.env` to read the key that decrypts `.env`). Set it at the shell/OS level instead, as shown below.
425
+
426
+ The key resolution priority is:
427
+
428
+ 1. `--key <key>` CLI flag (highest)
429
+ 2. `ENVLOPE_KEY` environment variable
430
+ 3. Interactive prompt (only when running in a TTY)
431
+
432
+ #### Windows (PowerShell)
433
+
434
+ ```powershell
435
+ # Current PowerShell session only (resets when the window closes)
436
+ $env:ENVLOPE_KEY = "envlope_key_<paste-here>"
437
+
438
+ # Persistent for your user account (survives reboots, takes effect in NEW terminals)
439
+ [Environment]::SetEnvironmentVariable("ENVLOPE_KEY", "envlope_key_<paste-here>", "User")
440
+
441
+ # Verify it's set in a new PowerShell window
442
+ $env:ENVLOPE_KEY
443
+
444
+ # Remove it later
445
+ [Environment]::SetEnvironmentVariable("ENVLOPE_KEY", $null, "User")
446
+ ```
447
+
448
+ You can also set it through the GUI: **Win+R → `sysdm.cpl` → Advanced → Environment Variables → New**.
449
+
450
+ #### Windows (CMD)
451
+
452
+ ```cmd
453
+ :: Current session only
454
+ set ENVLOPE_KEY=envlope_key_<paste-here>
455
+
456
+ :: Persistent for your user
457
+ setx ENVLOPE_KEY "envlope_key_<paste-here>"
458
+ ```
459
+
460
+ #### macOS / Linux (bash / zsh)
461
+
462
+ ```bash
463
+ # Current shell session only
464
+ export ENVLOPE_KEY="envlope_key_<paste-here>"
465
+
466
+ # Persistent — add this line to ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish
467
+ echo 'export ENVLOPE_KEY="envlope_key_<paste-here>"' >> ~/.zshrc
468
+ source ~/.zshrc
469
+ ```
470
+
471
+ #### Real-world dev workflow (Vite, Next.js, NestJS, etc.)
472
+
473
+ Add an `envlope decrypt` call to your `package.json` scripts using npm's `pre*` lifecycle hooks. Teammates set `ENVLOPE_KEY` once on their machine, then run their dev server normally — decryption happens automatically:
474
+
475
+ ```json
476
+ {
477
+ "scripts": {
478
+ "predev": "envlope decrypt --yes",
479
+ "dev": "vite",
480
+
481
+ "prestart": "envlope decrypt --yes",
482
+ "start": "nest start",
483
+
484
+ "prebuild": "envlope decrypt --yes",
485
+ "build": "next build"
486
+ }
487
+ }
488
+ ```
489
+
490
+ A new teammate's flow becomes:
491
+
492
+ ```bash
493
+ git clone <repo>
494
+ cd <repo>
495
+ npm install
496
+ # (one-time: set ENVLOPE_KEY using one of the methods above)
497
+ npm run dev # ← envlope auto-decrypts .env, then vite starts
498
+ ```
499
+
500
+ #### GitHub Actions / CI
501
+
502
+ ```yaml
503
+ - name: Decrypt env
504
+ env:
505
+ ENVLOPE_KEY: ${{ secrets.ENVLOPE_KEY }}
506
+ run: npx envlope decrypt --yes
507
+ ```
508
+
509
+ Store the key as a repository secret (`Settings → Secrets and variables → Actions`), reference it via `${{ secrets.ENVLOPE_KEY }}`.
510
+
511
+ ---
512
+
513
+ ### `--json` for automation
514
+
515
+ Every command emits a single line of JSON to stdout when `--json` is passed. Errors emit `{"error": "...", "code": 1}` on the same channel.
516
+
517
+ ```bash
518
+ # Check sync state in CI
519
+ RESULT=$(npx envlope status --json)
520
+ IN_SYNC=$(echo "$RESULT" | jq -r .in_sync)
521
+ if [ "$IN_SYNC" != "true" ]; then
522
+ echo "Env file out of sync — re-encrypt before committing"
523
+ exit 1
524
+ fi
525
+
526
+ # Init in CI and capture the new key
527
+ NEW=$(npx envlope init --yes --json)
528
+ KEY=$(echo "$NEW" | jq -r .key)
529
+ ```
530
+
531
+ In `--json` mode the CLI never prompts. Pass `--key` (or set `ENVLOPE_KEY`), and pass `--yes` for any destructive operation, otherwise the command exits with a clear error.
532
+
533
+ ---
534
+
535
+ ### Updating `.env` values
536
+
537
+ After you edit `.env` locally, sync the encrypted file before committing:
538
+
539
+ ```bash
540
+ # Edit .env however you like
541
+ echo "NEW_VAR=value" >> .env
542
+
543
+ # Re-encrypt with your existing key
544
+ npx envlope encrypt --key envlope_key_<paste-here>
545
+
546
+ # Commit the updated ciphertext
547
+ git add .env.encrypted
548
+ git commit -m "Update env vars"
549
+ git push
550
+ ```
551
+
552
+ When teammates pull, they re-run `envlope decrypt` to pick up the changes.
553
+
554
+ Tip: run `envlope status` before committing to confirm `.env` and `.env.encrypted` are in sync.
555
+
556
+ ---
557
+
558
+ ### Rotating the key
559
+
560
+ If someone leaves the team, or you suspect the key was exposed, generate a new one. **This invalidates the old key** — anyone still holding it can no longer decrypt new versions.
561
+
562
+ ```bash
563
+ # In the project where the encrypted file lives:
564
+ npx envlope init
565
+ # Warning: .env.encrypted already exists. Generating a new key will replace it...
566
+ # ✓ Backed up old .env.encrypted → .env.encrypted.bak
567
+ # Generate a new key and re-encrypt? (y/N) y
568
+ ```
569
+
570
+ The old ciphertext is automatically backed up to `.env.encrypted.bak` (gitignored) before being replaced — so if you regret the rotation in the next few seconds, `cp .env.encrypted.bak .env.encrypted` restores the previous state.
571
+
572
+ Output prints the new key. Save it. Share the new key with the remaining team via a secure channel. Old key is now useless against the freshly-encrypted file.
573
+
574
+ For non-interactive use (scripts/CI):
575
+
576
+ ```bash
577
+ npx envlope init --yes
578
+ ```
579
+
580
+ ---
581
+
582
+ ### Recovering from a lost key
583
+
584
+ If everyone on the team has lost the key, the encrypted file cannot be recovered — that's the whole security premise.
585
+
586
+ You can, however, start over from your current local `.env`:
587
+
588
+ ```bash
589
+ # Make sure your local .env is up to date
590
+ # Then run init again — it will prompt to replace the encrypted file
591
+ npx envlope init --yes
592
+ ```
593
+
594
+ This generates a fresh key and a fresh encrypted file. The old ciphertext goes to `.env.encrypted.bak` (where it's still unrecoverable without the lost key, but at least preserved on disk). Old encrypted versions in your git history remain unrecoverable.
595
+
596
+ ---
597
+
598
+ ## Security model
599
+
600
+ **The key IS the secret.** Once your team has the key, they can decrypt any past, present, or future version of the encrypted file. Treat it like a master password:
601
+
602
+ - Store it in a password manager (1Password, Bitwarden, etc.), not in plaintext anywhere.
603
+ - Share over a secure channel (encrypted DM, password manager sharing, in person) — never in a public Slack channel, email body, or commit message.
604
+ - Rotate the key (`envlope init`) whenever someone leaves the team or you suspect the key was exposed.
605
+ - The tool does **not** back up the key. If everyone loses it, the encrypted file is computationally unrecoverable.
606
+
607
+ **What about pushing the encrypted file to a public repo?** Yes, that's exactly what envlope is designed for. AES-256-GCM with a 256-bit random key has no known practical attack — making the ciphertext public exposes nothing as long as the key stays private.
608
+
609
+ **What envlope does NOT protect against:**
610
+
611
+ - A compromised teammate who has the key — they can decrypt everything.
612
+ - A keylogger on a teammate's machine that captures the key when they type it.
613
+ - A leaked `.env` file (the plaintext one) committed to git by accident — the encryption only protects what you encrypt.
614
+ - An attacker with shell access to a machine that has the decrypted `.env` sitting on disk.
615
+
616
+ These are the same trade-offs as every shared-secret system. envlope protects **at-rest** secrets that travel through your repo.
617
+
618
+ ---
619
+
620
+ ## Troubleshooting
621
+
622
+ ### `Invalid key — decryption failed.`
623
+
624
+ The key you provided doesn't decrypt the current `.env.encrypted`. Either:
625
+
626
+ 1. You typed/pasted the key wrong (most common — re-check from your password manager).
627
+ 2. A teammate rotated the key via `envlope init` and you have the old one — ask them for the current key.
628
+
629
+ ### `This key does not match the current .env.encrypted.`
630
+
631
+ Same root cause as above, but triggered by `encrypt`. The tool refuses to re-encrypt with a stale key because doing so would silently grant access to whoever still holds that key.
632
+
633
+ ### `No key provided. Pass --key, set ENVLOPE_KEY env var, or run interactively.`
634
+
635
+ You ran `encrypt`, `decrypt`, or `view` without a `--key`, without `ENVLOPE_KEY` in the environment, and stdin wasn't a TTY (so the tool couldn't prompt). Use one of the three options the message suggests.
636
+
637
+ ### I added `ENVLOPE_KEY=...` to my `.env` file but envlope still prompts for the key
638
+
639
+ **`ENVLOPE_KEY` is an OS environment variable, not a line in `.env`.** envlope doesn't read your `.env` file looking for its own key — that'd be circular (you'd need to decrypt `.env` to read the key to decrypt `.env`). See the [Using `ENVLOPE_KEY`](#using-envlope_key-set-the-key-once-every-command-picks-it-up) section above for how to set it correctly per platform (`$env:ENVLOPE_KEY` in PowerShell, `export` in bash, etc.).
640
+
641
+ ### `No .env file found in this directory.`
642
+
643
+ You ran `envlope init` or `envlope encrypt` in a directory without a `.env` file. Create one first, or pass a different file as a positional arg (`envlope encrypt .env.staging`).
644
+
645
+ ### `No .env.encrypted file found in this directory.`
646
+
647
+ You ran a command that requires an existing encrypted file but it isn't here. Either you're in the wrong directory, or no one has run `envlope init` for this project yet.
648
+
649
+ ### `Variable 'XYZ' not found in .env.encrypted.`
650
+
651
+ From `envlope view`. The variable name doesn't exist in the decrypted env file. Check spelling and that you're targeting the right file.
652
+
653
+ ### `envlope: command not found` after `npm i envlope`
654
+
655
+ Local installs don't put binaries on your PATH. Either use `npx envlope <command>`, or install globally with `npm install -g envlope`.
656
+
657
+ ### Re-init prompts fail in scripts / CI
658
+
659
+ The confirmation prompt for re-init requires a TTY. In non-interactive environments, pass `--yes`:
660
+
661
+ ```bash
662
+ npx envlope init --yes
663
+ npx envlope decrypt --key ... --yes
664
+ ```
665
+
666
+ In `--json` mode the tool refuses to prompt at all — pass `--yes` explicitly or provide all required keys/files via flags or env vars.
667
+
668
+ ---
669
+
670
+ ## How it works (technical)
671
+
672
+ - **Algorithm:** AES-256-GCM (authenticated encryption — provides both confidentiality and tamper-detection)
673
+ - **Key:** 32 random bytes from `crypto.randomBytes`, base64-encoded with an `envlope_key_` prefix
674
+ - **IV:** 12 random bytes per encryption — every encrypt produces a different ciphertext, even for identical plaintext
675
+ - **Auth tag:** 16-byte GCM tag — any tampering with the ciphertext is detected at decrypt time and rejected
676
+ - **Key derivation:** none. The key is already high-entropy random bytes; no PBKDF2/Argon2 needed.
677
+
678
+ The encrypted file is a single line:
679
+
680
+ ```
681
+ envlope:1:<base64(iv || ciphertext || tag)>
682
+ ```
683
+
684
+ The `envlope:1:` prefix is a version tag — if the format ever changes in a backwards-incompatible way (it won't on a whim), old files will be readable by version-aware tooling.
685
+
686
+ **Crypto implementation lives in [`src/crypto.ts`](./src/crypto.ts)** — ~50 lines, uses only Node's built-in `node:crypto` module. No third-party crypto dependencies.
687
+
688
+ **No background processes, no network calls (except the optional update-notifier version check), no telemetry.** The tool only reads/writes:
689
+
690
+ - `.env` (or whichever env file you pointed it at — the plaintext)
691
+ - `.env.encrypted` (the ciphertext)
692
+ - `.env.encrypted.bak` (created automatically when `init` replaces an existing ciphertext)
693
+ - `.gitignore` (to ensure plaintext files never accidentally end up in git)
694
+
695
+ That's it.
696
+
697
+ ---
698
+
699
+ ## Development
700
+
701
+ ```bash
702
+ # Clone and install
703
+ git clone <repo>
704
+ cd envlope
705
+ npm install
706
+
707
+ # Run tests (vitest)
708
+ npm test
709
+
710
+ # Watch mode while developing
711
+ npm run test:watch
712
+
713
+ # Run the CLI from source
714
+ npm run dev -- <command>
715
+
716
+ # Type-check
717
+ npm run typecheck
718
+
719
+ # Build production bundle to dist/
720
+ npm run build
721
+ ```
722
+
723
+ Test coverage spans crypto round-trips, key format validation, multi-file flows, env var resolution, JSON output, and end-to-end CLI tests against temp directories. See [`tests/`](./tests/).
724
+
725
+ ---
726
+
727
+ ## Changelog
728
+
729
+ See [CHANGELOG.md](./CHANGELOG.md) for the full version history.
730
+
731
+ ---
732
+
733
+ ## License
734
+
735
+ [MIT](./LICENSE) — do whatever you want, just don't blame me.