# pg_brakes

[![CI](https://github.com/NikolayS/pg_brakes/actions/workflows/ci.yml/badge.svg)](https://github.com/NikolayS/pg_brakes/actions/workflows/ci.yml)
![license](https://img.shields.io/badge/license-Apache--2.0-3ddc97)
![postgres](https://img.shields.io/badge/PostgreSQL-14--18-3ddc97)

pg_brakes is a Rust safety gateway between AI agents and PostgreSQL. It puts a
deterministic floor below the agent: least-privilege PostgreSQL roles, a
proxy-only connection path, read budgets and cancellation, explicit write
certification, human approval, and tamper-evident audit evidence.

This is a serious MVP, not yet a production-safe control plane for arbitrary
PostgreSQL databases. The project deliberately separates three runtime postures:

- **Production Read Posture** is the only v0.1 posture eligible for production
  evaluation. Eligibility is conditional on proving all network, process
  identity, credential, transport, schema, replica, audit-retention, and
  operational prerequisites. A green doctor report is necessary but cannot
  attest external topology.
- **Experimental Write Posture** is non-production. Writes must fail closed unless
  they fit the tiny Certified Write Envelope, use an operator-provisioned
  rehearsal target that applyd distinguishes from the primary, carry exact
  catalog identity, receive exact-set approval, and have durable restart-safe
  Recovery Material. Clone provenance, freshness, and isolation remain operator
  attestations.
- **Development Posture** is for the local stack, primary transaction rehearsal,
  benchmark fixtures, and the local file anchor.

The machine-readable source of truth is [`capabilities.json`](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/capabilities.json),
rendered as the [current capability table](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/capabilities.md). The narrowing
decision is [ADR-0001](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/adr/0001-v0-1-safety-postures.md).

## Honest guarantees

- **Writes:** fail-closed writes for explicitly certified PostgreSQL shapes. The
  v0.1 target envelope is exact-key, single-table `UPDATE` and `DELETE` with a
  full-row pre-image, no primary-key mutation, and no triggers, rules, RLS,
  cascades, partitions, foreign tables, generated/identity columns, or unresolved
  PostgreSQL semantics. The implemented experimental path carries catalog OIDs,
  key metadata, exact quoted names, and a catalog fingerprint through exact-set
  approval, apply, recovery, and compare-and-swap revert. The posture remains
  experimental pending independent security review and production hardening of
  operator identity/workflow, audit retention, and clone lifecycle/governance.
- **Reads:** bounded disclosure, not zero disclosure. The proxy limits rows,
  bytes, time, and cumulative volume, while PostgreSQL grants constrain which
  data is readable. The cumulative meter is shared across connections inside one
  proxy process, but resets on restart and is not coordinated across replicas.
  Bytes already returned cannot be unread.
- **Audit:** tamper-evident, not tamper-proof. Covered proxy statement decisions,
  applyd lifecycle records and pre-commit recovery/attempt evidence, and
  successfully appended warden action records enter the authoritative `_meta`
  chain. Primary mutation commits
  and final audit appends are separate transactions, so a post-commit
  sink/finalization failure can leave ambiguous recovery state without a
  definitive final audit row. MCP-local checks, requests rejected before an
  enforcement process, and some early applyd lookups also do not enter the chain.

The frozen benchmark currently matches expected containment/allow outcomes for
32 dangerous scenarios and 11 adversarial-legitimate scenarios. It is a
self-authored regression corpus, not a proof over PostgreSQL's behavioral
surface or the product guarantee.

## What exists today

The generated [capability table](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/capabilities.md) is the status table; CI
fails if it drifts from [`capabilities.json`](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/capabilities.json). It records
implementation and test evidence for the WALL, read proxy, verified backend
transport, replica routing, warden, native Rust MCP, audit chain, write envelope,
recovery, anchor, and risk-gate maturity.

Important deployment limits remain:

- `pgb-cli doctor` blocks missing/open HBA, replica, and audit evidence outside
  Development Posture, but cannot inspect every external firewall, cloud network,
  Kubernetes policy, or process identity;
- no production WORM/transparency-log anchor Adapter exists;
- production postures require verified TLS on agent/proxy TCP and on all
  synchronous PostgreSQL TCP hops used by applyd, warden, audit, doctor, and the
  MCP audit reader. Unix sockets are accepted as local OS transport; explicit
  plaintext TCP is Development-only;
- frontend startup/tagged frames are allocation-capped, each agent frame has a
  receive deadline, and concurrent admission is capped per proxy process;
- the warden terminates tagged runaways and audits its own breaker state, but the
  proxy does not yet consume that state to shed new traffic. Its publicly mintable
  Rust marker is not an authenticated external breaker channel;
- cloud-specific RDS, Aurora, Cloud SQL, Supabase, and Kubernetes recipes are not
  yet production-qualified.

See [KNOWN_BYPASSES.md](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/KNOWN_BYPASSES.md) and
[KNOWN_DANGERS.md](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/KNOWN_DANGERS.md) before evaluating a deployment.

## Try the Development Posture

Prerequisites are Rust 1.90 (pinned) and PostgreSQL 14–18 client/server tools.
The launcher creates throwaway clusters on dedicated high ports and never uses a
database listening on `5432`.

```sh
git clone https://github.com/NikolayS/pg_brakes.git
cd pg_brakes
bash deploy/up.sh
```

`deploy/up.sh` starts the local proxy, applyd, warden, native Rust MCP server, and
throwaway PostgreSQL fixtures. It prints the exact `claude mcp add` command for
that run. Tear it down with:

```sh
bash deploy/down.sh
```

This flow is a Development Posture demonstration. Its local file anchor,
throwaway credentials, cleartext backend hop, and write examples are not a
production deployment recipe.

## Evaluate the read floor

The agent must connect to `pgb-proxy`, never directly to PostgreSQL. The minimum
shape is:

```text
agent -> native pgb-mcp -> pgb-proxy -> pgb_agent -> PostgreSQL
                                  |
                                  +-> authoritative _meta audit chain
pgb-warden --------------------------> tagged agent sessions
```

The proxy is default-deny for the SQL subset it recognizes and parses, but it is
not a PostgreSQL-native semantic resolver. On shared databases, curated function
and expression handling plus the proxy-only origin remain load-bearing; the
known generic type-literal AST mismatch is explicitly denied, while a current
green doctor catalog assessment and schema-change controls remain mandatory.
Client-declared Parse parameter types are limited to OID 0 or bootstrap catalog
OIDs so custom domain/type semantics cannot fire later on Bind.
The shipped dedicated-database lockdown does not replace that assessment. Prefer a physical read replica so the
credential is database-incapable of ordinary writes, while remembering that a
replica does not prevent external effects from callable functions.

`RolePolicy.select_whitelist` and `autonomy` are forward-compatible policy fields,
not runtime controls in v0.1. The proxy uses the selected role's budget only.
Explicit PostgreSQL `SELECT` grants plus doctor define the read surface, and every
Experimental Write still requires the signed single-use approval workflow.

A production-eligible deployment must independently establish all of the
following:

1. `pgb_agent` is least-privilege and can connect only from a dedicated proxy
   origin; other workloads cannot impersonate that origin.
2. Agent-facing TLS is enabled; proxy-to-replica TLS verifies the configured CA,
   hostname, and SCRAM server signature; and every synchronous PostgreSQL TCP
   DSN verifies certificate and hostname using platform roots plus
   `PGB_PG_ROOT_CERT`/`PGSSLROOTCERT` when needed. Plaintext TCP is refused.
3. Row, byte, time, and window budgets match the data sensitivity and workload.
   Capacity accounts for the fact that the cumulative window resets on proxy
   restart and is independent in each proxy process.
4. The authoritative `_meta` sink is a dedicated database isolated from the
   audited principal. MCP receives only the exact doctor-inspected
   `PGB_MCP_META_READER_DSN`, never `PGB_META_DSN`; retention/anchor limitations
   are accepted explicitly.
5. Warden credentials, cancellation, monitoring, and incident procedures have
   been exercised; a breaker alarm is not assumed to shed proxy traffic.
6. `pgb-cli doctor` is treated as evidence, not as complete network attestation.

Treat a deployment as eligible only after both doctor and the environment's
external network/process evidence are green; this is not blanket BYO PostgreSQL
support.

For a prepared environment, the dedicated launcher starts only the read proxy
and warden after a blocking doctor check; it provisions none of the prerequisites
above. Use the same marked state directory for its dedicated teardown:

```sh
PGB_READ_ONLY_STATE_DIR=/run/pg_brakes/read-only bash deploy/read-only-up.sh
PGB_READ_ONLY_STATE_DIR=/run/pg_brakes/read-only bash deploy/read-only-down.sh
```

`deploy/down.sh` belongs only to the throwaway Development stack. See the
[deployment patterns](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/deployment-patterns.md) before using the read launcher.

## Write posture

Write tools are not production-supported in v0.1. The implemented experimental
Certified Write Envelope is deliberately boring:

- one ordinary table;
- `UPDATE` or `DELETE` only;
- a schema-qualified plain target with no alias, joins, `UPDATE FROM`, `DELETE
  USING`, `RETURNING`, `ORDER BY`, `LIMIT`, or target modifiers;
- equality on the sole `int4` primary key or an explicit literal key list;
- literal-only `UPDATE` assignments: no expressions, functions, casts,
  `DEFAULT`, placeholders, or column references;
- the actual catalog-derived, exactly quoted primary-key identity carried through
  certification, rehearsal, guarded apply, Recovery Material, and revert,
  together with relation/key OIDs and a catalog fingerprint;
- no primary-key update;
- no triggers, rules, cascades, RLS, partitions, foreign tables,
  generated/identity columns, mutating foreign-key actions, `CHECK`/exclusion
  constraints, secondary/expression/partial indexes, custom table access
  methods, or unresolved dependencies; only a heap table with its primary-key
  index is accepted;
- restorable columns limited to `int2`, `int4`, `int8`, `text`, `varchar`,
  `bpchar`, `name`, and `bytea`;
- an operator-provisioned rehearsal target that applyd distinguishes from the
  primary, plus exact key-set approval;
- apply success only after durable Recovery Material exists;
- signed-approval, single-use, restart-safe revert that revalidates catalog
  identity and compare-and-swaps captured after-images with exact cardinality.

Anything else is outside the envelope and must be refused before candidate SQL
executes. A rollback rehearsal on the primary is Development Posture because it
cannot undo sequences or external side effects.

Experimental applyd refuses startup unless `clone.provider: dblab` and a
distinct `PGB_REHEARSAL_DSN` resolve to a different running PostgreSQL instance,
identified by `(pg_control_system().system_identifier,
pg_postmaster_start_time())`. Unix/TCP aliases and separate databases on the
same postmaster are refused; physical clones with their own postmaster are
admitted. Both roles therefore need `EXECUTE` on `pg_control_system()` (a stock
PostgreSQL `PUBLIC` default that hardened deployments may revoke). This check
does not prove the target is a fresh, governed DBLab clone or isolate it from
external systems; those are operator responsibilities. Applyd
stages the typed inverse plus SHA-256 digest in `_meta` before primary commit and
returns a Recovery Receipt only after the record reaches `APPLIED`. Revert is an
operator-only RPC authorized by a signed, expiring, durable single-use approval;
runtime verification uses catalog identity, after-image compare-and-swap, and
exact cardinality. The restart integration test separately compares restored
state byte-for-byte. These controls do not make writes production-supported.
Primary commits, recovery-state finalization, and final audit appends are not one
atomic transaction; post-commit failure can leave ambiguous recovery material and
no definitive final audit row.

Approval is an owner-only CLI workflow, not an operator UI or identity-provider
integration. The CLI first obtains and displays applyd's canonical binding—the
exact SQL, role/session, catalog identity, cap, typed key set, nonce, and expiry—
then requires a typed confirmation (or an explicit non-interactive change-id),
signs locally, and submits only signed evidence:

```sh
export PGB_APPLYD_SOCKET=/run/pgb/applyd.sock
export PGB_APPROVER_SEED_HEX='<32-byte Ed25519 seed, hex>'
pgb-cli approve <request-id> <approver-id>
# automation must name its evidence explicitly:
pgb-cli approve <request-id> <approver-id> \
  --non-interactive-evidence <change-id>
```

Applyd revalidates the stored request, inspected binding, execution role,
configured public key, hard lifetime, signature, and single-use state. The seed
never enters applyd or the agent path. The caller-supplied approver identity must
differ from the requester, but there is no separate WebAuthn/IdP attestation in
v0.1.

The applyd socket path must be absolute. A missing parent is created `0700`; an
existing parent must already be a non-symlink directory with no group/other
permissions and is never chmod'd into compliance. The socket itself is `0600`.

The operator executes a Recovery Receipt through the CLI, outside the agent/MCP
path:

```sh
export PGB_APPLYD_SOCKET=/run/pgb/applyd.sock
export PGB_APPROVER_SEED_HEX='<32-byte Ed25519 seed, hex>'
pgb-cli revert <recovery-id> <approver-id>
```

The CLI generates a 32-byte CSPRNG nonce and a five-minute expiry, signs the
domain-separated recovery approval locally, and sends applyd only the recovery
id, approver id, nonce, expiry, and signature. The private seed never crosses the
socket. `PGB_REVERT_APPROVAL_TTL_MS` may shorten the lifetime or raise it up to
the enforced 30-minute maximum.

## Build and test

```sh
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --locked
python3 scripts/check-capabilities.py
```

Real-PostgreSQL tests are opt-in:

```sh
PG_BRAKES_IT=1 cargo test --workspace --locked
```

CI runs the integration safety suite across PostgreSQL 14, 15, 16, 17, and 18.
The native MCP server is a Rust workspace member, so normal workspace build,
test, Clippy, and dependency-policy gates cover it.

## Documentation

- [Capability registry](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/capabilities.md) — generated current-state table.
- [Architecture](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/architecture.md) — runtime postures and enforcement flows.
- [Quickstart](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/quickstart.md) — development evaluation and production-read
  eligibility checks.
- [Components](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/components.md) — concise crate map.
- [Development guide](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/development.md) — test and contribution process.
- [Historical specification](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/docs/spec/SPEC.md) — frozen design history; current
  claims are governed by ADR-0001 and the capability registry.

## License

[Apache-2.0](https://github.com/NikolayS/pg_brakes/blob/65d169e0177015a8b5ddb73b541a5112d7d6fdd1/LICENSE). Dependencies are restricted to the approved permissive
license set by `cargo deny`; GPL and AGPL dependencies are rejected.
