---
title: "Database Proxy"
description: "Postgres-family database proxying in agentsh: db_services, database rules, require_where guards, unavoidability, safe SQL redirects, and audit events."
doc_version: "1.0"
last_updated: "2026-05-22"
canonical: "https://www.agentsh.org/docs/database-proxy/"
---

# Database Proxy

agentsh provides database-aware enforcement for Postgres-family traffic. Declare protected upstreams with `db_services`, route governed processes through the per-session proxy, and apply connection and statement policy before SQL reaches the database.

## Current Scope

The shipped database runtime is **Postgres-family only**. PostgreSQL and Aurora Postgres use the primary supported path; Redshift and CockroachDB use the same protocol and classifier path with beta coverage. MySQL, MongoDB, Snowflake, BigQuery, Databricks, ClickHouse, MSSQL, Cassandra, Redis, and Oracle remain roadmap items.

| Area | Behavior |
| --- | --- |
| Protocol | PostgreSQL wire protocol v3, including Simple Query, Extended Query, SQL prepared statements, COPY handling, CancelRequest mapping, and transaction state tracking. |
| Dialects | `postgres`, `aurora_postgres`, beta `redshift`, beta `cockroachdb`. |
| Platforms | Linux runtime only today. Use native Linux, WSL2, or a Linux VM for enforcement. |
| TLS modes | `terminate_reissue`, `terminate_plaintext_upstream`, and `passthrough`. Statement rules require terminate mode. |
| Decisions | Connection and statement `allow`, `deny`, `approve`, `audit`; statement-level `redirect` for safe read-only relation replacement. |

        Scope of the unavoidability claim

The DB proxy is unavoidable for processes inside the agentsh-governed process tree, for declared `db_services`, assuming the agentsh supervisor and proxy are not compromised. Processes outside the session, undeclared databases, and compromised supervisor/proxy paths are out of model.

## Architecture

For each protected database service, agentsh starts a per-session Unix-socket listener and generates policy that redirects the agent's TCP connect to that listener. The proxy authenticates the connecting process with Linux peer credentials, evaluates startup and statement policy, forwards approved traffic upstream, and emits database audit events.

```text
agent process
  -> TCP connect to pg.internal:5432
  -> connect_redirect rewrites to session DB proxy Unix socket
  -> DB proxy authenticates peer SessionID with SO_PEERCRED
  -> startup packet and connection rules
  -> SQL classification and statement rules
  -> optional safe SQL rewrite for redirect decisions
  -> upstream PostgreSQL
```

The proxy owns PostgreSQL `BackendKeyData` remapping for cancellation. Clients receive synthetic cancel keys; CancelRequest side-channel connections are correlated back through the proxy and evaluated with `match_kind: cancel` rules.

## Point Agents At It

Do not give the agent a proxy socket path or a separate proxy host. Keep the agent's normal database configuration pointed at the declared upstream `host:port`, then run the agent inside an agentsh-governed session. When `policies.db.unavoidability` is `observe` or `enforce`, agentsh starts the per-session DB proxy and rewrites matching outbound connects to that proxy before traffic reaches the database.

```yaml
db_services:
  appdb:
    family: postgres
    dialect: postgres
    upstream: pg.internal:5432
    tls_mode: terminate_reissue

policies:
  db:
    unavoidability: enforce
```

The agent or application can keep using a normal Postgres connection string or libpq environment variables:

```bash
export DATABASE_URL='postgres://app:${DB_PASSWORD}@pg.internal:5432/app?sslmode=require'
# or:
export PGHOST=pg.internal
export PGPORT=5432
export PGDATABASE=app
export PGUSER=app

agentsh wrap --policy ./policy.yaml -- claude code "analyze the customer data query"
```

The upstream address in `DATABASE_URL`, `PGHOST`, or the application's DB config must match the declared `db_services.*.upstream`. Processes outside the agentsh session are not redirected, and traffic to undeclared database destinations is governed only by the rest of your network policy.

        Unix socket internally, TCP upstream for clients

agentsh does not expose a standalone TCP listener for agents to connect to. The agent-facing address remains the original Postgres TCP host and port; agentsh uses `connect_redirect` to move that connection onto a per-session Unix socket internally. The socket path is generated per session and authenticated with Linux peer credentials, so operators should not bake it into application configuration.

## Configuration

Database policy lives in the normal policy YAML. A complete Postgres read-only policy with safe user-table redirection looks like this:

```yaml
version: 1
name: postgres-guard

db_services:
  appdb:
    family: postgres
    dialect: postgres
    upstream: pg.internal:5432
    tls_mode: terminate_reissue

database_connection_rules:
  - name: allow-app-user
    db_service: appdb
    db_user: ["app"]
    database: app
    decision: allow

  - name: allow-cancel
    db_service: appdb
    match_kind: cancel
    decision: allow

database_rules:
  - name: deny-mutations
    db_service: appdb
    operations: [write, modify, delete, schema_create, schema_alter, schema_destroy]
    decision: deny
    deny_mode_in_tx: terminate

  - name: redirect-user-reads
    db_service: appdb
    operations: [read]
    relations: ["public.users"]
    match_object_resolution: catalog_resolved
    decision: redirect
    redirect:
      relation: public.safe_users

  - name: allow-resolved-reads
    db_service: appdb
    operations: [read]
    relations: ["public.safe_users", "public.orders"]
    match_object_resolution: catalog_resolved
    decision: allow

  - name: allow-safe-session
    db_service: appdb
    operations: [session, transaction]
    decision: allow

policies:
  db:
    unavoidability: enforce
    log_statements: parameters_redacted
    approval_statement_preview: redacted
    approval_statement_preview_chars: 200
    escalate_unknown_functions: true
```

### Service Fields

| Field | Required | Description |
| --- | --- | --- |
| `family` | Yes | Must be `postgres` today. |
| `dialect` | Yes | `postgres`, `aurora_postgres`, `redshift`, or `cockroachdb`. |
| `upstream` | Yes | Upstream `host:port`. This destination is also used to generate direct-egress deny rules. |
| `tls_mode` | Yes | `terminate_reissue`, `terminate_plaintext_upstream`, or `passthrough`. |
| `trusted_network` | No | Required when using plaintext upstream mode for a non-local destination. |
| `allow_function_call_protocol` | No | Opt into PostgreSQL FunctionCall protocol forwarding. Default is fail-closed denial. |
| `allow_gss_encryption` | No | Opt into GSS encryption passthrough. Statement visibility is degraded. |

## Connection Rules

`database_connection_rules` evaluate before any SQL statement is classified. They control normal connection startup, cancellation, and replication-mode startup.

| Field | Description |
| --- | --- |
| `match_kind` | `connect` (default), `cancel`, or `replication`. |
| `db_user` | List of startup user names. Available only in terminate TLS modes. |
| `database` | Startup database name. Available only in terminate TLS modes. |
| `application_name` | Startup application name glob. Available only in terminate TLS modes. |
| `client_identity` | agentsh internal client identity selector. Available in every TLS mode. |
| `decision` | `allow`, `deny`, `approve`, or `audit`. `redirect` is invalid for connection rules. |

Replication startup and GSS encryption are default-deny. If explicitly allowed, the proxy switches to passthrough and emits a `degraded_visibility_warning` because statement-level inspection is no longer available.

## Statement Rules

`database_rules` run after the Postgres classifier converts a statement into one or more effects. Effects include an operation group, optional subtype, object set, and object-resolution confidence. Evaluation is order-independent: every object slot in every effect must be covered by a non-deny rule, and any matching deny wins.

| Selector | Description |
| --- | --- |
| `operations` | Required. Operation groups or aliases such as `read`, `write`, `modify`, `delete`, `session`, `transaction`, `bulk_export`, `unknown`, `READ`, `MUTATE`, or `*`. |
| `subtypes` | Optional subtype filters, for example session-setting or unsafe-I/O subtypes. |
| `objects` | Syntactic object-name globs from the parsed statement. |
| `schemas` | Schema globs from syntactic or catalog-resolved references. |
| `relations` | Catalog-resolved canonical relation names formatted as `schema.name`. |
| `functions` | Catalog-resolved function identities formatted as `schema.name(identity_args)`. Use `schema.name(*)` for all overloads. |
| `match_object_resolution` | Resolution filter such as `catalog_resolved`, `qualified_syntactic`, `unqualified_syntactic`, `catalog_unresolved`, or `*`. |
| `require_where` | Optional syntactic guard for `modify` and `delete` rules. When `true`, the rule only matches top-level `UPDATE` or `DELETE` effects that include a top-level `WHERE`. |
| `deny_mode_in_tx` | For deny decisions inside a transaction: `terminate` or `rollback_then_continue`. |

`audit` is allow-with-observation. It forwards the statement and records `decision.verb: "audit"`. For dangerous operations, agentsh warns at policy load unless the rule sets `acknowledge_audit_on_dangerous: true`.

### Require WHERE

Use `require_where: true` for narrow mutation rules where accidental full-table updates or deletes are the main risk:

```yaml
database_rules:
  - name: allow-scoped-user-mutations
    db_service: appdb
    operations: [modify, delete]
    relations: ["public.users"]
    match_object_resolution: catalog_resolved
    require_where: true
    decision: allow
```

This covers statements such as `UPDATE public.users SET disabled = true WHERE id = 123`, but it does not cover `UPDATE public.users SET disabled = true`. The guard is syntactic: a top-level `WHERE` must exist, but agentsh does not prove that the predicate is selective or tenant-safe.

        Rule-local guard

`require_where` only changes whether that specific rule matches. If another unguarded `allow`, `audit`, or `approve` rule covers the same `modify` or `delete` effect, a no-WHERE mutation can still be permitted. Policy validation rejects `require_where` on rules whose operations expand beyond `modify` and `delete`.

## Runtime Redirect

Statement-level `decision: redirect` performs safe read-only Postgres relation replacement. It is intentionally narrow:

- The rule must expand only to read operations.

- The rule must select exactly one canonical source relation through `relations`.

- `match_object_resolution` must be `catalog_resolved`.

- The target must be declared as `redirect.relation: schema.name`.

- The service must use an eligible terminate-mode TLS configuration.

The runtime path is implemented for Simple Query and Extended Query `Parse`. Unsupported redirect shapes fail closed; the proxy does not forward the original SQL after a redirect decision that cannot be planned safely.

## Unavoidability

`policies.db.unavoidability` controls whether agentsh starts the proxy and installs generated routing and bypass-prevention rules for declared services.

| Value | Behavior |
| --- | --- |
| `off` | Do not install DB proxy bypass controls. |
| `observe` | Start proxy listeners and install generated routing and bypass-prevention rules, while treating DNS expansion failures as warnings for rollout testing. |
| `enforce` | Start proxy listeners and generated controls, and fail closed when required bypass-prevention context cannot be built. Traffic must route through the DB proxy. |

When enabled, agentsh synthesizes policy before the session starts:

- `connect_redirects` with `redirect_to_unix` route declared upstream `host:port` destinations to per-session DB proxy sockets.

- `network_rules` deny direct DB egress by hostname and resolved IP/CIDR for non-proxy processes.

- `unix_socket_rules` deny direct access to common local Postgres sockets such as `/var/run/postgresql/.s.PGSQL.*` and `/tmp/.s.PGSQL.*`.

- Convenience command rules catch common bypass tools such as `ssh -L`, `socat`, `kubectl port-forward`, `cloud-sql-proxy`, `gcloud sql connect`, `aws rds connect`, `chisel`, `gost`, `frpc`, `nc`/`ncat`/`netcat`, and host-network container launches.

The destination egress deny is the security boundary. The command rules are useful diagnostics, but custom tunnel binaries are still caught when they try to reach the declared upstream destination.

## Audit Events

Database proxying emits normal agentsh events, so session reports, event streams, and OCSF export can include database activity.

| Event | When emitted | Key fields |
| --- | --- | --- |
| `db_statement` | Each classified SQL statement or governed cancel request. | `db_service`, `db_user`, `database`, `effects`, `statement_digest`, `decision`, `result`, `tx_context`, redirect metadata. |
| `db_listener_auth_fail` | A process with the wrong SessionID connects directly to the proxy listener. | `peer_pid`, `peer_uid`, `peer_session_id`, `reason`. |
| `db_handshake_fail` | Startup packet or authentication forwarding fails closed. | `db_service`, `reason`, `error_code`, optional `sni_hostname`. |
| `degraded_visibility_warning` | Policy allows replication or GSS encryption passthrough. | `degraded_reason`, `db_service`, `client_identity`. |
| `db_bypass_attempt` | A generated unavoidability rule denies direct DB access. | `db_service`, `bypass_mode`, `destination`, `process_identity`, `suppressed_count`. |

Statement text redaction is controlled by `policies.db.log_statements`. The default is `parameters_redacted`; set `full` only where storing raw SQL and parameter values is acceptable.

## Policy Explain

Use the database explain command to inspect classifier effects, object resolution, per-object coverage, warnings, and the final decision offline:

```bash
agentsh policy db explain ./policy.yaml --service appdb --sql 'SELECT * FROM users'
```

Catalog fixtures can be supplied for local debugging of `relations` and `functions` selectors. Fixtures are snapshots, not live DB connections; the runtime proxy resolves catalog metadata through the protected database service.

## Limits & Roadmap

- Non-Postgres adapters are not runtime targets today.

- Column-level masking, row redaction, result-set DLP, and catalog-aware result metadata controls are future work.

- Credential brokering for database passwords is not part of the database proxy contract.

- Replication and GSS encryption remain default-deny unless explicitly allowed as degraded-visibility passthrough.

- TCP listener mode is not supported for the session DB proxy; agentsh uses Unix sockets with peer-credential authentication.

## Sitemap

- [Canonical HTML](https://www.agentsh.org/docs/database-proxy/)
- [Site map](https://www.agentsh.org/sitemap.md)
- [Full documentation](https://www.agentsh.org/llms-full.md)
