The pipeline
For every query, Midplane runs five steps in a fixed order. The decision is binary: allow or deny.1
Parse
The SQL is parsed with libpg_query, the actual Postgres parser — no regex on SQL, ever. If the SQL can’t be parsed, it’s denied by the
parse_error rule. Midplane never enforces policy on text it can’t read — it fails closed.2
Project to an IR
The parse tree is projected into a dialect-agnostic intermediate representation, and the policy rules evaluate that IR rather than a raw AST.
3
Policy
The IR is checked against the four rules in order; the first rule that denies wins. The guardrail (
dangerous_statement) runs last, so when a more specific rule would also deny, that rule’s reason surfaces.4
Audit (before execution)
The attempt and the decision are written to the audit trail before the query runs. Denied queries are recorded too. If the pre-execution audit write fails, the query never runs.
5
Execute (only if allowed)
Only allowed queries reach your database. If the policy masks any columns, the engine redacts those values in the result before it returns to the agent. The result and timing are appended to the same audit trail. Denied queries stop at the audit step — they never touch the database.
The four rules
The engine evaluates four rules in order. Two are built-in guards that are always on; the rest are driven by your policy —table_access and the dangerous_statement guardrails default on.
The built-in guards, the
dangerous_statement guardrails, and the per-table rule are all covered in table access and guardrails; the guardrails are configurable per database via the guardrails block.
”Audited before executed”
Most database logging records what already happened. Midplane inverts that: the attempt and the decision are durable before a single row is read or written, and the audit write is a precondition for execution. If the pre-execution audit write fails, the query does not run. There is no path where a query reaches your database without a corresponding audit row. That’s what makes the audit trail a complete record — including every denial, with the rule that decided it, the agent that asked, and the agent’s stated intent on the call.The two components
Midplane is one engine, exposed to agents by one server.The engine
@midplane/engine runs the parse → policy → audit → execute pipeline and makes the allow/deny decision. It’s embeddable, with no MCP dependency.The MCP server
@midplane/mcp-server wraps the engine and exposes it to agents over MCP (stdio and Streamable HTTP). It registers the tools your agent calls — query, list_tables, and describe_table — and routes each one through the engine.Two layers of enforcement
When an agent tries something destructive, two independent things stop it. Here’s adelete all users prompt to Claude Code, against a Midplane-fronted database:
1
The agent asks for confirmation
Hold on — that’s destructive and irreversible. Before I run DELETE FROM public.users, please confirm.
This is the agent’s own safety behavior. It’s useful, but it’s not a guarantee: a confirmation is just a prompt, and the next reply can be “yes.” It depends entirely on the agent’s good behavior.2
Midplane denies the query
After the user confirms, the agent runs the
DELETE. Midplane returns the denial:Midplane blocked it. The denial reason it returned: “Midplane denied this query because writes to tableThis is the layer that doesn’t depend on the agent cooperating. Theusersare not allowed by the table-access policy (usersresolves toread; mark itread_writein your MIDPLANE_POLICY_FILE to grant writes).” The DELETE was audited but not executed.
table_access rule denies the write because users is read, and the audit row for the attempt lands before the query would have reached the database.Postgres specifics
Midplane runs against Postgres — it’s the only dialect supported. The SQL is parsed by libpg_query, the real Postgres parser, so the AST the engine reasons about is the same one the server would build. Three guarantees keep that parse sound:search_pathis pinned topublic, pg_catalogper transaction, so bare table names resolve topublicas a hard guarantee — that’s what makes thepublic.<table>fallback in table access sound.SETis denied, so agent SQL can’t rewritesearch_pathon a pooled connection and change how bare names resolve.pg_catalogis policed like any other schema — a query into the system catalog is checked, not exempted.
dialect: key in the policy schema defaults to postgres and rejects any other value at boot. It exists for forward compatibility, not to select a backend today.
Layered, not a replacement
Midplane enforces policy on top of your database’s own permissions — give the connection a least-privilege database role too, so it’s the second layer, not the only one. And the engine decides allow or deny declaratively; human-in-the-loop approval workflows are on the Cloud roadmap, not part of the OSS engine.Where to go next
Write your first policy
Grant the reads and writes your agent actually needs.
Table access & guardrails
The access model, and the dangerous query shapes denied out of the box.
The audit trail
The durable record written before every query runs.