macrun 1.0.3

Keychain-backed local development secrets for macOS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# macrun User Guide

This guide covers the day-to-day use of macrun as a local development secrets tool for macOS.

It focuses on the current CLI as implemented in this repository and avoids assumptions about any larger parent project.

## 1. Overview

macrun stores local development secrets in macOS Keychain and injects them into a child process only when you explicitly ask it to.

It is built around three ideas:

- keep secret values out of repo files by default
- scope secrets by project and env
- keep each project/env scope internally coherent

It also has usable fallback defaults, so basic commands can work without a setup step.

## 2. What macrun Is For

Good fit:

- local app development on macOS
- moving away from plaintext `.env` files
- separating secrets between projects
- separating secrets between envs such as `dev` and `staging`
- selectively injecting keys into a process

Not a fit:

- production secret storage
- CI/CD secret handling
- cross-machine secret synchronization
- replacing Vault or another server-side secret manager
- defending against a malicious process once it has received a secret

## 3. Installation

Install from crates.io:

```bash
cargo install macrun
```

Install from this repository:

```bash
cargo install --path .
```

Or run during development without installing:

```bash
cargo run -- doctor
```

## 4. Command Summary

Current commands:

- `init`
- `set`
- `get`
- `import`
- `list`
- `exec`
- `env`
- `unset`
- `purge`
- `vault encrypt`
- `vault push`
- `doctor`

Global flags:

- `--project NAME`
- `--env NAME`
- `--json`

## 5. Scope Resolution

Every secret belongs to a scope:

- project
- env
- env var name

Example:

- project: `my-app`
- env: `dev`
- key: `APP_DATABASE_URL`

macrun resolves scope this way.

Project resolution order:

1. explicit `--project`
2. local `.macrun.toml` in the current directory or nearest ancestor
3. internal default project scope

Env resolution order:

1. explicit `--env`
2. `default_env` from `.macrun.toml`
3. `dev`

If no explicit flag or local config is present, the fallback scope is:

1. project: `(default)`
2. env: `dev`

This means `macrun set URL=https://somewhere` works even before you run `init`.

`(default)` is just how macrun displays the fallback project scope. It is not a reserved user-facing project name, so `macrun --project default ...` still means the literal project `default`.

## 6. Initialize a Working Tree

Create `.macrun.toml` in the current directory:

```bash
macrun init --project my-app --env dev
```

That file binds the working tree to a default scope.

If you need to overwrite an existing file:

```bash
macrun init --project my-app --env dev --force
```

The generated config is a small TOML file with the project name and default env.

`init` is optional. It is useful when you want the current working tree to resolve to a non-default project or env automatically.

## 7. Store Secrets

Store one value:

```bash
macrun set APP_DATABASE_URL=postgres://localhost/myapp
```

Store several at once:

```bash
macrun set APP_SESSION_SECRET=replace-me API_TOKEN=replace-me-too
```

Attach metadata for future listing:

```bash
macrun set APP_SESSION_SECRET=replace-me --source manual --note "created for local testing"
```

Rules for names:

- names must be valid environment variable names
- names may contain letters, digits, and `_`
- names may not start with a digit

## 8. Read and Remove Secrets

Read one value:

```bash
macrun get APP_DATABASE_URL
```

Remove one or more values:

```bash
macrun unset APP_DATABASE_URL
macrun unset APP_SESSION_SECRET API_TOKEN
```

`get` is best treated as an explicit inspection tool. For normal usage, `exec` is safer because it only hands secrets to the process you are launching.

## 9. Import From an Existing .env File

Import a file:

```bash
macrun import -f .env
```

Overwrite existing keys intentionally:

```bash
macrun import -f .env --replace
```

The importer currently accepts plain lines in these forms:

- `KEY=value`
- `export KEY=value`

It skips:

- blank lines
- comment lines starting with `#`

It does not attempt to behave like a full shell parser, so complex shell syntax should be cleaned up before import.

Suggested migration pattern:

1. import the existing `.env`
2. verify with `macrun list`
3. switch local commands to `macrun exec`
4. remove or stop relying on the plaintext file

## 10. List What Is Stored

List keys only:

```bash
macrun list
```

Show metadata such as source and update time:

```bash
macrun list --show-metadata
```

By default, `list` does not print secret values.

## 11. Print a Machine-Readable Environment

Shell output:

```bash
macrun env --format shell
```

JSON output:

```bash
macrun env --format json
```

This command is most useful for inspection, scripting, and debugging.

The safer default for interactive work is still to use `exec` rather than exporting secrets into your parent shell.

## 12. Run Commands With Injected Secrets

This is the main workflow.

Inject the entire active scope:

```bash
macrun exec -- cargo run
```

Run a non-Rust command:

```bash
macrun exec -- python3 manage.py runserver
```

Bootstrap an app with Vault Transit ciphertext instead of plaintext for one key:

```bash
macrun exec \
  --vault-encrypt APP_CLIENT_SECRET=APP_CLIENT_SECRET_CIPHERTEXT \
  --vault-addr http://127.0.0.1:8200 \
  --vault-key app-secrets \
  -- myapp
```

Behavior to expect:

- every key in the current scope is loaded from Keychain
- the child process inherits the normal parent environment plus every secret value in that scope
- macrun prints a short scope summary to stderr before launching
- the child process exit code is returned unchanged when possible

When `--vault-encrypt` is used:

- macrun reads the named plaintext secret from Keychain
- encrypts it with Vault Transit before launching the child process
- removes the plaintext source variable from the child environment
- injects the ciphertext under the destination variable name

`SRC=DST` means:

- `SRC` is the secret name in macrun Keychain storage
- `DST` is the env var name the child process receives

If you pass only `SRC`, macrun replaces that same env var with ciphertext.

This is intentional bootstrap behavior: the child process should not receive both the plaintext source variable and the ciphertext replacement unless you explicitly model that as two separate stored secrets.

If the scope has no stored keys, `exec` fails rather than silently running with an empty secret set.

## 13. Use Multiple Envs

Envs let you keep separate local contexts inside one project.

Examples:

- `dev`
- `test`
- `staging`
- `customer-a`

Store different values under different envs:

```bash
macrun --project my-app --env dev set APP_DATABASE_URL=postgres://localhost/devdb
macrun --project my-app --env staging set APP_DATABASE_URL=postgres://localhost/stagingdb
```

Run against a specific env:

```bash
macrun --project my-app --env staging exec -- cargo run
```

Envs are useful when the variable names stay the same but the actual endpoints or credentials change.

## 14. Purge a Scope

Remove every indexed secret for the active project and env:

```bash
macrun purge --yes
```

This is intentionally destructive. Without `--yes`, the command fails and asks for explicit confirmation by re-running it.

## 15. Check Local State

Run:

```bash
macrun doctor
```

`doctor` reports useful context such as:

- current working directory
- whether a local `.macrun.toml` was found
- the resolved project and env, if any
- the local state directory
- the index path
- how many secrets are indexed overall and in the current scope

If you are unsure why a command cannot resolve a project or env, start with `doctor`.

## 16. Storage Details

The current implementation stores secret values in macOS Keychain using:

- service: `macrun/<project>/<env>`
- account: env var name

macrun also stores non-secret metadata in its local application config directory. That metadata powers commands such as `list`, `unset`, `purge`, and scoped selection.

## 17. Security Model

macrun helps reduce these common local-development failures:

- committing plaintext `.env` files
- leaving sensitive values in repo-local files
- contaminating a long-lived shell session with broad exports
- mixing up projects or envs
- oversharing secrets to commands that do not need them

It does not protect you from:

- malware already running as your user
- root or admin compromise
- a child process that logs or forwards secrets
- terminal capture, clipboard leaks, or screen recording

The rule is simple: once a process receives a secret, that process is part of your trust boundary.

## 18. Vault Bootstrap Transfer

macrun's Vault support is for bootstrap transfer: moving a high-value secret out of local Keychain and into its real system boundary without first dropping it into a plaintext file.

There are two separate patterns.

### Transit ciphertext for application databases

Use `vault encrypt` when an app must keep a key in its own database.

That workflow:

1. reads a plaintext secret from Keychain
2. encrypts it with Vault Transit
3. prints the ciphertext so it can be stored downstream
4. can verify decrypt without printing plaintext

The app then stores the ciphertext and asks Vault Transit to decrypt it when needed, usually caching the plaintext only in memory.

If you are bootstrapping the app directly with `macrun exec`, the same pattern can be used without an intermediate copy step:

```bash
macrun exec \
  --vault-encrypt APP_CLIENT_SECRET=APP_CLIENT_SECRET_CIPHERTEXT \
  --vault-addr http://127.0.0.1:8200 \
  --vault-key app-secrets \
  -- myapp
```

Vault authentication currently uses `VAULT_TOKEN` from the environment.

Example:

```bash
export VAULT_TOKEN=...

macrun vault encrypt APP_CLIENT_SECRET \
  --vault-addr http://127.0.0.1:8200 \
  --transit-path transit \
  --vault-key app-secrets \
  --verify-decrypt
```

### Vault KV as shared secret storage

Use `vault push` when Vault itself should store the secrets and the app will fetch them from Vault directly.

That workflow:

1. reads one or more plaintext secrets from Keychain
2. writes them into Vault KV at a chosen mount and path
3. leaves Vault as the source of truth for the app-side read path

Example:

```bash
export VAULT_TOKEN=...

macrun vault push APP_CLIENT_SECRET API_TOKEN \
  --vault-addr http://127.0.0.1:8200 \
  --mount secret \
  --path apps/my-app/dev \
  --kv-version v2
```

## 19. Vault Over an SSH Tunnel

macrun does not create or manage SSH tunnels itself.

If your Vault endpoint is reachable only through a bastion or private network, open the tunnel separately and point `--vault-addr` at the local forwarded port.

Example:

```bash
ssh -L 18200:vault.internal:8200 user@bastion
```

Then in another shell:

```bash
export VAULT_TOKEN=...

macrun vault encrypt APP_CLIENT_SECRET \
  --vault-addr http://127.0.0.1:18200 \
  --vault-key app-secrets \
```

If the remote Vault endpoint expects HTTPS with a certificate valid only for its original hostname, forwarding to `127.0.0.1` may cause hostname validation failures. That is a transport configuration issue rather than a macrun-specific behavior.

## 20. Recommended Usage Patterns

Good patterns:

- initialize each working tree once with `macrun init`
- keep local secrets in Keychain instead of repo files
- use envs to separate materially different contexts
- keep each env self-contained so a command can consume the whole scope
- use `doctor` when scope resolution is unclear

Patterns to avoid:

- exporting your entire secret set into a long-lived shell session
- keeping large plaintext `.env` files around after import
- sharing one env across unrelated environments

## 21. Troubleshooting

`no project resolved`

- run `macrun doctor`
- check whether `.macrun.toml` exists in the current directory or an ancestor
- pass `--project` explicitly if needed

`exec` says there are no secrets in the current scope

- verify the scope with `macrun doctor`
- check the stored names with `macrun list`
- confirm you are using the intended project and env

`import` fails on a line in `.env`

- simplify the source file to plain `KEY=value` lines
- remove shell constructs the importer does not understand
- retry with a smaller file if needed

`VAULT_TOKEN is required`

- export `VAULT_TOKEN` before using `macrun vault encrypt` or `macrun vault push`

`purge` refuses to run

- re-run with `--yes` if you really intend to destroy the current scope