Repository navigation
v0.14.0
[0.14.0] - 2026-10-22
0.14.0 is a security release. It changes how murk decides whether to trust a
vault, and it changes the vault format. Read Upgrade first before you
install it.
Upgrade first
- The vault format is now 3.0, and murk 0.13 can't read it. The first save
by murk 0.14 rewrites the vault as 3.0, and 0.13 then refuses it as an
unsupported version. Upgrade every teammate and every CI runner together.
A 2.x vault still loads in 0.14 and is upgraded on its first 0.14 write. - On each machine, the first load of an existing vault is refused once.
Review it (git log -p -- .murk, ormurk diff 'HEAD@{1}'after a pull),
runmurk trust, then make the first 0.14 save. - Give CI the
MURK_TRUSTED_SIGNERSvariable (see below). - Run
murk setup-merge-driveragain in each clone. - Upgrade guide: teams and
CI/CD.
Trust
A .murk file lists its own recipients, so a valid signature alone doesn't
tell you whether to trust the person who signed it. murk 0.14 checks every
vault against a trust record kept on your machine, outside the repo.
- Trust is pinned per vault on each machine. On a fresh clone or a new
laptop, runmurk trustonce. It shows the vault id, each signer and its
signing-key fingerprint, and asks you to accept them. When a newly added
teammate with an age key writes for the first time, each other machine
refuses that write once. Compare the fingerprint they give you
(murk trust --print-key) over a channel other than the repo, then run
murk trust. Teammates onssh-ed25519keys skip this step. - An unsigned vault is refused when your key can sign. Keys that can't
sign (hardware, plugin andssh-rsakeys) still load it, with a warning. - Signatures are verified strictly, and weak signing keys are refused.
- Removing someone is permanent on every machine that sees the removal.
Their signature is refused on every branch from then on. The only exception
is their own vault from before the removal, at the commit where this machine
recorded the removal or an earlier one. Even then, it never makes them
trusted again. - A revoke on a branch only takes effect where it is merged. Until then,
the member is still a recipient on main and on every other branch the
revoke hasn't reached, and can read any secret added or rotated there.
Merge the offboarding change into main before you add or rotate secrets
anywhere else. A--rotateon that branch protects only the values it
rotated. - Reviewing an unmerged branch never adds trust. A branch this machine has
never accepted, such as a PR checked out for review, can only narrow what
this machine trusts. - Rollbacks are refused. Each vault carries a save counter. murk refuses
an older revision on a branch where it has seen a newer one, and it refuses
a jump of more than 1,000,000 saves until you runmurk trust. - CI uses
MURK_TRUSTED_SIGNERS. A runner has no trust record, so list
the people who write the vault. Each person runsmurk trust --print-key
to get their entry. With the variable set, an unsigned vault is refused for
every key.
Trust in murk is flat. Every recipient except an agent grant can add or remove
recipients and change values, and murk accepts those changes from anyone you
trust. It checks who made a change, not whether the change was a good one,
so review vault changes like code.
Merges
- The merge driver checks the common ancestor and both sides the way a load
does. A rollback hidden in a merge is refused, including one that uses
0.13-era history. - When both sides re-key a vault, the merge no longer drops the other side's
value changes. - The merge driver refuses a side signed by a removed member, and a side that
adds a removed recipient back. If a removed member wrote tomainbefore
the removal was merged, merge the branch that removed them, keep its side,
and make the trusted members' changes again. The SPEC describes the steps
under "Merging a removal into a branch the removed member wrote to".
Agent grants
- Grants are read-only. A grant key can't write the vault, and murk
refuses a vault signed by a grant key. - A grant gets less of the vault. It reads only its own record and the
values it was granted, not the shared metadata. - Grant key files carry their own trust.
murk agent grantwrites the
trusted signers into the key file, with the grant's name and expiry. A grant
trusts a vault only if one of those signers signed it. It stops at the
earlier of the two expiries, the one in its key file and the one in the
vault. - Revoking a signer revokes the grants that trust it.
- Grant TTLs are capped at 365 days.
murk agent execrefuses any key that is not a grant, including your own
key inMURK_KEYorMURK_KEY_FILE, unless you pass
--allow-operator-key.
Behaviour changes
- Breaking:
export,execandagent execrefuse to set reserved environment
names (PATH,LD_*,GIT_*,*_PROXY,MURK_*and others) unless you
pass--allow-reserved. The MCPmurk_exectool always refuses them. - Breaking:
murk diff --jsonnow prints an object,{"secrets": [...], "changes": [...]}, instead of an array.changeslists recipient, signer,
policy and grant changes. murk diffverifies the revision it compares against, and fails if the
verification fails.murk edit,murk importand other writes refuse new or changed header text
that contains control characters, bidi controls, zero-width characters or a
byte-order mark. Text that is already in the vault still loads.install.shinstalls a release whenghcan verify its build provenance.
When it can't (ghis missing or not logged in), it asks before it installs
on a terminal and refuses without one. SetMURK_SKIP_ATTESTATION=1to skip
the check. A failed verification is always refused.- Breaking: the Python and Node bindings apply the same trust check as the CLI. A
refused vault raisesmurk.TrustErrorin Python and throws anErrorwith
code'MURK_TRUST'in Node.
Known limits
The threat model lists
what 0.14 doesn't protect against. In short:
- A copied grant key file. Revoking a signer deletes murk's own copy of
each affected grant key file. A copy somewhere else, such as one handed to
an agent host, still trusts that signer until the grant expires. A grant key
passed as a bare key line (which is how murk-action passes it today) has
none of the key file's trusted-signer, grant-id or expiry lines. It relies
onMURK_TRUSTED_SIGNERSand on the expiry recorded in the vault. - Trust is per machine. Each laptop and runner keeps its own record, so
each one makes its own first trust decision. - The first
murk truston a fresh clone is trust on first use. murk
shows you the fingerprints, but you have to check them. - Five narrower cases:
- Two vaults that were copied from one 0.13 vault (for example, staging
copied from prod) share a vault id, so murk's id check can't tell them
apart. If one vault's file is swapped in for the other's and has a higher
save counter, murk accepts it, serves its values, andmurk verify
reports it as ok. The file still has to be signed by someone this machine
trusts for that vault. The merge driver accepts the other vault's content
whenever a trusted signer signed it. - Until a machine sees the vault's first 0.14 save, its merge driver can't
tell an older 0.13 revision from a newer one. Make the first 0.14 save
before you merge branches that still carry 0.13 vaults. - A crafted unsigned pre-0.14 vault used as a merge ancestor can let a merge
revert values to an older signed revision. Someone with write access to
the repo can introduce one without holding a key. - The save counter has a hard cap. A vault pushed past it is refused
everywhere, and a machine that accepted the cap recovers only by deleting
its trust record and trusting a restored vault again. - A machine that learned about a removal late (for example, only from a
merge) can still read a vault the removed member wrote after their
removal, if that vault's save counter is not above the removal's and a
new branch is deliberately placed at an older commit. It can only read it
there: a save that keeps the member is refused, and the member is not
trusted again. A vault they wrote with a higher counter is refused
everywhere.
- Two vaults that were copied from one 0.13 vault (for example, staging
murk-action
murk-action v1.2.0
supports murk 0.14. Pass the whole grant key file as murk-key, or use its new
trusted-signers input. Anyone on @v1 already gets it.
Security
This release fixes security issues in 0.13 and earlier. Details are in
GHSA-rcxf-7phg-x8w2.
Upgrade every machine and CI runner to 0.14.0.
All changes
Added
- feat: give agent grants their own record and trusted signers instead of the vault meta
Documentation
- docs: describe agent grant records and key-file trust anchors in the spec
- docs: correct factual errors in the spec
- docs: correct factual errors in security.md and suitability page
- docs: correct factual errors in readme
- docs: correct factual errors in concept pages
- docs: describe the grant layout in the concept pages
- docs: correct factual errors in cli help, generated references and the style guide
- docs: correct factual errors in python and node bindings docs
- docs: correct the ai agents and mcp docs
- docs: correct factual errors in guides: hardware keys, git merge and ssh, teams, worktrees
- docs: correct factual errors in the offboarding, recovery, ci, direnv and tier guides
- docs: correct factual errors in verification, changelog and roadmap pages
- docs: correct factual errors in contributor docs, code of conduct, templates, lints readme
- docs: correct factual errors in brand guide
- docs: correct the quick starts and install page
- docs: correct the threat model's factual errors
- docs: describe the grant layout in the threat model
- docs: describe binding trust, commit signatures and unverified header reads
- docs: label experimental features
- docs: match diff descriptions to the real murk diff output
- docs: add fingerprint confirmation, fresh-clone trust and CI steps to the quick starts
- docs: state env-var truthiness and describe's example reset as the code does
- docs: correct the experimental rationale for signatures, merge driver and diff
- docs: show a new grant in murk diff as an added line
- docs: describe merge rollback limits and grant revocation with their signer
- docs: state when the merge driver refuses 0.13-era sides
- docs: describe how revoking a signer treats grants with unknown anchors
- docs: correct the threat model and release verification docs for 0.14
- docs: correct the spec for 0.14
- docs: correct the readme, quick starts and guides for 0.14
- docs: correct the agent exec, grant and ci docs for 0.14
- docs: say grants are not meta recipients in the permissions table
- docs: correct the binding docs on key discovery and grant writes
- docs: re-record the ci grant demo with the key line and trusted signers
- docs: describe strict signature verification in the threat model
- docs: describe removals, registry adders and counter bounds in the trust spec
- docs: say which trust-pin keys strict verification drops
- docs: describe permanent removals, registry adders and counter bounds in the threat model
- docs: restore the spec's registry carry-over and trust re-sign rules
- docs: say when a save carries the loaded signer registry forward
- docs: say a save writes only registry keys its machine confirmed
- docs: match the threat model to the final trust-pin removal rules
- docs: say that revoking a signer revokes the grants that trust it
- docs: make removals and trust widening per branch in the threat model
- docs: scope the murk trust removed-key refusal to where the removal applies
- docs: limit the removal exception to an unmodified committed vault
- docs: match the murk trust removed-key refusal to the removal rule
- docs: update the threat model for the 0.14.0 trust fixes
- docs: say a load accepts a copied vault with a higher counter
- docs: state the 0.14 trust limits and fixed behaviour in the spec
- docs: say a surviving grant key file trusts a revoked signer
- docs: describe how the merge driver handles a both-sides re-key
- docs: correct branch trust, removal, revert and shared-id rules in the guides
- docs: describe the final trust-pin removal rule
- docs: describe the expiry a grant key file carries
- docs: say what the removal merge recovery drops and how to redo it
- docs: describe grant key files that carry their own expiry
- docs: review the removal recovery diff change by change
- docs: tie the removal exceptions to the commit the removal was accepted at
- docs: show the ci grant key forms and their expiry behaviour
- docs: re-record the ci grant demo with the expiry warning
- docs: say the pre-removal exception fails closed
- docs: match the guides to the final removal exception
- docs: note the late-learner exception to the removal rule
- docs: bound the pre-removal exception by the removal generation
- docs: warn that a removed member reads secrets on branches the removal has not reached
- docs: warn that a pending removal leaves the member a recipient on main
Fixed
- fix: never let murk init replace an existing key
- fix: reuse keys safely in init worktrees and strip remote credentials
- fix: authenticate vault header fields and refuse writes into a vault you are not on
- fix: anchor vault trust so forged or self-signed vaults are refused
- fix: never drop secrets a writer cannot read when saving the vault
- fix: refuse a keyless merge that would break the vault's integrity
- fix: keep rotated secrets in the tier they came from
- fix: keep a secret's tier when updating it without --group
- fix: never route a key that opens the meta into the grant path
- fix: canonicalise ssh recipients so revoke removes every copy
- fix: refuse vaults that bind an agent grant to a trusted signer's key
- fix: let the trust check see ghost grant records and keep them from blocking a merge
- fix: keep a trusted signer named by a grant record a signer and refuse grants bound to an ssh operator key
- fix: keep 0.13 vaults that list an ssh key twice loadable and list it once on save
- fix: let only a group member vouch for a group member's signing key
- fix: update the trust pin under a lock and recheck acceptance against the fresh pin
- fix: refuse grants bound to a signer named in MURK_TRUSTED_SIGNERS or a key-file anchor
- fix: make murk diff verify and fail closed
- fix: keep multi-line values intact in murk edit
- fix: harden git invocations, key files and memory hygiene
- fix: name the signer of the last vault commit in verify and flag keys that are not trusted signers
- fix: label keyless header reads as unverified and verify rotate --list before it passes
- fix: raise trust refusals as typed errors in the bindings and expose load warnings
- fix: tighten install verification and supply-chain guards
- fix: keep the integration tree green on linux, as root and in the node binding
- fix: describe which keys agent exec refuses without --allow-operator-key
- fix: refuse rollbacks smuggled through a merge of 0.13-era history
- fix: revoke agent grants that trust a revoked signer
- fix: refuse any merge side without the pinned vault id once the vault is pinned
- fix: treat an unrecorded grant anchor as trusting every signer and check local key files on revoke
- fix: stop the merge driver claiming a failed side was not merged
- fix: keep theirs' value changes when both sides re-key
- fix: write the pin for a new vault under the pin lock and keep what it already recorded
- fix: verify signatures strictly and refuse weak signing keys
- fix: keep a removal from the vault permanent in the trust pin
- fix: stop trusting registry keys once the signer who added them is removed
- fix: bound generation steps and refuse a forked vault
- fix: never widen the trust pin from a history load
- fix: refuse merge sides that bring a removed recipient back
- fix: never vouch for a registry key held on another signer's word
- fix: never widen trust from a branch this machine has not accepted
- fix: apply a removal only on branches whose history contains it
- fix: keep a removal in force when the vault file differs from the commit checked out
- fix: keep a removed signer refused on every branch and judge merges against every removal
- fix: let a removed member's own earlier vault load and name the merge that recovers a branch they wrote to
- fix: say which vault changes keeping the removing side drops and how to make them again
- fix: bound a grant key file by its own expiry
- fix: warn that a later vault from a trusted signer can still carry a removed member's changes
- fix: count a removed member's vault as pre-removal only at or before the commit the removal was recorded at
- fix: give a removal accepted from an uncommitted vault no pre-removal exception and point an abandoned removal to re-add
- fix: refuse a removed member's vault numbered above the removal even in history from before it
- fix: build the merge fuzz target against the current driver api
- fix: name stored keys and trust pins by the real checkout path so windows merges find them
Other
- test: label the 0.13 fixture key as a test-only key
- test: cover grant expiry through the mcp server and the node binding
- test: refuse a merge whose sides register different verifying keys for one signer
- test: mint the legacy grant's key with its grant, not as a teammate first
- test: cover low-order and legacy ssh recipients on signed vaults
- merge: never drop secrets a writer cannot read into the trust branch
- test: enforce who can do what with a role and command matrix
- test: check access invariants across random operation sequences
- test: run the permissions matrix under per-machine vault trust
- test: give expiring-grant tests a ttl a slow save cannot outlast
- test: let the node suite mint fresh keys after it sets MURK_KEY
- test: check that murk diff never records a revision in the trust pin
- test: show minted and revoked grants with their own sign in murk diff
- test: cover merge sides refused for a missing or foreign vault id on a pinned vault
- test: check vault trust invariants across machines
- test: check vault trust invariants across machines
- test: run each merge side on its own machine when both revoke the same recipient
- test: show a removed signer's registry binding is dropped, not just ignored
- test: make every stored trust sequence prove it exercised its scenario
- test: check the generation cap where no counter refuses first
- test: fail a decisive revoke that has no grant to cascade
- test: bind a removal to the branches its history reaches
- test: refuse a removed member's signature on main before the removal merges
- test: give the grant key-file expiry test room under load
- chore: release 0.14.0