> ## Documentation Index
> Fetch the complete documentation index at: https://midplane.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Threat model

> An honest account of what Midplane's enforcement covers — and what it doesn't.

Midplane secures one path: the path through Midplane. This page is the honest account of which attacks that path covers, which it doesn't, what's still exposed once it does its job, and the claims we will and won't make. It mirrors [`THREAT_MODEL.md`](https://github.com/midplaneai/midplane/blob/main/engine/THREAT_MODEL.md) in the repo.

If an attacker can reach your database without going through Midplane, Midplane never sees it.

## Trust boundaries

Every query crosses the same boundaries in the same order:

```
[ Agent (Cursor / Claude Code / Claude Desktop) ]
            │  MCP (Streamable HTTP; stdio also available for self-host)
            ▼
[ Midplane MCP server ]
            │  in-process call
            ▼
[ Midplane engine: parse → policy → audit → execute → audit ]
            │  connection pool
            ▼
[ Your database ]
```

The agent is untrusted input; the [engine](/docs/how-it-works) is where the decision is made — and it audits the attempt and the decision **before** any query reaches your database.

For hosted, one boundary is added: your database URL is [encrypted at rest with a per-region AWS KMS key](/docs/cloud/security#managed-credential-encryption).

## Vectors covered

These are the attack shapes Midplane is built to stop. Each is pinned by the [adversarial corpus](/docs/security/adversarial-corpus).

<AccordionGroup>
  <Accordion title="SQL injection via raw text in MCP arguments" icon="syringe">
    The agent hands Midplane SQL as text. Midplane parses it into an AST with `libpg_query`, the real Postgres parser — **no regex on SQL, ever**. Anything that doesn't parse is denied by [`parse_error`](/docs/concepts/table-access#beyond-table-access). Midplane never enforces policy on text it can't read.
  </Accordion>

  <Accordion title="Multi-statement (stacked-statement) injection" icon="layers">
    The classic `SELECT 1; DROP TABLE users` vector — the same class disclosed by Datadog Security Labs against the deprecated Anthropic Postgres MCP. The parser counts statements, not semicolons, so comments and string literals don't inflate the count. [`multi_statement`](/docs/concepts/table-access#beyond-table-access) denies anything that parses to more than one statement.
  </Accordion>

  <Accordion title="Destructive writes against production" icon="trash">
    [`table_access`](/docs/concepts/table-access) denies any write whose target table isn't `read_write` in your policy — `INSERT`, `UPDATE`, `DELETE`, `MERGE`, `DROP`, `TRUNCATE`, `ALTER`, `GRANT`, `REVOKE`, `CREATE`, `EXECUTE`, `CALL`, `COPY`, and more, at any AST depth. With no policy at all, every write denies. Grants are explicit and per-table.
  </Accordion>

  <Accordion title="CTE-embedded writes" icon="eye-slash">
    `WITH x AS (DELETE FROM y RETURNING *) SELECT * FROM x` looks like a `SELECT` at the top level. The recursive AST walk catches the inner `INSERT`/`UPDATE`/`DELETE` at any depth and checks it against that target's permission.
  </Accordion>
</AccordionGroup>

## Vectors out of scope

These are deliberately not covered. Stating them plainly is the point — Midplane secures the path through it, and these sit outside that path.

| Vector                                      | Why it's out of scope                                                                                                                                                                                       |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Compromised database role**               | If the connection string belongs to a privileged role, Midplane operates on top of those permissions. It does not replace your database's role-based access control. Give the agent a least-privilege role. |
| **Supply chain on the published artifacts** | Midplane ships only as the `midplane/midplane` Docker image. Hub-side access control plus multi-arch image digests are the current mitigation.                                                              |
| **Agent paths that bypass Midplane**        | If the agent is jailbroken to reach the database some other way (a direct connection outside MCP), Midplane sees nothing. It secures the path through it, not all paths.                                    |
| **What the agent does with returned rows**  | Midplane denies and audits queries; what the agent does with the rows it legitimately received afterward is the agent's and the user's session's responsibility.                                            |
| **Metadata side-channels on the audit log** | Audit row count and timing are observable to your own infrastructure operators. Midplane makes no guarantee against an insider threat at the customer.                                                      |

<Note>
  "Out of scope" means Midplane makes no claim here — not that the risk doesn't exist. The same spirit runs through [what we claim, and what we don't](#what-we-claim-and-what-we-dont): we name what we don't cover rather than imply we cover everything.
</Note>

## Self-host vs hosted

Both deployment models run the same open-source engine, so the *enforcement* is identical. The trust difference is entirely about where the container runs and who holds your credentials — *where it runs*, not *what code runs*. See [Cloud vs self-host](/docs/cloud-vs-self-host) for the operational side.

| Concern                                    | Self-host                                                                                                                                      | Hosted                                                                                                                                                                                    |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where do my database credentials live?** | Your environment. They never leave your network.                                                                                               | Encrypted at rest with a per-region [AWS KMS key](/docs/cloud/security#managed-credential-encryption). Decrypted into process memory at query time, cached briefly, and never written to disk. |
| **Who can read my queries?**               | Only you.                                                                                                                                      | Midplane operators with database access, until the audit retention window expires (Free: 7 days; Pro: 30 days; Team: 90 days).                                                            |
| **What if Midplane is breached?**          | Not affected — self-host has no Midplane-controlled infrastructure.                                                                            | Encrypted credentials, no plaintext at rest. The residual exposure during a breach window is the in-flight working set: decrypted credentials in cache plus warm connection pools.        |
| **What if I want to leave?**               | Stop the container.                                                                                                                            | Delete the account; credentials are wiped immediately and audit data after the retention period. Export your audit log to CSV or JSON any time before then.                               |
| **Where does the audit log live?**         | An indexed Postgres database you run, read in the dashboard.                                                                                   | An indexed Postgres database in your region. The audit write is durable before it's indexed, so a transient indexing outage can delay search but never drops an event.                    |
| **Can I read the source?**                 | Yes — Midplane is [open core](/docs/cloud-vs-self-host#licensing): the engine and control plane are MIT. Only the Enterprise `ee/` layer is closed. | Yes — the same open-core code, hosted.                                                                                                                                                    |

### Residual exposure

Even when Midplane does exactly its job, some exposure remains. **Self-host** has none of Midplane's infrastructure in the picture — your database URL stays in your network and the audit log is a Postgres you run; the trust question reduces to whether you trust the MIT code you're running, which you can read before you run it. **Hosted** adds the residual that comes with managing credentials for you:

* **Decrypted credentials in process memory** for the cache TTL plus a short grace window — see [credential encryption](/docs/cloud/security#managed-credential-encryption) for the exact TTL. Never written to disk.
* **Connection pools held warm** during the cache window; a process compromise inside that window exposes warm connections.
* **The Postgres index of audit data** is queryable by Midplane operators with database access until retention expires.
* **In-flight session cookies and MCP tokens**, as for any HTTP service.

## What we claim, and what we don't

Security marketing is full of claims that don't survive contact with how a system actually works. Here's what Midplane does **not** claim, stated plainly:

* **"We don't see your queries."** We do. Every query is logged as the [audit record](/docs/concepts/audit-trail) — that's the product.
* **"Your data never leaves your database."** Query results pass through Midplane in memory on the way back to your agent. We don't persist results, but they exist in our process for the duration of the response.
* **"We're SOC 2 certified."** Not yet. SOC 2 work is on the long-term roadmap, gated by enterprise demand.

<Warning>
  If a security claim sounds absolute, read it twice. Midplane's value is in what it provably denies and audits — not in promises it can't keep.
</Warning>

These claims, by contrast, hold and are load-bearing:

* **AST-based parsing. No regex on SQL.** Not now, not ever. Every query is parsed into a real syntax tree before any rule runs.
* **The same Docker image runs in our infrastructure and on your machine.** The trust difference is where the container runs, not what code runs.
* **Audit-fail-fails-the-query.** If the pre-execute audit write fails, the query does not execute. No query reaches your database unaudited.
* **The OSS engine is MIT.** No copyleft, no BSL, no "source-available." The license stays MIT.

## Report a vulnerability

Found something the threat model missed? Report it privately — we treat security reports as the highest priority.

<Warning>
  Please **do not open a public issue** for a vulnerability. Email the details privately so we can coordinate a fix before disclosure.
</Warning>

Email **[security@midplane.ai](mailto:security@midplane.ai)** with a description and affected version(s), steps to reproduce or a proof-of-concept, and your proposed remediation if you have one. We aim to acknowledge within **2 business days**, coordinate disclosure, and credit reporters in the release notes once a fix ships.

<Tip>
  If you've found a SQL shape that bypasses a policy rule but isn't a deeper vulnerability, the fastest path is often a corpus contribution — a markdown row plus a failing test. We treat a known bypass as a release blocker. See the [adversarial corpus](/docs/security/adversarial-corpus).
</Tip>

**Scope.** In scope: the **engine** (parse, policy, audit, execute), the **MCP server** (stdio and Streamable HTTP), and the **published Docker image** (`midplane/midplane`). Out of scope: **third-party dependencies** (libpg\_query, the MCP SDK, the SQLite bindings — report those upstream), **the hosted product** (`midplane.ai`, covered by a separate disclosure policy), and **your own database, network, or agent configuration** (outside the engine's boundary).

## Next steps

<Columns cols={2}>
  <Card title="Adversarial corpus" icon="crosshair" href="/docs/security/adversarial-corpus" horizontal>
    The bypass attempts that must stay denied, and the queries that must stay allowed.
  </Card>

  <Card title="How Cloud secures your data" icon="lock" href="/docs/cloud/security" horizontal>
    Credential encryption, key isolation, and the exact cache TTL.
  </Card>
</Columns>
