How lockmq works

Two processes in two accounts. Only the console holds the key.

Two processes

The console (lockmq console) runs in a session of the dedicated lockmq account. It holds the broker credentials and the only broker connections, applies the policy, masks messages and asks you to approve every read of message content.

The clients (the lockmq CLI and lockmq mcp, the MCP server on stdio) run on the agent’s side. They hold no secret and open no broker connection: they talk to the console over a Unix socket that only the lockmq-clients group reaches, and the kernel’s peer credentials are checked on every connection.

 agent account                         lockmq account (its own session)
┌──────────────────────┐   socket    ┌─────────────────────────────────────┐
│ Claude Code / Codex  │ ──────────▶ │ lockmq console                      │
│  lockmq mcp / CLI    │ ◀────────── │  policy · allowlist · masking       │
│  (no secret, no      │  JSON-RPC,  │  approval screens · audit log       │
│   broker connection) │  peer uid   │  credentials (prompt/keychain/Vault)│
└──────────────────────┘  checked    └───────────────┬─────────────────────┘
                                                     │ management API (GET)
                                                     │ stream protocol / AMQP
                                                     ▼
                                                 RabbitMQ

The path of a request

  1. The agent calls an MCP tool (lockmq_queues, lockmq_read, …) or the CLI.
  2. The console checks that the operation exists, its protocol is configured, the vhost is served by the profile, the tier allows it and the arguments are in range, before any broker call.
  3. Catalogue operations run at once and are audited. read and peek build an approval screen and wait for you.
  4. The console calls the broker, keeps only the fields lockmq renders, masks, caps the output and audits the call (never message content).
  5. The answer goes back framed as untrusted data.

The catalogue: an allowlist

What is not listed does not exist. Every operation is a CLI command and an MCP tool.

Operations
CommandReturnsApproval
lockmq overviewbroker version, nodes and their alarms, feature flags, totalsnone
lockmq queuesone page of queues, filtered (--filter GLOB) and sorted by name, messages, ready, unacked or consumersnone
lockmq queue NAMEone queue with its arguments, effective policy, bindings and consumersnone
lockmq exchanges, bindingsexchanges; bindings of a vhost, a queue or an exchangenone
lockmq policiespolicies, operator policies, runtime parameters (URI passwords removed)none
lockmq consumers, connections, channelswith prefetch and ack mode; on the served vhosts onlynone
lockmq stream NAMEa stream’s queue data and first/last offsetsnone
lockmq stream-offset NAME --consumer REFa named consumer’s stored offset and its lagnone
lockmq read STREAM --from first|last|N|TIMESTAMPstream messages, maskedyou, every time (auto mode outside production)
lockmq peek QUEUE --count Nthe head of a classic or quorum queue, masked; tier peek onlyyou, every time, never auto

--dry-run on read and peek asks the console for its verdict and the approval screen without asking you anything and without reading a message. All client commands.

Three protocols, one broker user

Protocols
ProtocolUsed forProfile key
Management HTTP APIevery catalogue operation; peekurl (required)
Stream protocolstream reads, first/last offsets, a named consumer’s stored offsetstream_url
AMQP 0-9-1stream reads when the stream port is not reachable, or through sshamqp_url

An operation that needs a protocol you did not configure is refused with the key to set. Tested against RabbitMQ 3.13 and 4.1.

Stream reads stay read-only

Over the stream protocol the consumer is opened without a name: no offset is stored and no single-active-consumer group is joined; it reads at most read_max messages, then closes. Over AMQP 0-9-1 it is a consumer with x-stream-offset and manual acks (on a stream an ack only returns credit; nothing is removed). A real consumer’s position never moves: store_offset is never called.

Queue peek: a per-profile setting

Reading a classic or quorum queue is not strictly read-only. The management get takes up to count ready messages from the head and requeues them in the same call: they come back redelivered=true, a quorum queue’s delivery count grows by one (over the limit, the message is dead-lettered or dropped), the order seen by active consumers can change, and only ready messages at the head are visible.

So peek exists only on a profile you set to tier = "peek" (lockmq setup asks, default no). Every peek is approved by you, never by auto mode. On a quorum queue the console reads the effective delivery limit (argument, policy, operator policy; the lowest; RabbitMQ 4.x’s default 20) and refuses a peek within delivery_headroom of it, from the highest count it saw this session.

╭─ PROD · rabbitmq.example.com:15671 / orders · user lockmq-ro · tier peek ─────
│ requested by uid 1000 (alice) · pid 48211 · process "lockmq" (self-reported name)
│ agent: see why order events fail in the dead-letter queue
│
│ PEEK orders.failed  count 3
│
│ PII: masked (2 mask rules, 1 allow rules; body redact-unknown; detectors: card, email, iban, phone)
│ ▲ NOT READ-ONLY: messages are taken from the head and requeued; they come back redelivered=true and their delivery count grows by 1
│ ▲ queue: quorum · ready 1204 · unacked 0 (not visible) · consumers 0
│ ▲ delivery-limit 20 · max seen so far 2/20
╰─ Type the profile name "prod" to approve:
A peek’s side effects are on the screen before you approve it.

Masking messages

Masking covers message properties, application properties, headers and bodies. Queue, exchange and stream names are kept.

  • JSON bodies are parsed and masked per leaf. body_mode = "redact-unknown" (the production default) redacts every string no allow rule names; detect uses mask rules, key-name heuristics and value detectors (email, phone, IBAN, card; national ids opt-in).
  • Other bodies: UTF-8 text goes through the detectors; binary, compressed or invalid bodies become <bytes:N sha256:H type:T>.
  • References: a redacted value becomes <redacted:rN.K>, the same reference for the same value within a result; the clear value stays in the console’s memory.
  • Dead-letter queues: mask rules of the queue read, of the origin queue in a message’s first x-death and of its exchange apply; allow rules only from the queue read, so a forged header can only add masking.
# .lockmq/pii.toml
[[mask]]
queue = ["orders/order-events"]     # vhost/name globs; "//name" for the vhost /
body  = "$.customer.email"          # JSON path: $.a.b, $.a[*].b, $.a[3]
mode  = "email"                     # redact (default) | partial | email

[[mask]]
queue  = ["*/*"]
header = "x-customer-*"

[[allow]]
queue = ["orders/order-events", "orders/order-events.dlq"]
body  = "$.items[*].sku"

The AI tightens, the human loosens

The console reads the config files every 2 seconds. A tightening (a lower limit, peek → read, a removed vhost, a new mask rule) applies at once, whoever wrote it. A loosening (read → peek, a larger limit, a lower headroom, a new vhost or URL, redact-unknown → detect, a new allow rule) waits for you: :review in the console shows it, and you apply or refuse it. No socket method loosens the policy. The agent may add mask rules (lockmq pii add) and queue change requests (lockmq request).

Console keys

Console keys and commands
Key / commandDoes
:reviewshow the pending loosening and the change requests of clients, apply or refuse
:statusthe policy in force, the session
Shift+Tab, :modeauto mode on/off (never on production; never for peek)
:quit, Ctrl-Cend the session

The switches are console-only and each change is audited. The session ends after idle_timeout without a request, after max_session, or on lockmq logout.

Refusals the agent can act on

Refusal codes
CodeRetryMeans
E_BAD_ARGSrewritean argument is missing, out of range or unknown
E_WRONG_QUEUE_TYPErewriteread on a queue or peek on a stream
E_VHOST_NOT_SERVEDask the userthe profile does not serve that vhost
E_PROTOCOL_NOT_CONFIGUREDask the userset stream_url or amqp_url
E_TIERask the userpeek on a profile with tier read
E_DELIVERY_LIMITask the usera peek could dead-letter or drop messages of this quorum queue
E_POLICYstopanything else the policy refuses

MCP clients get them as tool errors with a structured refusal (code, retry, hint); CLI exit codes are 0 ok, 1 refused, denied or failed, 2 no console, 3 usage or configuration error.