dotcrypt 0.0.0-stage → 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - `status` command: shows, per environment, whether you or your team changed something since your
12
+ last sync and what to do next. No password needed.
13
+ - Sync tracking: fingerprints recorded in `.git/dotcrypt/sync-state.json` (per clone, never
14
+ committed) so dotcrypt can tell local changes from incoming ones.
15
+ - `encrypt` now prints the Git command to send the `.enc` file to your team.
16
+
17
+ ### Changed
18
+
19
+ - Simpler dashboard: plain-language states ("You have unshared changes", "New changes from your
20
+ team", ...), one main button per environment, a copyable commit/push command, a "How sharing
21
+ works" guide, and a Compare dialog to choose between your version and your team's.
22
+
23
+ ### Fixed
24
+
25
+ - The dashboard no longer reports "Local changed" right after decrypting.
26
+
27
+ ## [1.0.0] - 2026-11-10
28
+
29
+ ### Added
30
+
31
+ - `init`, `encrypt`, `decrypt`, `rotate-key` and `diff` commands for AES-256-GCM (scrypt) encrypted
32
+ `.env` files that are safe to commit to Git.
33
+ - `ui` command: a local, token-protected web dashboard bound to `127.0.0.1`.
34
+ - Overwrite protection with key-level diffs (values are never printed).
35
+ - `encrypt --replace` to deliberately discard a vault file that is unreadable, belongs to another
36
+ environment, or uses a different password.
37
+ - Passwords via interactive prompt, `DOTCRYPT_PASSWORD` / `DOTCRYPT_NEW_PASSWORD`, or flags.
38
+
39
+ [Unreleased]: https://github.com/zubayerhimel/dotcrypt/compare/v1.0.0...HEAD
40
+ [1.0.0]: https://github.com/zubayerhimel/dotcrypt/releases/tag/v1.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zubayerhimel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,376 @@
1
- # Temporary Holding Version
1
+ # dotcrypt
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Securely sync multiple `.env` environments across machines **through your existing Git repository**.
4
+ Plaintext env files stay local and git-ignored; only AES-256-GCM encrypted `.enc` files and a shared
5
+ `.dotcrypt.json` configuration are committed.
6
+
7
+ ```
8
+ .dotcrypt.json <- committed: environments + file mappings (no secrets)
9
+ .env.development <- local only (git-ignored)
10
+ .env.development.enc <- committed: encrypted with AES-256-GCM
11
+ .env.production <- local only (git-ignored)
12
+ .env.production.enc <- committed
13
+ ```
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g dotcrypt
19
+ dotcrypt --help
20
+ ```
21
+
22
+ ### From source
23
+
24
+ Requires Node.js **20.19+** (needed to build the dashboard; the CLI alone runs on 18.17+).
25
+
26
+ ```bash
27
+ git clone https://github.com/zubayerhimel/dotcrypt.git
28
+ cd dotcrypt
29
+ npm run setup # installs CLI + dashboard dependencies and builds the dashboard
30
+ npm link # exposes the `dotcrypt` command globally
31
+ dotcrypt --help
32
+ ```
33
+
34
+ `npm run setup` is the same as running these one by one:
35
+
36
+ ```bash
37
+ npm install # CLI dependencies
38
+ npm --prefix ui install # dashboard dependencies
39
+ npm run build:ui # builds the dashboard into ui/dist
40
+ ```
41
+
42
+ Re-run `npm run build:ui` after pulling changes to the dashboard code.
43
+
44
+ > **Windows PowerShell:** if you get "running scripts is disabled on this system", use `npm.cmd`
45
+ > instead of `npm` (e.g. `npm.cmd run setup`, `npm.cmd link`), or run the commands from Command Prompt.
46
+
47
+ To uninstall the global link: `npm unlink -g dotcrypt`.
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ cd path/to/your-project
53
+ dotcrypt init # creates .dotcrypt.json and updates .gitignore
54
+ dotcrypt ui # opens the dashboard in your browser
55
+ ```
56
+
57
+ Prefer the terminal? Everything in the dashboard is also available as a CLI command (see below).
58
+
59
+ ## Commands
60
+
61
+ | Command | Description |
62
+ | --- | --- |
63
+ | `status` | Show what changed for each environment and what to do next. No password needed. |
64
+ | `init [-n name] [--envs a,b,c] [-d env] [-y] [-f]` | Create `.dotcrypt.json` and add ignore rules for plaintext env files to `.gitignore`. |
65
+ | `encrypt [-e env] [-p password] [-f] [--replace]` | Encrypt the mapped local file (e.g. `.env.staging`) into its `.enc` file. |
66
+ | `decrypt [-e env] [-p password] [-f]` | Decrypt the `.enc` file into the mapped local file. |
67
+ | `rotate-key [-e env] [-o old] [-n new]` | Re-encrypt with a new password, a fresh salt and a fresh IV. |
68
+ | `diff [-e env] [-p password] [--exit-code]` | Show **key names** that were added / removed / changed. Values are never printed. |
69
+ | `ui [--port n] [--no-open]` | Open the local web dashboard (see below). |
70
+
71
+ If `-e` is omitted you are prompted to pick an environment (non-interactive runs fall back to
72
+ `defaultEnvironment`).
73
+
74
+ ### Supplying passwords
75
+
76
+ Passwords are resolved in this order:
77
+
78
+ 1. CLI flag (`-p`, `-o`, `-n`) — works, but prints a warning because flags leak into shell
79
+ history and process listings.
80
+ 2. Environment variable — `DOTCRYPT_PASSWORD` (and `DOTCRYPT_NEW_PASSWORD` for the new
81
+ password in `rotate-key`). Recommended for CI.
82
+ 3. Interactive masked prompt (recommended for humans). New passwords must be at least 12
83
+ characters and are asked twice.
84
+
85
+ ### Overwrite protection
86
+
87
+ - `decrypt` never silently replaces a local file that differs from the vault: it shows which keys
88
+ would be lost/changed and asks for confirmation. Use `--force` to skip the prompt.
89
+ - `encrypt` shows a key-level summary of what will change in the vault and asks before
90
+ overwriting. If the files are identical, nothing is written (no Git churn).
91
+ - `encrypt` **verifies your password against the existing `.enc` file**. This prevents a
92
+ teammate from accidentally re-encrypting a shared vault with a different password. Change
93
+ passwords with `rotate-key`. To deliberately discard a vault file that is unreadable, belongs to
94
+ another environment, or uses a different password, pass `--replace` (`--force` alone never does
95
+ this). When the password was typed at a prompt, you must re-enter it before the vault is replaced.
96
+ - `init` refuses to overwrite an existing `.dotcrypt.json` without confirmation or `--force`.
97
+ - Non-interactive sessions (CI, pipes) never prompt; they fail safely unless `--force` is given.
98
+
99
+ ### Exit codes
100
+
101
+ | Code | Meaning |
102
+ | --- | --- |
103
+ | `0` | Success |
104
+ | `1` | General error, aborted, or (`diff --exit-code`) differences found |
105
+ | `2` | Wrong password or corrupted / tampered encrypted file |
106
+ | `130` | Cancelled at a prompt (Ctrl+C) |
107
+
108
+ ## `.dotcrypt.json`
109
+
110
+ ```json
111
+ {
112
+ "version": 1,
113
+ "vault": {
114
+ "name": "my-app",
115
+ "createdAt": "2026-11-07T06:00:00.000Z",
116
+ "cipher": "aes-256-gcm",
117
+ "kdf": "scrypt"
118
+ },
119
+ "defaultEnvironment": "development",
120
+ "environments": {
121
+ "development": { "file": ".env.development", "encryptedFile": ".env.development.enc" },
122
+ "staging": { "file": ".env.staging", "encryptedFile": ".env.staging.enc" },
123
+ "production": { "file": ".env.production", "encryptedFile": ".env.production.enc" }
124
+ }
125
+ }
126
+ ```
127
+
128
+ You may edit mappings by hand (e.g. `"file": "apps/api/.env"`). Validation rules:
129
+
130
+ - Environment names: 1–64 chars of letters, digits, `-`, `_`.
131
+ - Paths must be relative and stay inside the project (no absolute paths, no `..` escapes).
132
+ - `encryptedFile` must end in `.enc`; `file` must not.
133
+ - No two mappings may share a path (compared case-insensitively).
134
+
135
+ Custom plaintext paths that the default `.env` / `.env.*` rules don't cover are added to
136
+ `.gitignore` explicitly the next time you run `init`.
137
+
138
+ ## Team workflow
139
+
140
+ The idea in one sentence: **your secrets live in normal `.env` files; you share them by
141
+ encrypting them into `.enc` files and pushing those through Git.**
142
+
143
+ The easiest way to work is the dashboard (`dotcrypt ui`). Each environment card says in plain
144
+ words what state it is in and shows one button for the next step. `dotcrypt status` shows the
145
+ same thing in the terminal.
146
+
147
+ **Setting up (once, by one person):**
148
+
149
+ 1. Run `dotcrypt init` (or open `dotcrypt ui` and click through the setup).
150
+ 2. Create your `.env` files as usual and click **Share with team** on each card (or run
151
+ `dotcrypt encrypt -e <env>`). You choose a password per environment.
152
+ 3. Commit and push `.dotcrypt.json`, `.gitignore` and the `.enc` files. The card shows the exact
153
+ command.
154
+ 4. Give teammates the passwords privately (a password manager, not chat or the repo).
155
+
156
+ **You added or changed a variable:**
157
+
158
+ 1. Edit the `.env` file in your editor.
159
+ 2. The card now says **You have unshared changes**. Click **Share changes**
160
+ (CLI: `dotcrypt encrypt -e <env>`).
161
+ 3. Commit and push the `.enc` file. The card shows the command, for example:
162
+ `git add .env.development.enc && git commit -m "Update development secrets" && git push`.
163
+
164
+ **A teammate changed something:**
165
+
166
+ 1. `git pull`.
167
+ 2. The card says **New changes from your team**. Click **Get latest**
168
+ (CLI: `dotcrypt decrypt -e <env>`).
169
+
170
+ **A new teammate joins:** they clone the repo, run `npx dotcrypt ui`, click **Get secrets** on
171
+ each card, and enter the password you gave them.
172
+
173
+ **Both of you changed the same environment:** the card says **Changed on both sides**. Click
174
+ **Review** to see which variable names differ, then choose **Keep mine and share it** or **Use
175
+ team's version**. If you need parts of both, copy the missing variables into your file first,
176
+ then keep yours.
177
+
178
+ ### How dotcrypt knows what changed
179
+
180
+ `.enc` files are re-encrypted with a fresh random IV every time, so comparing timestamps or file
181
+ contents can't tell who changed what. Instead, every time you share, get, or compare an
182
+ environment, dotcrypt records fingerprints (SHA-256) of both files at that moment in
183
+ `.git/dotcrypt/sync-state.json`. That file is local to your clone and never committed. Later it
184
+ compares the current files with those fingerprints:
185
+
186
+ | State | Meaning | Next step |
187
+ | --- | --- | --- |
188
+ | Up to date | Neither file changed since your last sync. | Nothing. |
189
+ | You have unshared changes | Your `.env` changed. | Share, then commit and push. |
190
+ | New changes from your team | The `.enc` changed (e.g. after `git pull`). | Get latest. |
191
+ | Changed on both sides | Both changed. | Review and choose a version. |
192
+ | Not shared yet / Ready to download | Only one of the two files exists. | Share / Get secrets. |
193
+ | Not checked yet | No fingerprint yet (e.g. first run on an existing checkout). | Compare once with the password. |
194
+
195
+ Neither the dashboard nor `status` needs a password to show these states.
196
+
197
+ **Someone leaves the team / password leaked:**
198
+
199
+ ```bash
200
+ dotcrypt rotate-key -e production
201
+ git commit -am "Rotate production vault password" && git push
202
+ ```
203
+
204
+ > ⚠️ Rotating the password does **not** revoke secrets. Anyone who had the old password can still
205
+ > decrypt old versions of the `.enc` file from Git history. After a departure or leak, rotate the
206
+ > **underlying secrets** (API keys, DB passwords) too, then re-encrypt.
207
+
208
+ **Merge conflicts in `.enc` files:** encrypted files can't be merged textually. Resolve by taking
209
+ one side (`git checkout --theirs .env.x.enc`), running `decrypt`, re-applying your changes to the
210
+ plaintext file, and running `encrypt` again.
211
+
212
+ **CI usage:**
213
+
214
+ ```bash
215
+ DOTCRYPT_PASSWORD="$VAULT_PASSWORD_PRODUCTION" dotcrypt decrypt -e production --force
216
+ ```
217
+
218
+ ## Security architecture
219
+
220
+ ### Encrypted file format
221
+
222
+ Each `.enc` file is a JSON envelope:
223
+
224
+ ```json
225
+ {
226
+ "version": 1,
227
+ "algorithm": "aes-256-gcm",
228
+ "kdf": { "name": "scrypt", "N": 32768, "r": 8, "p": 1, "salt": "<base64, 32 bytes>" },
229
+ "iv": "<base64, 16 bytes>",
230
+ "metadata": {
231
+ "environment": "production",
232
+ "sourceFile": ".env.production",
233
+ "encryptedAt": "2026-11-07T06:00:00.000Z"
234
+ },
235
+ "authTag": "<base64, 16 bytes>",
236
+ "ciphertext": "<base64>"
237
+ }
238
+ ```
239
+
240
+ ### Cryptographic design
241
+
242
+ - **Cipher:** AES-256-GCM (authenticated encryption) via `node:crypto`, 128-bit auth tag
243
+ enforced with `authTagLength` on both encrypt and decrypt.
244
+ - **Key derivation:** `crypto.scryptSync` → 256-bit key. Default cost `N=2^15, r=8, p=1`
245
+ (~32 MiB, OWASP baseline). Passwords are Unicode NFC-normalized so the same password works on
246
+ every OS/keyboard.
247
+ - **Fresh randomness every run:** a new 32-byte salt and new 16-byte IV come from the CSPRNG
248
+ (`crypto.randomBytes`) on every `encrypt` and `rotate-key`. IVs are never fixed, reused, or
249
+ derived; because the key is also fresh per salt, (key, IV) reuse is cryptographically
250
+ negligible.
251
+ - **Authenticated header (AAD):** the format version, algorithm, all KDF parameters, salt, IV,
252
+ and metadata (environment, source file, timestamp) are bound to the ciphertext as GCM
253
+ Additional Authenticated Data. Any modification — including swapping `staging.enc` into
254
+ `production.enc` or editing the environment label — fails authentication.
255
+ - **Environment binding:** on decrypt, the authenticated `metadata.environment` must match the
256
+ requested environment.
257
+ - **Downgrade / DoS resistance:** KDF parameters read from files are bounds-checked (N must be a
258
+ power of two in `[2^14, 2^20]`, `r ∈ [8, 32]`, `p ∈ [1, 16]`, memory ≤ 256 MiB). Base64 fields,
259
+ salt/IV/tag lengths, and file sizes (5 MiB) are strictly validated before any crypto runs.
260
+ - **Uniform failure:** a wrong password and a tampered file produce the same error (GCM cannot and
261
+ should not distinguish them); no partial plaintext is ever written.
262
+ - **Key hygiene:** derived keys, password byte buffers and plaintext buffers are zeroed after use
263
+ (best-effort in a garbage-collected runtime).
264
+
265
+ ### Filesystem safety
266
+
267
+ - **Atomic writes:** output is written to an exclusive (`wx`) temp file, `fsync`ed, then renamed
268
+ over the target, so crashes never leave truncated `.env` or `.enc` files.
269
+ - **Restrictive permissions:** decrypted plaintext files are created with mode `0600`.
270
+ - **Path confinement:** all paths from `.dotcrypt.json` must resolve inside the project root.
271
+ - **Symlink protection:** the tool refuses to read or write through symlinks, or through
272
+ symlinked directories that resolve outside the project. A malicious repository therefore can't
273
+ make `decrypt` overwrite e.g. `~/.bashrc`, or make `encrypt` pull `~/.ssh/id_rsa` into a
274
+ committed file.
275
+ - **Git hygiene:** `init` appends `.env`, `.env.*` and `!.env.*.enc` (plus explicit entries for
276
+ custom paths) to `.gitignore` without touching existing rules. `encrypt`/`decrypt` warn loudly if
277
+ a plaintext file is tracked by Git or not ignored.
278
+ - **No secret output:** `diff` and the overwrite summaries print key names only; key names are
279
+ sanitized of control characters to prevent terminal escape injection.
280
+
281
+ ### Threat model & limitations
282
+
283
+ - Protects secrets **at rest in Git** (hosting provider, forks, leaked clones) against anyone
284
+ without the password.
285
+ - Does **not** protect against a compromised developer machine, or against someone who holds the
286
+ password. Old ciphertexts in Git history remain decryptable with old passwords.
287
+ - Security depends on password strength. Use long random passphrases from a password manager and a
288
+ separate password per environment.
289
+ - Not a replacement for a full secrets manager (per-user access control, audit logs, automatic
290
+ rotation). It is designed for small, trusted teams.
291
+
292
+ ## Web dashboard
293
+
294
+ ### Opening the dashboard
295
+
296
+ 1. Make sure you ran `npm run setup` (or `npm run build:ui`) once in this repository.
297
+ 2. In a terminal, go to the project whose env files you want to manage (the folder with
298
+ `.dotcrypt.json`, or any folder if you want to create one from the dashboard).
299
+ 3. Run:
300
+
301
+ ```bash
302
+ dotcrypt ui # random free port, opens your browser
303
+ dotcrypt ui --port 4600 # fixed port
304
+ dotcrypt ui --no-open # just print the URL
305
+ ```
306
+
307
+ 4. The terminal prints a URL like `http://127.0.0.1:53412/?token=3f9c…`. Your browser opens it
308
+ automatically; if it does not, copy the **full** URL (including `?token=`) into your browser.
309
+ 5. Leave the terminal open while you use the dashboard. Press **Ctrl+C** to stop it.
310
+
311
+ Each run creates a new token, so old URLs stop working after a restart. If the page says
312
+ "Session token missing or expired", open the URL from the current run again. If it says the
313
+ dashboard assets are not built, run `npm run build:ui` in this repository.
314
+
315
+ ### What it does
316
+
317
+ A local dashboard built with React, [shadcn/ui](https://ui.shadcn.com) (Base UI primitives) and
318
+ Tailwind. It shows:
319
+
320
+ - a short "How sharing works" guide (hide it, and reopen it from **How it works** in the header)
321
+ - one card per environment with a plain-language state (see [Team workflow](#how-dotcrypt-knows-what-changed))
322
+ and a single button for the next step: **Share changes**, **Get latest**, **Review**, ...
323
+ - the Git command to send a shared `.enc` file to your team (with a copy button), and a hint when a
324
+ teammate pushed something newer
325
+ - Git safety: whether plaintext files are ignored or (dangerously) tracked, and a one-click fix for
326
+ missing `.gitignore` rules
327
+ - an activity log for the session
328
+
329
+ Less common actions (compare, force share or get, change password, make default) are in each
330
+ card's **⋯** menu. Changes that overwrite a file show the variable names involved and ask for
331
+ confirmation, just like the CLI. The dashboard refreshes every 5 seconds, so changes made in the terminal or by `git pull`
332
+ show up automatically.
333
+
334
+ ### Dashboard security model
335
+
336
+ - **Loopback only.** The server binds to `127.0.0.1` and never to `0.0.0.0`.
337
+ - **Per-run secret token.** A random 256-bit token is generated on every start and required as a
338
+ bearer token on every API call, compared in constant time. The browser moves it from the URL into
339
+ `sessionStorage` and removes it from the address bar. Do not share the printed URL.
340
+ - **DNS-rebinding and CSRF protection.** Requests whose `Host` is not `127.0.0.1:<port>` or
341
+ `localhost:<port>` are rejected (421). Writes require `Content-Type: application/json`, a matching
342
+ `Origin`, and a same-origin `Sec-Fetch-Site`.
343
+ - **Strict headers.** A restrictive CSP (`script-src 'self'`, `frame-ancestors 'none'`, …),
344
+ `X-Frame-Options: DENY`, `nosniff`, `no-referrer`, and `Cache-Control: no-store` on API responses.
345
+ - **No secret values leave the server.** The API returns metadata and key names only. Passwords are
346
+ sent per request, held only in the tab's memory (the **Lock** button clears them), and never
347
+ stored in `localStorage` or on disk. Request bodies are capped at 64 KB.
348
+
349
+ ### Developing the dashboard
350
+
351
+ ```bash
352
+ # terminal 1, inside any project with a vault
353
+ dotcrypt ui --port 4600 --no-open # note the token it prints
354
+ # terminal 2, in this repository
355
+ VAULT_API_PORT=4600 npm run dev:ui # Vite on http://localhost:5173, /api is proxied
356
+ # open http://localhost:5173/?token=<token>
357
+ ```
358
+
359
+ UI code lives in `ui/src` (`App.tsx`, `components/`, `lib/api.ts`). Add more shadcn components with
360
+ `npx shadcn@latest add <component>` from the `ui/` directory.
361
+
362
+ ## Development
363
+
364
+ ```bash
365
+ npm install
366
+ npm test
367
+ ```
368
+
369
+ Source layout: `src/crypto.js` (envelope format and AES-256-GCM/scrypt), `src/config.js`
370
+ (`.dotcrypt.json`), `src/utils.js` (filesystem and Git safety), `src/vault.js` (operations shared
371
+ by the CLI and dashboard), `src/server.js` (dashboard server), `bin/cli.js` (Commander CLI) and
372
+ `ui/` (dashboard).
373
+
374
+ ## License
375
+
376
+ MIT