Security model

lockmq lets an AI agent inspect a RabbitMQ broker without holding its credentials or connecting to it. This page says what it protects, how, and where it stops.

It summarises docs/security-model.md, which is the reference.

Principles

  1. Two processes, one key holder. The console holds the credentials and the only broker connections; the clients (CLI, MCP server) hold neither.
  2. The console is the enforcement point. Skills, rules and tool descriptions only guide the agent.
  3. The AI tightens, the human loosens. Any change that widens what the agent can do takes effect only after you confirm it in the console.
  4. Fail closed. An unknown operation, field, body format or rule is refused or treated as the most restrictive case.
  5. Defence in depth. The broker user’s own permissions back the allowlist where RabbitMQ allows it.
  6. Results are data, not instructions. Everything read from the broker is framed as untrusted.
  7. Reading a message is not always free. An operation that can change what a consumer receives (peek) needs its own tier, set by you, and your approval every time, with its side effects on the screen.

Boundaries

Boundaries and what enforces them
BoundaryEnforced by
Agent ↔ consolea separate OS account (lockmq install); a Unix socket in a directory only the clients group reaches; the kernel’s peer credentials checked on every connection; the console refuses to start without the separated setup
Console ↔ humanapproval screens in the console’s own terminal; on production the typed profile name; a 5-minute timeout means denied
Console ↔ brokeran allowlist of operations and endpoints; one broker user; TLS verify-full by default; an optional ssh tunnel opened by the console
Policyconfig files read every 2 s; loosenings held until confirmed; the approved policy stored in the console account’s state

What lockmq sends to the broker

  • Management API: GET on overview, nodes, feature flags, queues, one queue and its bindings, exchanges, bindings, policies, operator policies, consumers, connections, channels, whoami and runtime parameters; plus, under tier peek only, POST /api/queues/{vhost}/{name}/get with ackmode = "ack_requeue_true".
  • Stream protocol: an unnamed consumer (no stored offset, no single-active-consumer group), metadata and QueryOffset.
  • AMQP 0-9-1: basic.qos, basic.consume with x-stream-offset on a stream, manual acks, basic.cancel.

Never: /api/definitions, /api/users* (password hashes), other users’ /api/permissions*, any other non-GET endpoint, basic.get, basic.publish, a declare, a delete, a purge, store_offset, ack_requeue_false or reject_requeue_false. Every management URL path segment is escaped.

The broker user

Recommended: tag monitoring, permissions configure "^$", write "^$", and read on what lockmq may read. With configure and write empty, the broker refuses publish, declare, delete and policy changes from that user (tested on RabbitMQ 3.13 and 4.1).

The read permission also allows a purge

RabbitMQ authorises queue.purge (and the management API’s DELETE …/contents) with read alone. lockmq never purges, but the broker user cannot back that up: grant read on the streams only (^(order-events|payment-events)$), and on queues only for profiles with tier peek.

A monitoring user cannot read runtime parameters nor its own permissions: policies says the parameters are not visible, and the console’s privilege audit relies on the user’s tags only. At start and at each reconnection the console reads whoami: a user tagged administrator or policymaker is refused on production and needs a typed continue elsewhere.

Credentials

Asked at console start (echo off), read from the console account’s OS keychain, or read from Vault at every connection (the console never handles the Vault token; a lease is renewed by reconnecting before it ends). They never appear in argv, the environment, a file, the socket, the audit log or an error: errors are generic, URIs are built without userinfo (AMQP and stream logins go through SASL), and every error text that leaves the console is scrubbed of known secrets, URI userinfo and Authorization headers. URIs inside runtime parameters (shovels, federation) lose their userinfo before they reach a client.

Message content

  • Approval. Every read and peek shows a screen: the queue or stream, the starting point and count, what moves (nothing, for a stream read), the masking in force, the agent’s intent. Auto mode (non-production only) approves read, never peek.
  • Masking. JSON bodies are masked per leaf: redact-unknown (the production default) redacts every string no allow rule names; detect uses mask rules, field-name heuristics and value detectors. Headers and properties follow rules and heuristics. Bodies that are binary, compressed, invalid JSON, deeper than 64 levels or larger than four times max_body_bytes become <bytes:N sha256:H type:T>; so does a body without content type that looks like JSON but does not parse (under redact-unknown), and any body the broker cut short. Clear output: Ctrl-O prints results in clear in the console only; unmasked output to the agent needs :unmask in the console and your approval of every such request.
  • Size. At most read_max (≤ 200) or peek_max (≤ 20) messages, each body cut at max_body_bytes, the answer cut at max_output_bytes.
  • Audit. One JSON line per event in the console account’s ~/.local/state/lockmq/audit.log (mode 0600), with the operation, vhost, target, message count, the agent’s intent and the client’s uid. Never a message body, header or property value.

Threats

Threats and mitigations
ThreatMitigation
Agent changes broker stateoperation allowlist; GET only plus the get of peek; no publish, declare, purge, store_offset or destructive ack mode; broker user with empty configure/write
Peek dead-letters or drops a messageoff by default; tier per profile set by the human; delivery limit read first, refusal near it; side effects on the screen; never auto-approved
Agent turns peek onread → peek is a loosening: no effect until confirmed in the console; no IPC method loosens
A read moves a real consumerunnamed stream consumer; never store_offset
Password hashes or URI passwords leakthose endpoints never called; userinfo stripped from every URI
PII in bodies of unknown shaperedact-unknown on production; detectors; proposals of mask rules
Prompt injection in payloadsresults framed as untrusted data
Broker overloadpaginated lists with selected columns, one request at a time, request_timeout, read_max
Agent reads the console’s terminal or types into itseparate account and session; same-user mode refused

Known limits

  • Masking is best effort: a personal value in a free-text field that no rule, heuristic or detector catches comes back in clear. Use redact-unknown and allow rules on production.
  • RabbitMQ allows a purge to any user with read on the queue: the allowlist is the only barrier.
  • The approval screen can only show what the broker reports before the read: unacked messages are invisible to a peek, and a peek cannot know the next message’s delivery count before taking it.
  • Through an ssh tunnel streams are read over AMQP; offsets and lag need the stream protocol and are unavailable.
  • The audit log is append-only by convention (chattr +a on Linux makes it so for the console account too); there is no hash chain.

Reporting a vulnerability

Privately, through GitHub security advisories, not in a public issue. See SECURITY.md.