@sharedenv/cli 1.1.1 → 2.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/README.md CHANGED
@@ -13,7 +13,8 @@ machine: sharedenv itself never sees a single secret.
13
13
  ```bash
14
14
  npm install -g @sharedenv/cli
15
15
  sharedenv login
16
- sharedenv run prod -- npm start
16
+ sharedenv link # once per project: pick its vault
17
+ sharedenv run -- npm start # your app, with the vault's secrets
17
18
  ```
18
19
 
19
20
  ## Why teams use it
@@ -31,8 +32,8 @@ sharedenv run prod -- npm start
31
32
 
32
33
  ## Get started in two minutes
33
34
 
34
- 1. **Create your account** on [sharedenv.com](https://sharedenv.com) and your first vault.
35
- It is free to start, no credit card.
35
+ 1. **Create your account** on [sharedenv.com](https://sharedenv.com). It is free to start,
36
+ no credit card.
36
37
  2. **Install the CLI** (Node.js 20.19 or later):
37
38
  ```bash
38
39
  npm install -g @sharedenv/cli
@@ -42,20 +43,61 @@ sharedenv run prod -- npm start
42
43
  ```bash
43
44
  sharedenv login
44
45
  ```
45
- 4. **Use your vaults:**
46
+ 4. **Link your project to a vault**, from the project's directory:
46
47
  ```bash
47
- sharedenv list # your vaults
48
- sharedenv run prod -- npm start # your app, with the prod secrets
48
+ sharedenv create my-app-dev # a new vault, or pick one you already have:
49
+ sharedenv link # lists your vaults, you choose
49
50
  ```
51
+ 5. **Use it:**
52
+ ```bash
53
+ sharedenv set DATABASE_URL=postgres://localhost/app
54
+ sharedenv run -- npm start
55
+ ```
56
+
57
+ ## Which vault a command works on
58
+
59
+ You may have many vaults: your own, the ones people shared with you, the ones your teams
60
+ reach. A command never guesses which one you mean.
61
+
62
+ - **In a project**, `sharedenv link` writes `.sharedenv.json` in its directory. It holds the
63
+ vault's id and nothing else, no secret: commit it, and your whole team works on the same
64
+ vault. Every command run in that directory, or below it, uses that vault.
65
+ - **Anywhere**, `-v <name or id>` names the vault: `sharedenv get API_KEY -v staging`.
66
+ - **A name two of your vaults share is refused**, with both listed (id, owner, teams): give
67
+ the id instead. `sharedenv create` refuses a name you already have.
68
+ - **Every command says which vault it used**, on stderr: a link in a repository you cloned
69
+ might not name the vault you expect.
70
+
71
+ ```bash
72
+ sharedenv vaults # every vault you can open: id, your access, owner, teams
73
+ sharedenv show # this vault: members, teams, names of its keys
74
+ ```
75
+
76
+ `vaults` and `show` never ask for your master password: vault and key names are stored in
77
+ clear, only values are encrypted.
50
78
 
51
79
  ## Everyday use
52
80
 
81
+ ### Keys
82
+
83
+ ```bash
84
+ sharedenv set API_KEY=sk_test_1 DEBUG=1 # add or replace
85
+ sharedenv set TLS_KEY < key.pem # value from stdin
86
+ sharedenv set STRIPE_KEY # value asked, not echoed
87
+ sharedenv get API_KEY # one value, on stdout
88
+ sharedenv unset DEBUG # remove
89
+ ```
90
+
91
+ A key given without a value has its value asked without echo, or read from stdin: it lands
92
+ neither in your shell history nor in the process list. `get` prints the value as is, with
93
+ nothing added when the output is a pipe or a file (`export API_KEY=$(sharedenv get API_KEY)`).
94
+
53
95
  ### Run anything with its secrets
54
96
 
55
97
  ```bash
56
- sharedenv run prod -- npm start
57
- sharedenv run staging -- docker compose up
58
- sharedenv run prod -- python manage.py migrate
98
+ sharedenv run -- npm start
99
+ sharedenv run -v staging -- docker compose up
100
+ sharedenv run -v prod -- python manage.py migrate
59
101
  ```
60
102
 
61
103
  The secrets reach the command as environment variables, in memory. The command's exit code
@@ -67,9 +109,9 @@ replaced behind your back: add `--override-env` when you want the vault's value.
67
109
  ### Get your secrets as a file, or as JSON
68
110
 
69
111
  ```bash
70
- sharedenv pull prod # dotenv, on stdout
71
- sharedenv pull prod --out .env # the same, in a file only you can read
72
- sharedenv pull prod --format json # JSON, for scripts and programs
112
+ sharedenv pull # dotenv, on stdout
113
+ sharedenv pull --out .env # the same, in a file only you can read
114
+ sharedenv pull --format json # JSON, for scripts and programs
73
115
  ```
74
116
 
75
117
  stdout carries the secrets and nothing else (questions and messages go to stderr), so
@@ -78,7 +120,7 @@ ask for your master password: set `SHAREDENV_PASSWORD`.
78
120
 
79
121
  #### dotenv (the default)
80
122
 
81
- For a vault holding five secrets, `sharedenv pull prod` prints:
123
+ For a vault holding five secrets, `sharedenv pull` prints:
82
124
 
83
125
  ```dotenv
84
126
  APP_NAME='Acme billing'
@@ -119,12 +161,12 @@ One exception: `docker run --env-file` reads values literally (it would keep the
119
161
  and refuses multi-line values. Hand the variables to Docker through `run` instead:
120
162
 
121
163
  ```bash
122
- sharedenv run prod -- docker run -e DATABASE_URL -e STRIPE_KEY my-app
164
+ sharedenv run -- docker run -e DATABASE_URL -e STRIPE_KEY my-app
123
165
  ```
124
166
 
125
167
  #### JSON
126
168
 
127
- `sharedenv pull prod --format json` prints one object, names sorted, every value a string:
169
+ `sharedenv pull --format json` prints one object, names sorted, every value a string:
128
170
 
129
171
  ```json
130
172
  {
@@ -136,12 +178,6 @@ sharedenv run prod -- docker run -e DATABASE_URL -e STRIPE_KEY my-app
136
178
  }
137
179
  ```
138
180
 
139
- One value, from a shell:
140
-
141
- ```bash
142
- sharedenv pull prod --format json | jq -r .DATABASE_URL
143
- ```
144
-
145
181
  From a program:
146
182
 
147
183
  ```js
@@ -149,7 +185,7 @@ From a program:
149
185
  import { execFileSync } from 'node:child_process';
150
186
 
151
187
  const secrets = JSON.parse(
152
- execFileSync('sharedenv', ['pull', 'prod', '--format', 'json'], { encoding: 'utf8' })
188
+ execFileSync('sharedenv', ['pull', '-v', 'prod', '--format', 'json'], { encoding: 'utf8' })
153
189
  );
154
190
  console.log(secrets.DATABASE_URL);
155
191
  ```
@@ -161,7 +197,7 @@ import subprocess
161
197
 
162
198
  secrets = json.loads(
163
199
  subprocess.run(
164
- ["sharedenv", "pull", "prod", "--format", "json"],
200
+ ["sharedenv", "pull", "-v", "prod", "--format", "json"],
165
201
  check=True, capture_output=True, text=True,
166
202
  ).stdout
167
203
  )
@@ -171,19 +207,33 @@ print(secrets["DATABASE_URL"])
171
207
  If the command fails (wrong password, unknown vault, no network), it exits with a non-zero
172
208
  code and says why on stderr: `execFileSync` throws, and `check=True` raises.
173
209
 
210
+ ### Share a vault
211
+
212
+ ```bash
213
+ sharedenv share alice@example.com # read access
214
+ sharedenv share alice@example.com --write # or --admin
215
+ sharedenv share --team backend # a team you are a member of
216
+ sharedenv teams # your organizations and their teams
217
+ ```
218
+
219
+ Sharing with a person shows the fingerprint of their key: compare it with them over another
220
+ channel (a call, a chat) before you say yes, they read theirs with `sharedenv fingerprint`.
221
+ The share then waits for their answer. Removing someone's access means changing the vault's
222
+ key and re-encrypting every secret: that is done in the web app, as is managing teams and
223
+ organizations.
224
+
174
225
  ### Accept what your team shares with you
175
226
 
176
227
  When a teammate shares a vault with you, it waits for your answer:
177
228
 
178
229
  ```bash
179
- sharedenv list # the shares waiting for you, with who sent them
230
+ sharedenv vaults # the shares waiting for you, with who sent them
180
231
  sharedenv accept staging # shows the sender's key fingerprint, then asks
181
232
  sharedenv decline staging
182
233
  ```
183
234
 
184
- Before you accept, compare the fingerprint with the sender over another channel (a call,
185
- a chat). It proves the share really comes from them. They can check yours the same way:
186
- `sharedenv fingerprint`.
235
+ Before you accept, compare the fingerprint with the sender over another channel. It proves
236
+ the share really comes from them.
187
237
 
188
238
  ### Sign out
189
239
 
@@ -194,7 +244,8 @@ sharedenv logout # closes the session on our side too
194
244
  ## In CI/CD
195
245
 
196
246
  The CLI asks nothing when there is no terminal: give it what it would ask for, from your
197
- CI's secret store.
247
+ CI's secret store. Name the vault by its id (`sharedenv vaults` shows it), or commit the
248
+ project's `.sharedenv.json`.
198
249
 
199
250
  **GitHub Actions**
200
251
 
@@ -204,7 +255,7 @@ CI's secret store.
204
255
  env:
205
256
  SHAREDENV_IDENTIFIER: ${{ secrets.SHAREDENV_IDENTIFIER }}
206
257
  SHAREDENV_PASSWORD: ${{ secrets.SHAREDENV_PASSWORD }}
207
- - run: sharedenv run prod -- ./deploy.sh
258
+ - run: sharedenv run -v 6721f0c2a8e4b1d3c5f70912 -- ./deploy.sh
208
259
  env:
209
260
  SHAREDENV_PASSWORD: ${{ secrets.SHAREDENV_PASSWORD }}
210
261
  ```
@@ -216,19 +267,19 @@ deploy:
216
267
  script:
217
268
  - npm install -g @sharedenv/cli
218
269
  - sharedenv login
219
- - sharedenv run prod -- ./deploy.sh
220
- # SHAREDENV_IDENTIFIER and SHAREDENV_PASSWORD: masked CI/CD variables
270
+ - sharedenv run -v "$SHAREDENV_VAULT" -- ./deploy.sh
271
+ # SHAREDENV_IDENTIFIER, SHAREDENV_PASSWORD and SHAREDENV_VAULT: masked CI/CD variables
221
272
  ```
222
273
 
223
274
  **Docker** (the secrets are fetched when the container starts, never baked into the image)
224
275
 
225
276
  ```dockerfile
226
277
  RUN npm install -g @sharedenv/cli
227
- CMD ["sh", "-c", "sharedenv login && exec sharedenv run prod -- node server.js"]
278
+ CMD ["sh", "-c", "sharedenv login && exec sharedenv run -v \"$SHAREDENV_VAULT\" -- node server.js"]
228
279
  ```
229
280
 
230
281
  ```bash
231
- docker run -e SHAREDENV_IDENTIFIER -e SHAREDENV_PASSWORD my-app
282
+ docker run -e SHAREDENV_IDENTIFIER -e SHAREDENV_PASSWORD -e SHAREDENV_VAULT my-app
232
283
  ```
233
284
 
234
285
  Good practice for pipelines:
@@ -246,13 +297,16 @@ Good practice for pipelines:
246
297
  - **Your password and your private key never leave your machine.** Signing in sends a
247
298
  verifier derived from your password, never the password. Your private key is unlocked in
248
299
  memory for the command, and never written anywhere.
249
- - **You know who shares with you.** A share opens only if it was sealed by its sender, and
250
- the CLI remembers the keys it has seen: if someone's key ever changes, it stops and asks.
300
+ - **You know who shares with you, and whom you share with.** A share opens only if it was
301
+ sealed by its sender, and the CLI remembers the keys it has seen: if someone's key ever
302
+ changes, it stops and asks.
251
303
  - **A key cannot be swapped in our database without a trace.** Every public key the server
252
304
  hands out is first written to a log that only grows, each entry chained to the previous
253
- one by its hash. The CLI trusts a key only if it is the last one logged for its owner,
254
- warns you when a key that is not yours is logged under your name, and refuses a log that
255
- rewrites what it already checked.
305
+ one by its hash. The CLI seals to a key, or trusts its seal, only if it is the last one
306
+ logged for its owner; it warns you when a key that is not yours is logged under your
307
+ name, or under the name of a team whose key you hold, and refuses a log that rewrites
308
+ what it already checked. A warning is repeated at every unlock until you acknowledge it
309
+ with `sharedenv log --ack`.
256
310
  - **A vault cannot take over your machine.** Anyone who can edit a vault chooses its
257
311
  values, so `run` holds back variables that would make a program run code or reroute its
258
312
  traffic (`LD_*`, `PATH`, `NODE_OPTIONS`, proxies, custom certificates...) unless you pass
@@ -263,8 +317,7 @@ Good practice for pipelines:
263
317
  What it does not protect against, said plainly:
264
318
 
265
319
  - **Names.** Vault names and secret names (like `DATABASE_URL`) are stored in clear, so
266
- that they can be listed and organized; values, and the URL and username that go with
267
- them, are encrypted. Never put a secret in a name.
320
+ that they can be listed and organized; values are encrypted. Never put a secret in a name.
268
321
  - **A swap is caught, not prevented.** Someone who can write to our database could still
269
322
  log a key of their own: the log keeps the trace, and its owner is warned the next time
270
323
  they unlock. Someone who controls our servers themselves could show you a different log.
@@ -277,18 +330,29 @@ What it does not protect against, said plainly:
277
330
 
278
331
  ## Reference
279
332
 
280
- | Command | What it does |
281
- | --------------------------------------------------------------------- | ---------------------------------------------------------- |
282
- | `sharedenv login [--api <url>] [--identifier <id>]` | Sign in. Keeps the session, nothing else. |
283
- | `sharedenv list` | Your vaults, then the shares waiting for your answer. |
284
- | `sharedenv run <vault> -- <command>` | Run a command with the vault's secrets in its environment. |
285
- | `sharedenv pull <vault> [--out <file>] [--format dotenv\|json]` | Print or write the secrets. |
286
- | `sharedenv accept <vault> [--team <id>] [--yes] [--fingerprint <fp>]` | Accept a share after checking who sealed it. |
287
- | `sharedenv decline <vault> [--team <id>]` | Decline a share. |
288
- | `sharedenv fingerprint` | Your key fingerprint, to read to a teammate. |
289
- | `sharedenv logout` | Close the session, on our side and on this machine. |
290
-
291
- `<vault>` is a vault's name or id. `sharedenv help <command>` lists every option.
333
+ | Command | What it does |
334
+ | ------------------------------------------------------- | ----------------------------------------------------------- |
335
+ | `sharedenv login [--api <url>] [--identifier <id>]` | Sign in. Keeps the session, nothing else. |
336
+ | `sharedenv logout` | Close the session, on our side and on this machine. |
337
+ | `sharedenv vaults` | Your vaults, then the shares waiting for your answer. |
338
+ | `sharedenv create <name>` | Create a vault (prints its id). |
339
+ | `sharedenv link [<vault>]` | Link this directory to a vault (`.sharedenv.json`). |
340
+ | `sharedenv show` | The vault: owner, members, teams, names of its keys. |
341
+ | `sharedenv get <KEY>` | Print one value. |
342
+ | `sharedenv set <KEY>=<value>... [<KEY>]` | Add or replace keys. |
343
+ | `sharedenv unset <KEY>...` | Remove keys. |
344
+ | `sharedenv run -- <command>` | Run a command with the vault's secrets in its environment. |
345
+ | `sharedenv pull [--out <file>] [--format dotenv\|json]` | Print or write every secret. |
346
+ | `sharedenv share <email> [--write\|--admin]` | Share the vault with a person. |
347
+ | `sharedenv share --team <team> [--write\|--admin]` | Share the vault with one of your teams. |
348
+ | `sharedenv accept <vault> [--team <team>]` | Accept a share after checking who sealed it. |
349
+ | `sharedenv decline <vault> [--team <team>]` | Decline a share. |
350
+ | `sharedenv teams` | Your organizations and their teams. |
351
+ | `sharedenv fingerprint` | Your key fingerprint, to read to a teammate. |
352
+ | `sharedenv log [--ack]` | The public key log warnings kept here, or acknowledge them. |
353
+
354
+ Commands that work on a vault take `-v <name or id>`, or use the vault the current
355
+ directory is linked to. `sharedenv help <command>` lists every option.
292
356
 
293
357
  | Environment variable | Use |
294
358
  | ---------------------- | ------------------------------------------------------- |
@@ -300,11 +364,12 @@ What it does not protect against, said plainly:
300
364
 
301
365
  The CLI keeps three files there, all readable by you only: `session.json` (your session),
302
366
  `pins.json` (the public keys this machine has checked) and `log.json` (how far it checked
303
- the public key log). Never a secret, never your password, never your private key.
367
+ the public key log, and the warnings not acknowledged yet). Never a secret, never your
368
+ password, never your private key.
304
369
 
305
- Exit codes: `0` success, `1` error, `2` wrong usage, `130` cancelled. With `run`: the
306
- command's own code, `128 + n` when signal `n` ended it, `127` when the command does not
307
- exist.
370
+ Exit codes: `0` success, `1` error, `2` wrong usage (an ambiguous vault name included),
371
+ `130` cancelled. With `run`: the command's own code, `128 + n` when signal `n` ended it,
372
+ `127` when the command does not exist.
308
373
 
309
374
  ## License
310
375