dotcrypt 0.0.0-stage → 1.0.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,22 @@
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
+ ## [1.0.0] - 2026-11-10
10
+
11
+ ### Added
12
+
13
+ - `init`, `encrypt`, `decrypt`, `rotate-key` and `diff` commands for AES-256-GCM (scrypt) encrypted
14
+ `.env` files that are safe to commit to Git.
15
+ - `ui` command: a local, token-protected web dashboard bound to `127.0.0.1`.
16
+ - Overwrite protection with key-level diffs (values are never printed).
17
+ - `encrypt --replace` to deliberately discard a vault file that is unreadable, belongs to another
18
+ environment, or uses a different password.
19
+ - Passwords via interactive prompt, `DOTCRYPT_PASSWORD` / `DOTCRYPT_NEW_PASSWORD`, or flags.
20
+
21
+ [Unreleased]: https://github.com/zubayerhimel/dotcrypt/compare/v1.0.0...HEAD
22
+ [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,347 @@
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
+ | `init [-n name] [--envs a,b,c] [-d env] [-y] [-f]` | Create `.dotcrypt.json` and add ignore rules for plaintext env files to `.gitignore`. |
64
+ | `encrypt [-e env] [-p password] [-f] [--replace]` | Encrypt the mapped local file (e.g. `.env.staging`) into its `.enc` file. |
65
+ | `decrypt [-e env] [-p password] [-f]` | Decrypt the `.enc` file into the mapped local file. |
66
+ | `rotate-key [-e env] [-o old] [-n new]` | Re-encrypt with a new password, a fresh salt and a fresh IV. |
67
+ | `diff [-e env] [-p password] [--exit-code]` | Show **key names** that were added / removed / changed. Values are never printed. |
68
+ | `ui [--port n] [--no-open]` | Open the local web dashboard (see below). |
69
+
70
+ If `-e` is omitted you are prompted to pick an environment (non-interactive runs fall back to
71
+ `defaultEnvironment`).
72
+
73
+ ### Supplying passwords
74
+
75
+ Passwords are resolved in this order:
76
+
77
+ 1. CLI flag (`-p`, `-o`, `-n`) — works, but prints a warning because flags leak into shell
78
+ history and process listings.
79
+ 2. Environment variable — `DOTCRYPT_PASSWORD` (and `DOTCRYPT_NEW_PASSWORD` for the new
80
+ password in `rotate-key`). Recommended for CI.
81
+ 3. Interactive masked prompt (recommended for humans). New passwords must be at least 12
82
+ characters and are asked twice.
83
+
84
+ ### Overwrite protection
85
+
86
+ - `decrypt` never silently replaces a local file that differs from the vault: it shows which keys
87
+ would be lost/changed and asks for confirmation. Use `--force` to skip the prompt.
88
+ - `encrypt` shows a key-level summary of what will change in the vault and asks before
89
+ overwriting. If the files are identical, nothing is written (no Git churn).
90
+ - `encrypt` **verifies your password against the existing `.enc` file**. This prevents a
91
+ teammate from accidentally re-encrypting a shared vault with a different password. Change
92
+ passwords with `rotate-key`. To deliberately discard a vault file that is unreadable, belongs to
93
+ another environment, or uses a different password, pass `--replace` (`--force` alone never does
94
+ this). When the password was typed at a prompt, you must re-enter it before the vault is replaced.
95
+ - `init` refuses to overwrite an existing `.dotcrypt.json` without confirmation or `--force`.
96
+ - Non-interactive sessions (CI, pipes) never prompt; they fail safely unless `--force` is given.
97
+
98
+ ### Exit codes
99
+
100
+ | Code | Meaning |
101
+ | --- | --- |
102
+ | `0` | Success |
103
+ | `1` | General error, aborted, or (`diff --exit-code`) differences found |
104
+ | `2` | Wrong password or corrupted / tampered encrypted file |
105
+ | `130` | Cancelled at a prompt (Ctrl+C) |
106
+
107
+ ## `.dotcrypt.json`
108
+
109
+ ```json
110
+ {
111
+ "version": 1,
112
+ "vault": {
113
+ "name": "my-app",
114
+ "createdAt": "2026-11-07T06:00:00.000Z",
115
+ "cipher": "aes-256-gcm",
116
+ "kdf": "scrypt"
117
+ },
118
+ "defaultEnvironment": "development",
119
+ "environments": {
120
+ "development": { "file": ".env.development", "encryptedFile": ".env.development.enc" },
121
+ "staging": { "file": ".env.staging", "encryptedFile": ".env.staging.enc" },
122
+ "production": { "file": ".env.production", "encryptedFile": ".env.production.enc" }
123
+ }
124
+ }
125
+ ```
126
+
127
+ You may edit mappings by hand (e.g. `"file": "apps/api/.env"`). Validation rules:
128
+
129
+ - Environment names: 1–64 chars of letters, digits, `-`, `_`.
130
+ - Paths must be relative and stay inside the project (no absolute paths, no `..` escapes).
131
+ - `encryptedFile` must end in `.enc`; `file` must not.
132
+ - No two mappings may share a path (compared case-insensitively).
133
+
134
+ Custom plaintext paths that the default `.env` / `.env.*` rules don't cover are added to
135
+ `.gitignore` explicitly the next time you run `init`.
136
+
137
+ ## Team workflow
138
+
139
+ **Project owner (once):**
140
+
141
+ ```bash
142
+ dotcrypt init # creates .dotcrypt.json, updates .gitignore
143
+ # create .env.development, .env.staging, ...
144
+ dotcrypt encrypt -e development # choose a strong password
145
+ dotcrypt encrypt -e production # use a *different* password per environment
146
+ git add .dotcrypt.json .gitignore *.enc
147
+ git commit -m "Add encrypted environments"
148
+ ```
149
+
150
+ Share each environment's password **out of band** (password manager, not chat or the repo).
151
+
152
+ **Teammate on a new machine:**
153
+
154
+ ```bash
155
+ git pull
156
+ dotcrypt decrypt -e development
157
+ ```
158
+
159
+ **Changing a secret:**
160
+
161
+ ```bash
162
+ git pull # always start from the latest vault
163
+ dotcrypt diff -e development # see what differs before acting
164
+ # edit .env.development
165
+ dotcrypt encrypt -e development
166
+ git add .env.development.enc && git commit -m "Update dev env" && git push
167
+ ```
168
+
169
+ **Someone leaves the team / password leaked:**
170
+
171
+ ```bash
172
+ dotcrypt rotate-key -e production
173
+ git commit -am "Rotate production vault password" && git push
174
+ ```
175
+
176
+ > ⚠️ Rotating the password does **not** revoke secrets. Anyone who had the old password can still
177
+ > decrypt old versions of the `.enc` file from Git history. After a departure or leak, rotate the
178
+ > **underlying secrets** (API keys, DB passwords) too, then re-encrypt.
179
+
180
+ **Merge conflicts in `.enc` files:** encrypted files can't be merged textually. Resolve by taking
181
+ one side (`git checkout --theirs .env.x.enc`), running `decrypt`, re-applying your changes to the
182
+ plaintext file, and running `encrypt` again.
183
+
184
+ **CI usage:**
185
+
186
+ ```bash
187
+ DOTCRYPT_PASSWORD="$VAULT_PASSWORD_PRODUCTION" dotcrypt decrypt -e production --force
188
+ ```
189
+
190
+ ## Security architecture
191
+
192
+ ### Encrypted file format
193
+
194
+ Each `.enc` file is a JSON envelope:
195
+
196
+ ```json
197
+ {
198
+ "version": 1,
199
+ "algorithm": "aes-256-gcm",
200
+ "kdf": { "name": "scrypt", "N": 32768, "r": 8, "p": 1, "salt": "<base64, 32 bytes>" },
201
+ "iv": "<base64, 16 bytes>",
202
+ "metadata": {
203
+ "environment": "production",
204
+ "sourceFile": ".env.production",
205
+ "encryptedAt": "2026-11-07T06:00:00.000Z"
206
+ },
207
+ "authTag": "<base64, 16 bytes>",
208
+ "ciphertext": "<base64>"
209
+ }
210
+ ```
211
+
212
+ ### Cryptographic design
213
+
214
+ - **Cipher:** AES-256-GCM (authenticated encryption) via `node:crypto`, 128-bit auth tag
215
+ enforced with `authTagLength` on both encrypt and decrypt.
216
+ - **Key derivation:** `crypto.scryptSync` → 256-bit key. Default cost `N=2^15, r=8, p=1`
217
+ (~32 MiB, OWASP baseline). Passwords are Unicode NFC-normalized so the same password works on
218
+ every OS/keyboard.
219
+ - **Fresh randomness every run:** a new 32-byte salt and new 16-byte IV come from the CSPRNG
220
+ (`crypto.randomBytes`) on every `encrypt` and `rotate-key`. IVs are never fixed, reused, or
221
+ derived; because the key is also fresh per salt, (key, IV) reuse is cryptographically
222
+ negligible.
223
+ - **Authenticated header (AAD):** the format version, algorithm, all KDF parameters, salt, IV,
224
+ and metadata (environment, source file, timestamp) are bound to the ciphertext as GCM
225
+ Additional Authenticated Data. Any modification — including swapping `staging.enc` into
226
+ `production.enc` or editing the environment label — fails authentication.
227
+ - **Environment binding:** on decrypt, the authenticated `metadata.environment` must match the
228
+ requested environment.
229
+ - **Downgrade / DoS resistance:** KDF parameters read from files are bounds-checked (N must be a
230
+ power of two in `[2^14, 2^20]`, `r ∈ [8, 32]`, `p ∈ [1, 16]`, memory ≤ 256 MiB). Base64 fields,
231
+ salt/IV/tag lengths, and file sizes (5 MiB) are strictly validated before any crypto runs.
232
+ - **Uniform failure:** a wrong password and a tampered file produce the same error (GCM cannot and
233
+ should not distinguish them); no partial plaintext is ever written.
234
+ - **Key hygiene:** derived keys, password byte buffers and plaintext buffers are zeroed after use
235
+ (best-effort in a garbage-collected runtime).
236
+
237
+ ### Filesystem safety
238
+
239
+ - **Atomic writes:** output is written to an exclusive (`wx`) temp file, `fsync`ed, then renamed
240
+ over the target, so crashes never leave truncated `.env` or `.enc` files.
241
+ - **Restrictive permissions:** decrypted plaintext files are created with mode `0600`.
242
+ - **Path confinement:** all paths from `.dotcrypt.json` must resolve inside the project root.
243
+ - **Symlink protection:** the tool refuses to read or write through symlinks, or through
244
+ symlinked directories that resolve outside the project. A malicious repository therefore can't
245
+ make `decrypt` overwrite e.g. `~/.bashrc`, or make `encrypt` pull `~/.ssh/id_rsa` into a
246
+ committed file.
247
+ - **Git hygiene:** `init` appends `.env`, `.env.*` and `!.env.*.enc` (plus explicit entries for
248
+ custom paths) to `.gitignore` without touching existing rules. `encrypt`/`decrypt` warn loudly if
249
+ a plaintext file is tracked by Git or not ignored.
250
+ - **No secret output:** `diff` and the overwrite summaries print key names only; key names are
251
+ sanitized of control characters to prevent terminal escape injection.
252
+
253
+ ### Threat model & limitations
254
+
255
+ - Protects secrets **at rest in Git** (hosting provider, forks, leaked clones) against anyone
256
+ without the password.
257
+ - Does **not** protect against a compromised developer machine, or against someone who holds the
258
+ password. Old ciphertexts in Git history remain decryptable with old passwords.
259
+ - Security depends on password strength. Use long random passphrases from a password manager and a
260
+ separate password per environment.
261
+ - Not a replacement for a full secrets manager (per-user access control, audit logs, automatic
262
+ rotation). It is designed for small, trusted teams.
263
+
264
+ ## Web dashboard
265
+
266
+ ### Opening the dashboard
267
+
268
+ 1. Make sure you ran `npm run setup` (or `npm run build:ui`) once in this repository.
269
+ 2. In a terminal, go to the project whose env files you want to manage (the folder with
270
+ `.dotcrypt.json`, or any folder if you want to create one from the dashboard).
271
+ 3. Run:
272
+
273
+ ```bash
274
+ dotcrypt ui # random free port, opens your browser
275
+ dotcrypt ui --port 4600 # fixed port
276
+ dotcrypt ui --no-open # just print the URL
277
+ ```
278
+
279
+ 4. The terminal prints a URL like `http://127.0.0.1:53412/?token=3f9c…`. Your browser opens it
280
+ automatically; if it does not, copy the **full** URL (including `?token=`) into your browser.
281
+ 5. Leave the terminal open while you use the dashboard. Press **Ctrl+C** to stop it.
282
+
283
+ Each run creates a new token, so old URLs stop working after a restart. If the page says
284
+ "Session token missing or expired", open the URL from the current run again. If it says the
285
+ dashboard assets are not built, run `npm run build:ui` in this repository.
286
+
287
+ ### What it does
288
+
289
+ A local dashboard built with React, [shadcn/ui](https://ui.shadcn.com) (Base UI primitives) and
290
+ Tailwind. It shows:
291
+
292
+ - every environment in `.dotcrypt.json`: local file and vault file status, sizes, last-modified /
293
+ last-encrypted times, and the default environment
294
+ - a health badge per environment (Synced, Local changed, Not encrypted, Not decrypted, Vault
295
+ unreadable, Plaintext tracked)
296
+ - Git safety: whether plaintext files are ignored or (dangerously) tracked, whether `.enc` files are
297
+ committed, and a one-click fix for missing `.gitignore` rules
298
+ - an activity log for the session
299
+
300
+ You can initialize a vault, add environments, set the default, and run encrypt, decrypt, diff and
301
+ rotate-key from the dashboard. Overwrites show the key-name diff and need confirmation, just like
302
+ the CLI. The dashboard refreshes every 5 seconds, so changes made in the terminal or by `git pull`
303
+ show up automatically.
304
+
305
+ ### Dashboard security model
306
+
307
+ - **Loopback only.** The server binds to `127.0.0.1` and never to `0.0.0.0`.
308
+ - **Per-run secret token.** A random 256-bit token is generated on every start and required as a
309
+ bearer token on every API call, compared in constant time. The browser moves it from the URL into
310
+ `sessionStorage` and removes it from the address bar. Do not share the printed URL.
311
+ - **DNS-rebinding and CSRF protection.** Requests whose `Host` is not `127.0.0.1:<port>` or
312
+ `localhost:<port>` are rejected (421). Writes require `Content-Type: application/json`, a matching
313
+ `Origin`, and a same-origin `Sec-Fetch-Site`.
314
+ - **Strict headers.** A restrictive CSP (`script-src 'self'`, `frame-ancestors 'none'`, …),
315
+ `X-Frame-Options: DENY`, `nosniff`, `no-referrer`, and `Cache-Control: no-store` on API responses.
316
+ - **No secret values leave the server.** The API returns metadata and key names only. Passwords are
317
+ sent per request, held only in the tab's memory (the **Lock** button clears them), and never
318
+ stored in `localStorage` or on disk. Request bodies are capped at 64 KB.
319
+
320
+ ### Developing the dashboard
321
+
322
+ ```bash
323
+ # terminal 1, inside any project with a vault
324
+ dotcrypt ui --port 4600 --no-open # note the token it prints
325
+ # terminal 2, in this repository
326
+ VAULT_API_PORT=4600 npm run dev:ui # Vite on http://localhost:5173, /api is proxied
327
+ # open http://localhost:5173/?token=<token>
328
+ ```
329
+
330
+ UI code lives in `ui/src` (`App.tsx`, `components/`, `lib/api.ts`). Add more shadcn components with
331
+ `npx shadcn@latest add <component>` from the `ui/` directory.
332
+
333
+ ## Development
334
+
335
+ ```bash
336
+ npm install
337
+ npm test
338
+ ```
339
+
340
+ Source layout: `src/crypto.js` (envelope format and AES-256-GCM/scrypt), `src/config.js`
341
+ (`.dotcrypt.json`), `src/utils.js` (filesystem and Git safety), `src/vault.js` (operations shared
342
+ by the CLI and dashboard), `src/server.js` (dashboard server), `bin/cli.js` (Commander CLI) and
343
+ `ui/` (dashboard).
344
+
345
+ ## License
346
+
347
+ MIT