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 +40 -0
- package/LICENSE +21 -0
- package/README.md +375 -2
- package/bin/cli.js +556 -0
- package/package.json +59 -4
- package/src/config.js +167 -0
- package/src/crypto.js +259 -0
- package/src/server.js +321 -0
- package/src/sync.js +100 -0
- package/src/utils.js +283 -0
- package/src/vault.js +419 -0
- package/ui/README.md +4 -0
- package/ui/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/ui/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/ui/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/ui/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/ui/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/ui/dist/assets/index-BlKnGv6b.js +9 -0
- package/ui/dist/assets/index-DL_3iL0a.css +2 -0
- package/ui/dist/index.html +14 -0
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
|
-
#
|
|
1
|
+
# dotcrypt
|
|
2
2
|
|
|
3
|
-
|
|
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
|