murk
Encrypted secrets manager for developers. One key unlocks everything.
murk stores encrypted secrets in a single .murk file that's safe to commit to git. It uses age encryption, works with direnv, and supports teams — all in one binary with no runtime dependencies.
murk is pre-1.0 and has not been independently audited. Use good judgment with production secrets.
Why
Most teams share .env files over Slack. That's bad. Tools like SOPS and Vault exist but they're complex, require cloud setup, or pull in runtimes you don't want.
murk is simple: one key in your .env, one encrypted file in your repo, done.
How murk compares
| murk | SOPS | Vault | dotenvx | git-crypt | |
|---|---|---|---|---|---|
| Encrypted values, readable keys | Yes | Yes | N/A | Yes | No (whole file) |
| Per-recipient encryption | Yes | Yes | ACL-based | No (shared key) | Yes (GPG) |
| Scoped per-user overrides | Yes | No | No | No | No |
| Requires a server | No | No | Yes | No | No |
| Cloud KMS required | No | Optional | Typically | No | No |
| Single binary, no runtime | Yes | Yes | No | Yes | Yes |
| Built-in direnv integration | Yes | No | No | Yes | No |
| Recovery phrase | Yes (BIP39) | No | No | No | No |
SOPS is the closest alternative. Both encrypt values in-place and support age. murk differs in having scoped (per-user) secrets, a single-file vault model, built-in team management (murk circle), and BIP39 key recovery. SOPS has broader KMS backend support and a larger ecosystem.
Vault solves a different problem — it's centralized infrastructure for secret storage, rotation, and dynamic credentials. If you need a secrets server, use Vault. If you want encrypted secrets in your repo, use murk.
dotenvx encrypts .env files but uses a single shared key for the whole team. There's no per-recipient encryption — if someone leaves, everyone needs a new key.
git-crypt encrypts entire files via git filters. Diffs are opaque, and revoking a team member is effectively impractical without re-encrypting git history.
Install
&&
Or via Cargo (requires Rust toolchain):
Or download a pre-built binary:
|
Pre-built binaries are available for Linux (x86_64, aarch64, armv7), macOS (x86_64, Apple Silicon), and Windows on the releases page. Binary releases are attested and can be verified with gh attestation verify murk-* --owner iicky.
Quick start
# Initialize — generates your key and recovery phrase
# Add secrets (prompts for value, hidden input)
# Use with direnv
How it works
Your .murk file has a plaintext header (key names, descriptions — no values) and encrypted values. Anyone can see what secrets exist via murk info. Only recipients with a valid MURK_KEY can see values.
Shared secrets vs scoped secrets
murk has two layers of encryption inside the .murk file:
Shared secrets (the murk) are encrypted to all recipients. When you run murk add KEY, every authorized team member can decrypt it. This is where production credentials, API keys, and other team-wide secrets live.
Scoped secrets (motes) are encrypted to only your key. When you run murk add KEY --scoped, the value is encrypted to only your key in the vault. During murk export, scoped values override shared ones — so you can use a local database URL while the rest of the team uses production.
# Shared — everyone sees this (prompts for value, hidden input)
# Scoped — only you see this, overrides the shared value during export
# Or pipe for scripting (use a command that doesn't leak to shell history)
|
Teams
# Alice sets up the vault
# Bob generates his own key
# Alice adds Bob as a recipient
# Bob can now decrypt
# Bob overrides a value for local dev
Offboarding
When someone leaves, revoke their access and rotate the secrets:
&&
CI/CD
Use murk-action to decrypt secrets in GitHub Actions workflows:
steps:
- uses: actions/checkout@v4
- uses: iicky/murk-action@v1
with:
murk-key: ${{ secrets.MURK_KEY }}
- run: ./deploy.sh # all vault secrets are now in the environment
Store your MURK_KEY as a GitHub Actions secret. All decrypted values are masked in logs.
Recovery
Your key is a BIP39 mnemonic. murk init prints 24 recovery words — write them down.
# Lost your key? Recover it (prompts for phrase, hidden input)
Commands
| Command | Description |
|---|---|
murk init |
Generate keypair and create vault |
murk add KEY [--scoped] |
Add or update a secret (prompts for value) |
murk generate KEY [--hex] [--length N] |
Generate a random secret and store it |
murk rm KEY |
Remove a secret |
murk get KEY |
Print a single decrypted value |
murk ls |
List key names |
murk export |
Print all secrets as shell exports |
murk import [FILE] |
Import secrets from a .env file |
murk describe KEY "..." |
Set description for a key |
murk info |
Show public schema (no key required) |
murk circle |
List recipients |
murk circle authorize PUBKEY [--name NAME] |
Add a recipient |
murk circle revoke RECIPIENT |
Remove a recipient |
murk restore |
Recover key from BIP39 phrase |
murk recover |
Show recovery phrase for current key |
Design
- age does the crypto — no custom cryptography
- Git is the audit trail — murk doesn't replicate what git does
- Header is public, values are private — key names are visible, values are not
- Explicit over magic — never silently overwrites or destroys data
The .murk file is safe to commit — key names are readable, values are individually encrypted:
See SPEC.md for the full specification.
Security notes
Shell history — murk add and murk restore prompt interactively with hidden input. Prefer these over passing secrets as arguments or via echo, which can leak to shell history. When piping from scripts, use commands that don't record to history (e.g. pbpaste | murk add KEY or reading from a file).
Key names are plaintext — the .murk header exposes key names (e.g. STRIPE_SECRET_KEY, DATABASE_URL) so that murk info works without a key and git diffs stay readable. Only values are encrypted. If your threat model requires hiding what services you use, this is a trade-off to be aware of.
Key stored in .env — your MURK_KEY lives in a .env file with chmod 600 permissions (owner read/write only). This file is gitignored. The key is equivalent to a password — anyone with access to the file or the MURK_KEY environment variable can decrypt shared secrets. This is the same trust model as SSH keys in ~/.ssh. If a machine is compromised, rotate your key and re-authorize with a new one.
Access control is advisory — any authorized recipient can decrypt all shared secrets. Per-key access metadata in the schema is cosmetic and not enforced cryptographically. If a recipient has MURK_KEY and is in the recipient list, they can read everything in the shared layer. Use scoped secrets (motes) for values that should stay private to one recipient.
See THREAT_MODEL.md for the full threat model.
License
MIT OR Apache-2.0