Skip to main content
User-facing highlights for each release of the Midplane engine. For the full version-by-version detail — including breaking changes, audit schema migrations, and the reasoning behind each change — see CHANGELOG.md in the repo.
2026-07-22
Self-signed and private-CA Postgres connect on sslmode=require. The engine now follows libpq’s TLS semantics instead of node-postgres’s stricter default: sslmode=require (and prefer) encrypts without verifying the certificate, so a database presenting a self-signed or private-CA cert connects rather than failing with an opaque self signed certificate the moment an agent runs its first query. Supplying a root CA (sslrootcert=…) still verifies against it, verify-ca / verify-full are unchanged, and disable turns TLS off. A DSN with no sslmode behaves exactly as before.See connecting to your Postgres.
2026-07-01
Column masking moves to a source-side query rewrite. Masking is now applied by rewriting the query before it runs, instead of redacting the result after execution. This fixes the blast radius of the earlier approach: a mask on one column no longer blanket-denies every aggregate or computed query, so count(*), joins, and GROUP BY over a masked table run normally. It also closes the filter-inference bypass — a predicate or sort on a masked column now operates on the masked value. Shapes the engine can’t safely rewrite — views, whole-row serialization, opaque functions — still fail closed with a column_masking reject, now before the query runs. On Cloud this is on for every masked database; self-host opts in with MIDPLANE_MASK_SOURCE_REWRITE=1 (or per database, mask_source_rewrite: true).See column masking.
2026-06-24
Expanded the column-masking transform catalog. On top of 0.12.0’s full-redact and consistent-hash, five more transforms ship: null-out (type-preserving NULL), partial (reveal a prefix/suffix — this subsumes the retired keep-last-4), generalize (truncate a date or bucket a number), pseudonymize (deterministic realistic fakes — email, name, phone), and noise (proportional numeric jitter, the only non-deterministic transform). Each parametric rule declares the column type it accepts and fails closed — applying a transform to an out-of-domain column rejects the whole result set rather than silently passing it through.SELECT … INTO now denies under a read-only policy. It’s the legacy spelling of CREATE TABLE … AS SELECT, so it’s governed as a write to the new table — a read-only agent can no longer materialize a table this way. Previously the two spellings parsed differently and SELECT … INTO slipped through; they’re now indistinguishable to policy.See column masking and what Midplane blocks.
2026-06-19
Column-level dynamic masking. Redact sensitive values in query results, declared per column on top of table_access. The engine resolves each output field back to its true base column by provenance — so aliases, joins, and SELECT * are all covered — and applies the transform after execution, before any rows leave the engine. It’s fail-closed: a result shape it can’t prove safe to mask (a view, an aggregate, a whole-row output) is withheld entirely (a column_masking reject), never leaked. Initial transforms: full-redact, consistent-hash, and keep-last-4 (the catalog expands in 0.13.0). Masks need a per-deployment MIDPLANE_MASK_SALT; the engine refuses to boot a database that declares masks without it.See column masking.
2026-06-18
Per-agent least-privilege database scope. The engine narrows each session to the databases a credential was granted, clamping writes to read-only where the grant says so. This is what lets each agent reach only the databases you allow, at the access you allow — chosen at OAuth consent or token creation.See roles & team access.
2026-06-12
The CLI grows into the OSS operator surface. OSS Midplane has no web UI by design — so the midplane binary becomes the product UI. Every new command reuses the real enforcement path rather than reimplementing it. Output is human-readable on a TTY and JSON lines when piped (--json / --pretty to force either; color respects NO_COLOR).
  • midplane init — an interactive setup wizard: introspect the database, pick per-table grants, and write a schema-validated, linted policy file (the DSN is never echoed or written).
  • midplane query — send one statement through the server exactly as an agent would, with the same policy decision, audit row (agent_name=midplane-cli), and deny message. A verification harness, not a database client.
  • midplane doctor — preflight + smoke checks in boot order, ending in an end-to-end MCP canary that proves the audit pipeline records the call.
  • midplane audit denies and midplane audit show <query_id> — “what got blocked and why,” and the full event chain for one query.
  • midplane policy test --server — dry-run against a running server’s currently-loaded policy via /admin/dry-run, not just a file.
See the CLI reference.
2026-06-11
Dangerous-statement guardrails — on by default. A new policy rule, dangerous_statement, denies categorically-destructive operations regardless of table_access — so a table you’ve granted read_write can’t be turned into a whole-table wipe or a schema change. block_unqualified_dml denies a DELETE/UPDATE with no WHERE clause (caught at any AST depth, including inside a CTE); block_ddl denies DROP/TRUNCATE/ALTER (CREATE is not blocked). Both default on, even with no policy file; opt out per flag in the guardrails block.Behavior change (safe-by-default): upgrading a deployment that marks tables read_write will start denying those shapes on those tables. The no-YAML default already denied all writes, so out-of-the-box outcomes are unchanged. See upgrading.
2026-06-10
POST /admin/dry-run — test a policy decision without executing. “Would this SQL be allowed or denied?” answered by the same code that enforces policy at query time (parse → classify → table_access → guardrails → decide), stopped before execution — it never opens a database connection and never runs a statement. Ask with a literal sql or with (table, action) probes; the response carries a verdict, the matched_rule, and a policy_hash. This powers the Cloud dashboard’s policy test panel and midplane policy test --server.See HTTP endpoints.
2026-06-05
Configurable HTTP bind host — MIDPLANE_HOST. The Streamable HTTP transport was hard-bound to IPv4 (0.0.0.0); the bind host is now read from MIDPLANE_HOST. It defaults to 0.0.0.0, so self-host and local boot are unchanged — set it to :: to serve a dual-stack (IPv6 + IPv4) network, such as an IPv6-only private network. See environment variables.
2026-06-04
midplane policy CLI — author, trust, and dry-run a policy file without hand-editing YAML blind. Four subcommands on the existing midplane binary:
  • policy init — scaffold a commented policy file; with --url, list every table in the public schema (read-only) and emit each under table_access. The DSN is never printed or written to the file.
  • policy validate — parse the YAML against the same schema the server boots with. Offline-safe: an unset ${VAR} doesn’t fail a structural check.
  • policy lint — security-posture findings beyond schema validity (writes granted, policies that restrict nothing). Errors exit nonzero for CI gating.
  • policy test --sql "..." — run a query through the real engine against the file’s policy and print the decision, the rule, and the exact agent-facing denial message — no database connection.
CTE self-shadow read bypass fixed (table_access). A non-recursive CTE’s name doesn’t bind inside its own body, so WITH audit_log AS (SELECT * FROM audit_log) SELECT * FROM audit_log reads the real audit_log in its body — which must be policy-checked. That body read was previously mistaken for a reference to the CTE itself and skipped, allowing a read of a denied table. Now a CTE’s own name is excluded while walking its body; recursive CTEs keep their self-reference, and legitimate shadowing (a body that reads nothing real) still allows.See the policy CLI and what Midplane blocks.
2026-05-20
Per-token audit attribution. The Cloud proxy injects an X-Midplane-Token-Id header on every forwarded request, naming the token that opened the session. The engine stamps mcp_token_id on every audit row from that session — so customers issuing multiple named tokens per connection (one for CI, one for an interactive analyst) can attribute every query to the token that authorized it. The column is additive and nullable; self-host and non-MCP callers read NULL.See tokens and the audit events reference.
2026-05-02
Structured per-call intent. intent is now a required structured field on the query tool’s input schema. Agents fill it the same way they fill sql, validated by the MCP SDK before the engine runs — so every audit row carries an LLM-supplied “why” (observed fill rate: 100% on stock Claude Code traffic). This replaced three earlier resolution channels (MCP _meta, SQL comment hint, HTTP header) that no off-the-shelf agent actually populated.See the audit trail.
2026-05-01
Agent identity on every audit row. The MCP initialize handshake already sends clientInfo: { name, version }; the audit row now stores them as separate agent_name and agent_version columns (rather than one combined string that would kill grouping and filtering). No agent action required — every MCP client already sends clientInfo.See the audit events reference.
2026-04-30
Multiple databases. One midplane/midplane container can now serve N databases through a single MCP endpoint. Configure them under a top-level databases: block, each with its own url and table_access. ${ENV_VAR} interpolation keeps DSNs out of YAML files.Dynamic tool surface. With two or more databases, query and describe_table gain a required database arg, list_tables gains an optional one (omitted = fan out across all databases), and a new list_databases tool reports each database’s policy summary. Single-database setups see no change. Every audit row is tagged with the originating database name.See multiple databases.
2026-04-29
First tagged release. Three policy rules — table_access, multi_statement, and parse_error — sit on the audit-before-execute pipeline, pinned by an adversarial SQL corpus covering CTE-hidden writes, stacked-statement injection, parser edges, and exec side-effects. 100% line coverage on the policy surface.Ships as @midplane/engine (the embeddable pipeline) and @midplane/mcp-server (three MCP tools over stdio and Streamable HTTP), plus a multi-arch, non-root production Docker image. Agent compatibility verified across Cursor, Claude Code, and Claude Desktop.
  • Per-table read/write policy via YAML, with schema-qualified keys and default: read.
  • COPY and LOCK deny unconditionally — side effects the per-table YAML can’t grant.
  • Audit-before-execute ordering: if the pre-execute audit write fails, the query never runs.
  • midplane audit CLI (tail | stats | since) to read the audit log without writing SQL.
  • Anonymous, aggregate telemetry, disabled with MIDPLANE_TELEMETRY=0.

Next steps

Upgrading

How to move a self-host instance between versions, including audit schema migrations.

Contributing

The dev setup and the highest-leverage contribution.