Skip to main content
The policy file is the YAML you mount via MIDPLANE_POLICY_FILE. It holds your per-table access rules, the dangerous-statement guardrails, and per-column masks. With no file at all, every read is allowed, every write is denied, and the guardrails stay on — so the file opts specific tables into writes, masks specific columns, and (rarely) relaxes a guardrail. A policy file has two shapes:
  • Single-database (the common case) — a top-level table_access block. The DSN comes from the DATABASE_URL env var.
  • Multi-database (0.2.0+) — a top-level databases: array, one entry per database, each with its own url and table_access. When databases: is present, the top-level table_access and the DATABASE_URL env var are ignored.
The file is validated against the exact same schema the server boots with. Run midplane policy validate to check a candidate file, or midplane policy lint for a security-posture review.
Unknown keys are silently ignored, not rejected. A misspelled key like table_acess is dropped and the default applies, so validate won’t catch the typo — and a silently widened access slips through. Double-check spelling.

table_access

Per-table read/write policy. Each referenced table resolves to a level, and a query is denied if any referenced table fails its required permission. See table access for the matching semantics.
enum
default:"read"
Access level for any table not listed under tables. One of deny, read, or read_write.
  • readSELECT allowed; any write denied.
  • read_writeSELECT and INSERT / UPDATE / DELETE allowed.
  • deny — no access at all, not even SELECT.
map<string, enum>
default:"{}"
Per-table overrides. Keys are table names; values are deny, read, or read_write. Keys may be schema-qualified (public.users, stripe.charges) or bare (users); the qualified key is matched first. See table access lookup order for how bare names resolve to public.

guardrails

Categorical blocks on destructive operations that fire regardless of table_access (0.9.0+) — the safety net so a table you’ve granted read_write can’t be turned into a whole-table wipe or a schema change. The dangerous_statement rule reads this block. Both flags are on by default, even when the guardrails section (or the whole file) is omitted, so a self-host deployment is protected out of the box.
boolean
default:"true"
Deny DELETE / UPDATE with no WHERE clause — the whole-table write. Any WHERE (even WHERE true) makes the statement qualified; this is the missing-WHERE footgun specifically, not a predicate-strength check. Detected at every DELETE/UPDATE node, including ones nested in a data-modifying CTE.
boolean
default:"true"
Deny DROP, TRUNCATE, and the whole ALTER family (including ALTER … RENAME, ALTER TYPE … ADD VALUE, ALTER ROLE). CREATE is not blocked. Each flag defaults true independently, so guardrails: { block_ddl: false } keeps unqualified-DML blocking on while allowing DDL.
Disabling a guardrail widens what a read_write table can do. midplane policy lint warns on an explicit opt-out (block_ddl: false / block_unqualified_dml: false).

column_masks

Per-column masks (0.12.0+) — a sibling of table_access / guardrails that transforms specific column values before they ever reach the agent. The block is shaped "schema.table" → (column → mask rule). A masked column must still be readable under table_access; masking shapes a value you’re already allowed to read, it never grants access. See column masking for the transform catalog and where it sits in the pipeline.
map<string, map<string, mask rule>>
default:"{}"
Outer keys are table references, resolved like table_access: a schema-qualified key (public.users) is matched first, and a bare reference (users) resolves to public. Inner keys are column names; each value is a mask rule (below).
A mask rule is one of two shapes:
  • A preset — a bare string naming a non-parametric transform: full-redact, null-out, or consistent-hash.
  • A tagged object — a map with a t: key naming a parametric transform plus its parameters: { t: partial, keepStart?, keepEnd?, glyph? }, { t: generalize, granularity }, { t: pseudonymize, kind }, or { t: noise, ratio }.
See column masking for what each transform does, its parameter defaults, and which types it applies to.
A policy with column_masks carries a requires_features: [column_masks] marker that the engine writes and requires: an engine that can’t enforce masking refuses the policy at boot rather than silently serving unmasked values, so a downgrade fails loud instead of leaking. Masking also needs a per-deployment salt — the engine refuses to boot a database that declares column_masks without MIDPLANE_MASK_SALT.
boolean
default:"false"
Apply this database’s masks by rewriting the query at the source (0.14.0+) rather than redacting the result after execution. Overrides the engine-wide MIDPLANE_MASK_SOURCE_REWRITE default for this database; when set, the policy also carries a requires_features: [mask_source_rewrite] marker. Cloud sets it on for every masked database. In a multi-database policy it lives under each databases[] entry.

databases

A top-level array (0.2.0+) that serves multiple databases through one MCP endpoint. When present, the top-level table_access and the DATABASE_URL env var are ignored (Midplane warns at boot if both are set). See multiple databases for the operational guide.
string
required
The database’s logical name, surfaced to agents as the database argument on MCP tools and stamped on every audit row. Must match the regex ^[a-z][a-z0-9_-]{0,31}$ — lowercase, starts with a letter, dashes / underscores / digits allowed, max 32 characters. Must be unique within the array. __default__ is reserved for the legacy single-database path and may not be used.
string
required
The connection string for this database. Supports ${ENV_VAR} interpolation — ${PG_PRIMARY_URL} is replaced with that env var’s value at boot. A reference to an unset or empty env var fails the boot loudly (so a typo’d reference never boots with an empty DSN). Keep the literal DSN out of the file; reference an env var instead.
enum
default:"postgres"
The SQL dialect for this database. Currently only postgres is accepted — the key exists for forward compatibility with additional dialects. Omit it for Postgres. Any other value (for example mysql) fails the boot at schema time. See the policy engine.
object
Per-database table_access, with the same default / tables shape as the top-level block above.
object
Per-database guardrails, with the same shape as the top-level block above. Omitting it keeps both guards on for that database.
object
Per-database column_masks, with the same "schema.table" → (column → mask rule) shape and the same boot requirements as the top-level block above. See serve multiple databases.

Validation rules

Beyond the per-key types, the loader enforces these semantic rules. Each fails the boot — and midplane policy validate — with a precise message:
Env-var interpolation failures (${VAR} unset) only matter at connect time, so an offline validate / lint treats them as a pass. The boot path enforces them.

Full examples

The inline snippets above show each block; for complete copy-paste recipes — single-DB and multi-database — see the policy cookbook.

Policy CLI

Scaffold, validate, lint, and dry-run a policy file.

Environment variables

Where MIDPLANE_POLICY_FILE and DATABASE_URL fit in.