lock family docs GitHub

Changelog · Changelogs

lockmq changelog

All notable changes to lockmq. The format follows Keep a Changelog; versions follow Semantic Versioning (no stability promise before 1.0).

[Unreleased] #

Fixed #

  • Hub mode: a read or peek the hub decides within its short wait (an auto rule of lox run, lockkit#162) runs in the same call and returns its result, or denied, instead of answering pending with no notification to follow (#64).

[0.9.0] - 2026-11-13 #

Builds on lockkit v0.19.0; works with lox run as v0.8.0.

Breaking #

  • lockkit v0.18.0 (lockkit#120): the console's status answer no longer carries auto_approve, and the auto-mode identifiers deprecated since lockkit v0.9.0 are gone. A client that reads auto_approve gets false; the hub's auto rules ([profiles.NAME.auto]) are unchanged.
  • lockkit v0.18.0: more config keys and values are refused as secrets: keys holding passphrase, apikey, bearer or cookie (camelCase and joined parts too, apiKey, authToken), and values embedding a bearer token, an Authorization: header or a URL with userinfo. A profile named like a secret no longer loads: rename it.
  • With a managed configuration (/etc/lockfamily/managed.toml), a profile beyond its [lockmq.bounds], or a project or user profile named as a managed one, is refused (see Added). Without the file, nothing changes.

Behaviour #

  • lockkit v0.19.0 (lockkit#130): masking with the default detectors can mask more, or under another kind: fr_nir runs before card (a NIR whose digits pass Luhn is masked as a NIR), phone also finds a national number after a phone word and 12-digit German mobiles, be_niss finds other separators after NISS/rijksregister, an e-mail local part may hold an apostrophe. The new lockkit detectors are opt-in.

Added #

  • The company's managed configuration (/etc/lockfamily/managed.toml, the Lock Family's managed.toml): lockmq enforces its [lockmq] section (#57). [lockmq.profiles.<name>] are served as written and cannot be redefined in the config files; [lockmq.defaults] fill the keys a profile leaves unset; [lockmq.bounds] bound every profile (tier, production, tls, body_mode, vhosts, credentials, hosts, credentials_ttl, auto rules, limits): a profile beyond them is refused at the console's start and at every policy poll, with no prompt, citing the managed key, and no human can approve past it. Required detectors and [[lockmq.bounds.pii_mask]] rules are always on; a user [[allow]] that could clear a managed mask is refused. [family] vault.required requires credentials = "vault". Keys: docs/configuration.md.
  • lockmq managed show and lockmq managed check [--profile P] (exit 3 on a violation, one blocked by company policy: … line each; lox run runs it before it starts the lock), and a managed line in lockmq doctor.

Changed #

  • A mask rule in redact mode wins over a weaker rule on the same value, whatever their order.

  • Hub mode: the console opens at most 30 tickets a human decides per 5 minutes (was 10) and 30 pending ones (was 20); a ticket lox run's auto mode decides leaves that count, and a safety cap of 300 tickets per window counts every one (lockkit v0.15.0, lockkit#150).

  • The profile's limits.tickets_per_window, limits.ticket_window and limits.max_pending set those limits; more tickets, a shorter window or more pending tickets is a loosening, reviewed like the others (the typed profile name on production). A lowering applies at once. The status screen shows the limits in force.

Fixed #

  • The launcher mounts the machine's managed directory (/etc/lockfamily, /Library/Application Support/lockfamily on macOS) read-only on /etc/lockfamily in its containers, as lox run does in its locks: lockmq managed show, managed check, doctor and the other commands run from the host see the company's managed configuration (#59). No directory: no mount; a path that is not a directory (a file, a dangling link) or holds a : stops the launcher.

[0.8.1] - 2026-11-07 #

Builds on lockkit v0.14.1.

Changed #

  • Docs, agent assets (skill, instructions), the launcher, help, errors and doctor name lox run, not the deprecated lox up: one terminal outside the agent starts the hub and the locks, and serves the decisions. No behavior, flag, label or file name change (up.json, .lockfamily/ stay) (#54).
  • lockkit v0.14.1: its shared texts (StartHint, ErrNoHub) name lox run.

[0.8.0] - 2026-11-06 #

Builds on lockkit v0.13.0; needs lox run (lockhub#121) or a hub on lockkit v0.13.0 or later.

Added #

  • Auto mode per profile: [profiles.NAME.auto] rules (connect, list, read, peek, get, publish, purge, delete: auto, ask or deny; auto = "full-auto"). Defaults: connect/peek ask, list/read auto. The lock enforces deny (E_AUTO_DENIED) and ask (never_auto tickets). A looser rule is a policy loosening (typed name on production). The audit records auto_rule and decided_by.
  • lockmq profile rules [--json], for lox run.

Changed #

  • Tickets are version 2 (kinds action and connect); an older hub refuses them. In hub mode a broker session serves the agent only after its connect ticket is approved.
  • CI: macOS tests run on main, nightly and on demand, not on pull requests.

[0.7.0] - 2026-11-01 #

Added #

  • --instance NAME: reach one lock among several of this type (lea mounts each at /run/lockmq/NAME). Every agent-side command, mcp and doctor take it; it defaults to LOCKMQ_INSTANCE, then to the one lock listed in LEA_LOCKMQ_INSTANCES.
  • credentials = "vault": the console asks the Vault token at its start (masked), under lox up too.

[0.6.1] - 2026-11-01 #

doctor --fix keeps .lockmq as lox up copies it and checks as the agent account. Builds on lockkit v0.10.0; lockhub as v0.6.0 (v0.3.0 or later).

Fixed #

  • lockmq doctor --fix no longer breaks lox up (found by lox init, DavidGodefroid/lockhub#92). It gave the project's .lockmq/ to the console account (uid 10011), a leftover of the standalone console that read .lockmq itself; lox up reads it as you and copies only your own files, so it then refused it ("owned by uid 10011, not by you"). doctor now checks .lockmq as lox up copies it: yours, as are the files and directories in it, none writable by group or others, no link. --fix takes group and others' write away there, and gives back to you what an older doctor --fix gave the console account (.lockmq itself and its copy of pii.toml); a link or a file of another account is reported, never repaired. Removed with the standalone era: the console account's ownership of .lockmq and pii.toml, and the "readable by the console account" repair of config.toml (lox up keeps owner-only modes in its copy, which the console account owns).
  • doctor --fix checks as the agent account, not as root. The launcher ran the whole doctor --fix as root, without lox up's socket: the console's check always failed (the console refuses root as a client) and the checks read files as root. The launcher now passes your uid (LOCKMQ_HOST_UID) and lox up's socket to every doctor; with --fix the image starts as root for the repairs only, then becomes the agent account for good before any check, as a doctor without --fix runs. On a fresh setup doctor --fix --yes --json exits 0 (warnings only). The project-access check now names the agent account, which must enter the project, instead of the console account.

Added #

  • Each release tag bumps the lockmq formula of the Homebrew tap (brew install davidgodefroid/tap/lockmq): the release workflow's last job dispatches the bump once the release is signed (DavidGodefroid/lockfamily#8). It is skipped while the TAP_DISPATCH_TOKEN secret is not set.

[0.6.0] - 2026-11-01 #

The same launcher verbs as every lock of the family, which lox init drives (DavidGodefroid/lockkit#111, T5; lockkit GUIDE §14): lockmq profile add replaces lockmq setup, which is removed. Builds on lockkit v0.10.0; lockhub as v0.5.0 (v0.3.0 or later).

Added #

  • lockmq profile add adds one broker profile to the project's .lockmq/config.toml from its flags: --profile NAME (default dev), --url (management API), --user, --vhost (repeatable), --amqp-url, --stream-url, --production, --credentials ask|vault [--vault-path P], --tier read|peek, --project DIR. Without --non-interactive, in a terminal, it asks for what the flags did not give; without a terminal it refuses rather than wait. --non-interactive never reads stdin (--url is then required). No form takes, asks for or writes a secret: the arguments go through lockkit's verbs.Parse and verbs.RefuseSecrets (a flag named like a secret, a value that embeds a password, a positional argument) and verbs.Credentials (keychain refused), and an error names the flag, never its value. A profile already in the config is refused with code exists and nothing is written. --json prints exactly one line on stdout: verbs.Profile, or verbs.Error (usage, secret, exists, failed); questions and messages then go to stderr.
  • lockmq doctor --json prints one verbs.Doctor (ok, fixed, every check with its status ok|warn|fail, detail and fix); a usage error prints a verbs.Error. The exit status is 0 exactly when no check fails: a console that does not run yet and unwired agents are warnings.

Changed #

  • lockmq doctor --fix without a terminal and without --yes is refused before any check (it was refused only when a repair was due); --yes without --fix stays a usage error.
  • The launcher runs profile add as you with HOME=/tmp, as it ran setup; every flag reaches the image as given.
  • Builds on lockkit v0.10.0 (verbs).

Removed #

  • lockmq setup, replaced by lockmq profile add (pre-release: no alias).

[0.5.0] - 2026-11-01 #

The console is hub-attached only (DavidGodefroid/lockkit#110): lox up starts it, and the launcher's agent commands reach the console lox up runs through .lockfamily/up.json (lockhub#78). Builds on lockkit v0.9.1.

Changed #

  • The agent commands reach the console lox up runs (lockhub#78). The launcher's mcp, doctor and client commands (status, queues, read, peek...) mount the socket volume of the workspace's lockmq lock that lox up names in .lockfamily/up.json, read-only, once Docker confirms it is lox up's (the checks of lea; no container lox up did not start may mount it read-write), and no longer the fixed lockmq-run: without lox up they exit 2 and say to run it (doctor runs without a socket and says why). With several lockmq locks, --instance NAME picks one (in .mcp.json: "args": ["mcp", "--instance", "NAME"]). .mcp.json is unchanged (docs/docker.md).

  • lockmq console is launched by lox up only: --hub, --hub-key-file and --instance are required, and without them (or with only some) it refuses before anything else ("the console is launched by lox up only", exit 3). The usage text and lockmq help list them first. The quick start (README, docs, site) goes through lox add lockmq <instance>, lox up, lox attach and lox approve.

  • A pending answer of lockmq read or lockmq peek (decided on lockhub) prints the ticket to run with --ticket, no longer null.

  • No console running: the CLI, lockmq_status and the MCP tools now ask the user to run lox up ("the console is started by lox up only: ask the user to run lox up"), no longer a lockmq console command; the status's start is lox up and carries no profile or project. lockmq doctor prints "the console runs under lox up: run lox up, then lox attach <instance> for its questions". The agent skill says the same.

  • The agent skill, the agent notes (AGENTS.md, Claude Code) and the README say message reads are approved on lockhub (lox approve, the lea deck or a paired phone), no longer in the console; the console still asks its own questions (credentials, policy) through lox attach.

  • Builds on lockkit v0.9.1 (hub-only console, lox up start hint, console PolicyOnly mode).

  • The docs and comments name lockhub's host CLI lox (lockhub v0.3.0 renamed it from lock, lockhub#79): lox up in docs/usage.md, docs/docker.md and lockmq hub-key's comment.

Removed #

  • --auto and auto mode (Shift+Tab, :mode); the dry run's auto verdict. Requests are decided on lockhub; the console's own decisions (a policy change, :unmask) stay at its terminal.
  • The standalone start that withdrew the plans hub mode had left pending.

[0.4.3] - 2026-10-31 #

Builds on lockkit v0.8.1: a --hub URL is http(s)://host[:port] only, opt-in ip, secret and person_name detectors, and no error echoes a value typed in the wrong place.

Added #

  • detectors also accepts lockkit v0.8.0's opt-in ip (public IPv4 and IPv6 addresses), secret (credentials, tokens, private keys, masked as <redacted>) and person_name; none is in the default set.

Changed #

  • Builds on lockkit v0.8.1 (was v0.7.2). console --hub takes http(s)://host[:port] only: a hub URL with a path (https://h/lockhub), query, fragment, IPv6 zone, a host that is not a DNS name or an IP address, or a port outside 1-65535 is refused, without quoting the URL (such a URL never reached a working hub: lockhub serves /v1/ at its root). A hub that cannot be reached is named by scheme and host, no longer by the whole request URL.
  • Through lockkit v0.8.0, the detectors read a JSON \n, \r or \t escape, and the \u0026-style escapes of &, <, >, = and quotes, as the blank or delimiter they stand for: a value right after one ({"msg":"tel:\t0475123456"}) is now masked.

Fixed #

  • Errors no longer quote a value typed in the wrong place (a token or a URL with a password never reaches the terminal). The --profile errors of the client commands, console and doctor refuse an invalid name with the allowed form and an unconfigured one with the configured profiles only; setup checks the typed profile name with lockkit's CheckProfileName (an invalid or production-reserved name is a configuration error, exit 3) and does not name a duplicate; an unknown command prints the usage, an unknown agent of init (or a flag) the known agents, and a refused --allowed-uids field is not repeated. Through lockkit v0.8.1, neither do the errors of --instance, hub-key, a profile name in a config file, --require-device, the IPC socket names, the service_user and client_group lookups and the console's isolation refusals: they name the key and the allowed form.

[0.4.2] - 2026-10-31 #

Errors never echo a value that may be a secret; builds on lockkit v0.7.2.

Fixed #

  • Config and rule errors no longer quote the value they refuse: a token or a URL with a password pasted into the wrong key never reaches the terminal, a log or an agent. The errors of credentials, tier, body_mode and tls in a profile, of the globs, header and fields of a .lockmq/pii.toml or console mask rule, and of console --allowed-uids name the key and the values it accepts; a refused glob is named by its key and position (queue entry 2). A bad flag of a client command (lockmq queues --page ...) is reported as "wrong arguments", as for the other commands, and the image's ALLOWED_UIDS build check no longer prints the argument. Through lockkit v0.7.2, so do detectors (by position), ssh.credentials and the --hub URL errors.

Changed #

  • Builds on lockkit v0.7.2 (was v0.6.0). console --hub refuses a hub URL with a user or password; a tightening that cannot be saved as the approved policy now applies anyway (audited policy/unsaved), where the looser policy stayed in force before.

[0.4.1] - 2026-10-30 #

lea's agents reach the release image's console: console --allowed-uids (lea#226).

Added #

  • lockmq console --allowed-uids UID,...: these uids may connect besides the members of lockmq-clients, as allowed_uids of the image's system.toml (the ALLOWED_UIDS build argument) does, for that console only, with the release image unchanged: an agent in a container of its own whose uid has no account in the console's image (lea's agents run as the host user's uid). Set only by whoever starts the console (the human: the launcher passes it on, lock up of lockhub v0.2.4 a lock's reviewed allowed_uids); root, leading zeros, 65534 (nobody) and the console's own uid are refused; every isolation check sees the uids (a session or terminal of one is the agent's), and the console prints them at start.

Changed #

  • The ALLOWED_UIDS build argument also refuses 65534 (nobody, the kernel's overflow uid).
  • scripts/hygiene.sh no longer matches lockkit's CheckIsolation.

[0.4.0] - 2026-10-30 #

Hub mode on lockkit v0.6.0: expired tickets are approved again, reveal from a local facade, and a console restarted without the hub withdraws its hub plans.

Added #

  • Hub mode renewals (lockkit decision H1): the run of an expired ticket answers pending with a new ticket that renews it, instead of a timeout; the hub's own renew requests are served too. The plan records its plan-hash inputs by name (params, queue). The renewal is recomputed under the approved policy without the broker (it may run on the hub's watch goroutine): the params are compared, the queue is unchecked (never claimed unchanged), and the new ticket's run reads the queue and refuses one that moved. Renewals are audited (renew) with the stored request.
  • Hub mode reveal: lockmq console --facade-keys-file FILE (written by lock up) pins the local facades that may reveal a ticket's clear screen (Ctrl-O), audited (reveal).

Changed #

  • A console started without the hub flags after hub mode withdraws the plans left pending and refuses a run naming a ticket with withdrawn, before anything runs.
  • Builds on lockkit v0.6.0 (ticket.UncheckedInput, console.Options.OnHub: the recompute is registered at hub start, before any request; was v0.3.1).
  • A closed broker session is no longer the backend's: nothing reads on it.
  • lockmq console's usage and lockmq help list --hub-plain-http (the flag lock up passes for its http://lockhub:8443); docs/usage.md says when it is needed.

[0.3.0] - 2026-10-30 #

Hub mode: reads and peeks can be decided on lockhub instead of the console's own prompt.

Added #

  • Hub mode (lockmq console --hub URL --hub-key-file FILE --instance NAME): a read or peek never runs without a decided plan. op.run stores it as a plan (lockkit plans: the client's uid, the agent's lea token, the policy), pushes a signed ticket with a masked screen and answers pending; op.run with ticket (--ticket, the MCP tools' ticket) runs it once decided, prepared again and hashing the same. Decisions are audited with the ticket, approver, facade and hub signature; the dry run says hub. lockmq hub-key --instance NAME prints the instance key. Requests carry the agent's lea name and the hash of its token (the raw token stays in the agent's process, lockkit#71).
  • Hub mode plan hash: the operation, vhost, target and arguments, plus the queue's properties read on the operation's own session (type, arguments, policy, operator policy, broker major; not its message counts). A queue changed between the approval and the run is refused E_STALE (retry: rewrite) before anything is read.
  • Builds on lockkit v0.3.1 (was v0.2.1). An http:// hub other than a loopback address needs --hub-plain-http and must be a service name without a dot or a private address; redirects from the hub are refused (lockkit#72).

[0.2.0] #

Docker-only: every command runs in the image through the lockmq launcher, the host install code is gone, and lockmq builds on lockkit v0.2.1. Upgrade from v0.1.0 with scripts/install.sh (it installs the launcher); see docs/docker.md. This release also holds the fixes from the v0.1.0 spec audit (below), never tagged on their own as v0.1.1.

lockkit v0.2.1 #

lockmq now builds on lockkit v0.2.1 (was v0.1.0), the version locksql and lockhttp use. What changes for lockmq:

  • Clear results are refused at desktop security level 1 (the agent's account can read the console window): lockmq console --show-results stops with a configuration error there, and Ctrl-O / :results keep the results masked with results stay masked at level 1: the agent's account can read this window. Levels 0 and 2 are unchanged; --auto and --allow-unmask are unchanged.
  • The PII detectors mask more: Unicode e-mail domains; non-breaking, narrow and thin spaces and full-width digits inside phone, IBAN, card and national-id values; integral floats in JSON bodies. A value masked before is still masked.

Removed #

  • The host install (cmd/lockmq/install.go) and internal/separate: the binary refused install since the Docker-only launcher. lockmq install and lockmq update in the image still print the hint to the launcher. doctor --fix keeps its project-access repair (access.go), which runs in the image.

One lockmq command, backed by Docker #

Every lockmq command now runs in Docker, through one host command: the lockmq launcher.

  • scripts/lockmq, a POSIX shell launcher, runs each command in ghcr.io/davidgodefroid/lockmq with the account and volumes it needs: console as lockmq in a container of its own per project (lockmq-console-<n>), confined (no capability, no-new-privileges, read-only root with a small /tmp, 256 processes, 1 GB), with no log driver, host.docker.internal mapped, VAULT_ADDR/VAULT_NAMESPACE passed and VAULT_CACERT's file mounted read-only, volumes lockmq-run and lockmq-home, --project added; init as you with your home; setup as you; doctor --fix as root; everything else, mcp included, as agent with lockmq-run. It mounts the project (the nearest directory up holding .lockmq/) at its own path (a path with : is refused) and never mounts the Docker socket. LOCKMQ_IMAGE, LOCKMQ_VAULT=1, LOCKMQ_DOCKER_ARGS (never globbed), LOCKMQ_READONLY_PROJECT=1 and LOCKMQ_CONSOLE_NAME adjust it.
  • scripts/install.sh installs the launcher, not the binary, after checking the cosign signature of checksums.txt against the release workflow run for that very tag (a signature of another release does not verify; only a vX.Y.Z tag is accepted), the archive's checksum and the images' signatures; cosign is now required (LOCKMQ_SKIP_COSIGN=1 is the explicit way past it, and replaces LOCKMQ_REQUIRE_COSIGN). The launcher runs its release's image by the verified digest.
  • lockmq version prints the launcher's and the image's versions; lockmq update [vX.Y.Z] replaces the launcher the same way, then pulls its image.
  • The release workflow builds the image (and its -vault variant) for amd64 and arm64, publishes it to ghcr.io and signs it with cosign. The archives ship the launcher; the .deb, .rpm and .apk packages are gone.
  • The binary refuses install and update ("lockmq runs in Docker"). lockmq init writes lockmq mcp, which now works as is.

Breaking, migration: install.sh installs the launcher in place of the binary, and releases no longer ship packages. Replace the docker run … lockmq … commands and sh -c 'exec docker run … lockmq mcp' agent entries of the former guide with lockmq … (the old ones keep working). A host install (sudo lockmq install) leaves the lockmq account and group, /etc/lockmq/ and the socket directory behind: see docs/docker.md.

Docker image #

Docker is now the default and only documented way to run lockmq, at parity with locksql.

  • A multi-stage Dockerfile (base images pinned by digest): a static build, then the console image with the separated setup of lockmq install baked in: lockmq (uid 10011) runs the console, agent (uid 10012, in lockmq-clients, gid 10010) runs the MCP server and the CLI, root-owns the binary and /etc/lockmq/system.toml, no sudo, and /run/lockmq (2710) and /home/lockmq as volumes. --build-arg ALLOWED_UIDS= names the uid of an agent in a container of its own; a vault target adds the Vault CLI checked against pinned sums. The build refuses root (0) and the console's uid (10011) in ALLOWED_UIDS.
  • lockmq setup knows when it runs in a container: the management URL defaults to host.docker.internal, localhost, 127.0.0.1 and ::1 are refused, and the keychain is not offered.
  • lockmq doctor checks the project's .lockmq (owned by the console account, by uid, mode 0755, its files readable) and doctor --fix [--yes] repairs it as root, without sudo when it already is root (docker run --user 0 … lockmq doctor --fix --yes). The repair enters the directory and changes ., so a symbolic link swapped in by the agent's account redirects nothing; a file is made readable only once the directory belongs to the console account and no other account may write it, and only a regular file of its checked, non-root owner with one link; a pii.toml the console account does not own is replaced by a copy it owns (a new inode: a descriptor held open on the old file writes nowhere). A .lockmq that is a link, and a directory or file owned by root, are reported only.
  • docs/docker.md is the setup guide (build, project, console, Vault, agent wiring, agent in its own container, broker in a container, what Docker does and does not separate). The README, docs/usage.md and the site describe only the Docker setup; the other deployment modes stay in the code, no longer documented.
  • CI builds both targets, checks ALLOWED_UIDS, and runs an end-to-end test of the guide against RabbitMQ (test/docker/e2e.py).

Fixes from the v0.1.0 spec audit #

  • Security: a body without content type that looks like JSON but does not parse is opaque under redact-unknown (it was masked as text, detectors only), and a body the broker cut short on peek is always opaque: names and addresses of a large dead-lettered message no longer come back in clear.
  • Security: a vhost or name that is, or holds, a . or .. path segment is refused before any request (E_UNSAFE_NAME) and in the config, so a management path cannot be normalised outside the endpoint allowlist.
  • limits.delivery_headroom = 0 is refused (a peek could dead-letter a message); the peek guard never goes below 1.
  • Ctrl-O / :results prints results in clear in the console, and --unmask (MCP unmask) returns them unmasked when the human allowed it with :unmask: approved every time, audited "unmasked":true; refused otherwise (E_UNMASK_OFF). Both were documented but did nothing.
  • status shows the profile's tier.
  • Stream offsets and lag say they are precise to one chunk (precision).
  • Connection and channel user names and client properties go through the detectors.
  • queue reads its own consumers instead of the whole vhost's.

[0.1.0] - 2026-10-19 #

First release.

  • Human-held console on lockkit v0.1.0: separate account (lockmq install), approval screens, typed profile name on production, policy review (the AI tightens, the human loosens), audit log without message content.
  • Catalogue over the RabbitMQ management API, an allowlist of read-only operations: overview, queues, queue, exchanges, bindings, policies, consumers, connections, channels; stream metadata and a named consumer's lag over the stream protocol.
  • read: stream messages from first, last, an offset or a timestamp, over the stream protocol or AMQP 0-9-1, with an unnamed consumer; approved by the human.
  • peek (tier peek only, off by default): the first messages of a classic or quorum queue, requeued; never auto-approved; refused near a quorum queue's delivery limit.
  • Message masking: JSON paths, header and property rules, redact-unknown and detect body modes, value detectors, redacted references, opaque binary and compressed bodies; rules in .lockmq/pii.toml.
  • CLI and MCP server (17 tools), setup, doctor, init for Claude Code, Codex, Cursor and Gemini CLI.
  • Integration suite on RabbitMQ 3.13 and 4.1.