macrun is a macOS command-line tool for local development secrets.
It stores secret values in macOS Keychain, tracks non-secret metadata separately, and injects secrets into a child process only when you explicitly run a command.
If you want the convenience of environment variables without leaving plaintext .env files around your repo, this is the tool.
It also works with sensible defaults, so common usage does not require setup first.
Local secret handling tends to drift into one of a few bad patterns:
- large plaintext
.envfiles copied between projects - long-lived
exportcommands in a shell session - reusing the wrong project's credentials by accident
- handing every secret to every process whether it needs them or not
macrun is designed to tighten that up without trying to be a full secret platform.
It helps by:
- storing secret values in Keychain instead of repo files
- scoping secrets by project and env
- importing from existing
.envfiles when needed - keeping the main workflow centered on whole-scope
macrun exec -- ...
macrun is for local development on macOS. It is not a replacement for:
- Vault or another server-side secret manager
- CI/CD secret distribution
- production secret storage
- process sandboxing
If a process receives a secret, that process can still leak it. macrun reduces exposure before process start; it does not make an unsafe program safe.
From crates.io:
cargo install macrunFrom this repository:
cargo install --path .During development you can also run it directly:
cargo run -- doctorIf you use macrun heavily during local development on macOS, consider signing the debug binary with a stable local code identity. Rebuilt binaries can trigger repeated Keychain access prompts because macOS may treat each rebuilt binary as a new caller.
The default Makefile workflow uses ad-hoc signing with a stable identifier, which works for local development on this machine:
make build-signedIf you prefer, you can still override the signing identity explicitly.
To install the exact lockfile-resolved dependency set from a published release:
cargo install --locked macrunStore a value in the default project and default env:
macrun set URL=https://somewhereStore a value in a named env while keeping the default project:
macrun set --env staging URL=https://staging.example.comInitialize the current working tree:
macrun init --project my-app --env devImport an existing .env file:
macrun import -f .envList stored keys without printing values:
macrun listRun a command with only the secrets it needs:
macrun exec -- cargo runRun a command with one secret replaced by Vault Transit ciphertext:
macrun exec \
--vault-encrypt APP_CLIENT_SECRET=APP_CLIENT_SECRET_CIPHERTEXT \
--vault-addr http://127.0.0.1:8200 \
--vault-key app-secrets \
-- myappPrint the full resolved environment for the active project/env:
macrun env --format jsonEach stored secret is identified by:
- project
- env
- environment variable name
Example scope:
- project:
my-app - env:
dev - key:
APP_DATABASE_URL
When you run a command, macrun resolves the active project and env, reads every stored value for that scope from Keychain, and injects them into the child process you launched.
Implemented today:
initsetgetimportlistexecenvunsetpurge --yesdoctorvault encryptvault push
Global flags:
--project NAME--env NAME--json
Set secrets manually:
macrun set APP_DATABASE_URL=postgres://localhost/appdb
macrun set APP_SESSION_SECRET=change-me API_TOKEN=replace-meRead a specific value:
macrun get APP_DATABASE_URLImport a dotenv file into the active scope:
macrun import -f .envInspect metadata:
macrun list --show-metadataPrint a machine-readable environment snapshot:
macrun env --format jsonRemove keys:
macrun unset APP_SESSION_SECRET API_TOKENmacrun can resolve the active scope from a local config file named .macrun.toml.
Project resolution order:
- explicit
--project .macrun.tomlin the current directory or nearest ancestor- internal default project scope
Env resolution order:
- explicit
--env default_envfrom.macrun.tomldev
That means a typical workflow is:
- run
macrun initonce in a working tree - store or import secrets for that project
- run local commands via
macrun exec
If you do not initialize a working tree, macrun falls back to:
- project:
(default) - env:
dev
So commands like macrun set URL=https://somewhere work immediately.
(default) is a display label for the fallback project scope, not a literal project name. If you run macrun --project default ..., the project name is exactly default.
Secret values live in macOS Keychain.
The current Keychain layout uses one bundled item per project:
- service:
macrun/<project> - account:
__project_bundle__
Inside that bundle, secrets remain grouped by env.
Non-secret metadata is stored in the app config directory so macrun can efficiently list entries and track source and update time.
If macOS asks for Keychain access repeatedly after every rebuild, that is usually a code-signing identity problem rather than a macrun bug.
Cause:
- Keychain trust is associated with the binary's code identity
- an unsigned or ad-hoc rebuilt binary can look like a different app after each build
- macOS then asks again because the caller no longer matches the previously approved identity
Recommended fix:
- sign the debug binary after each build with a stable identity or stable ad-hoc identifier
- approve Keychain access once for that signed identity
Helpful targets:
make build-signedmake codesign-debugmake dist
The default signing mode is ad-hoc signing with the identifier io.frogfish.macrun. You can override it with CODESIGN_IDENTITY=... and SIGN_NAME=... if needed.
macrun's Vault support exists for bootstrap transfer, not day-to-day runtime secret serving.
The useful cases are:
- get Vault Transit ciphertext for an app that must store a key in its database
- write one or more secrets into Vault KV so the app fetches them from Vault directly
vault encrypt reads a plaintext secret from Keychain, sends it to Vault Transit, and prints the ciphertext.
That ciphertext can then be stored in an application database or handed to an admin API. At runtime, the app asks Vault to decrypt it and keeps plaintext only in memory.
If the app is being bootstrapped directly from macrun exec, you can also replace a plaintext env var with Transit ciphertext in the child process:
macrun exec \
--vault-encrypt APP_CLIENT_SECRET=APP_CLIENT_SECRET_CIPHERTEXT \
--vault-addr http://127.0.0.1:8200 \
--vault-key app-secrets \
-- myappIn that mode, macrun removes APP_CLIENT_SECRET from the child environment and injects APP_CLIENT_SECRET_CIPHERTEXT instead.
That removal is intentional. The bootstrap target process should receive ciphertext only, not both plaintext and ciphertext.
Example:
export VAULT_TOKEN=...
macrun vault encrypt APP_CLIENT_SECRET \
--vault-addr http://127.0.0.1:8200 \
--vault-key app-secrets \
--verify-decryptvault push reads one or more plaintext secrets from Keychain and writes them into Vault KV.
Example:
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 v2macrun helps reduce:
- accidental commits of plaintext secret files
- broad shell-session contamination
- wrong-project and wrong-env reuse
- oversharing secrets to processes that do not need them
It does not protect against:
- malware or a compromised user session
- root or admin compromise of the machine
- a child process that logs or forwards its environment
- terminal capture, clipboard leaks, or screen capture
- USER_GUIDE.md for full usage and operational guidance
- TODO.md for implementation notes and future work
Typical release flow:
make bump
make dist
cargo publishWhat those steps do:
make bumpincrements VERSIONmake distincrements BUILD, builds a release binary, and stages release artifacts indist/cargo publishpublishes the crate so users can install it withcargo install macrun
The staged distribution currently includes:
dist/bin/macrundist/USER_GUIDE.mddist/README.mddist/LICENSE
BUILD is intentionally included in the published crate source because the binary reads both VERSION and BUILD at compile time to produce the custom --version output.
GPL-3.0-or-later
Copyright (c) Alexander R. Croft