Skip to content

v0.14.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 02:50
· 71 commits to main since this release
v0.14.0
af0e034

[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, or murk diff 'HEAD@{1}' after a pull),
    run murk trust, then make the first 0.14 save.
  • Give CI the MURK_TRUSTED_SIGNERS variable (see below).
  • Run murk setup-merge-driver again 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, run murk trust once. 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 on ssh-ed25519 keys skip this step.
  • An unsigned vault is refused when your key can sign. Keys that can't
    sign (hardware, plugin and ssh-rsa keys) 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 --rotate on 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 run murk trust.
  • CI uses MURK_TRUSTED_SIGNERS. A runner has no trust record, so list
    the people who write the vault. Each person runs murk 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 to main before
    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 grant writes 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 exec refuses any key that is not a grant, including your own
    key in MURK_KEY or MURK_KEY_FILE, unless you pass
    --allow-operator-key.

Behaviour changes

  • Breaking: export, exec and agent exec refuse to set reserved environment
    names (PATH, LD_*, GIT_*, *_PROXY, MURK_* and others) unless you
    pass --allow-reserved. The MCP murk_exec tool always refuses them.
  • Breaking: murk diff --json now prints an object, {"secrets": [...], "changes": [...]}, instead of an array. changes lists recipient, signer,
    policy and grant changes.
  • murk diff verifies the revision it compares against, and fails if the
    verification fails.
  • murk edit, murk import and 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.sh installs a release when gh can verify its build provenance.
    When it can't (gh is missing or not logged in), it asks before it installs
    on a terminal and refuses without one. Set MURK_SKIP_ATTESTATION=1 to 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 raises murk.TrustError in Python and throws an Error with
    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
    on MURK_TRUSTED_SIGNERS and 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 trust on 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, and murk 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.

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