Skip to content

feat: enforce statements on the PostgreSQL wire protocol - #2

Merged
mickamy merged 5 commits into
mainfrom
feat/pg-wire
Aug 26, 2026
Merged

feat: enforce statements on the PostgreSQL wire protocol#2
mickamy merged 5 commits into
mainfrom
feat/pg-wire

Conversation

@mickamy

@mickamy mickamy commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

Teaches the proxy the PostgreSQL protocol so it can see and refuse individual statements. The relay is now driven by a dialect: internal/pg parses the startup packet, relays authentication untouched, frames the simple and extended query protocols, and consults a handler on every statement. No policy is wired yet, so the handler allows everything; this is the seam the policy and the ledger attach to.

Design

  • internal/wire holds dialect-neutral types (Startup, Statement, Verdict, Handler) and the Dialect/Session interfaces. Nothing in the proxy, and nothing a future policy or ledger touches, imports a database-specific package. MySQL is a planned second dialect and slots in behind the same interface.
  • internal/pg implements the interface for PostgreSQL v3.
  • internal/proxy drives one Session per connection: bounded handshake, then Frontend and Backend run concurrently with half-close propagation.

Statement handling

  • The handler is consulted on Query (simple protocol) and on Parse (the only extended-protocol message that carries SQL). FunctionCall is refused (fast-path is not supported).
  • Extended messages are buffered until their Sync, so a batch is accepted or rejected as a unit. If any statement in a batch is denied, nothing in it reaches the upstream and the client receives one ErrorResponse followed by the ReadyForQuery from a forwarded Sync: no partial execution, no desync, no hung upstream.
  • Responses are kept in request order by a per-session slot queue, enqueued before a request can reach the upstream so a reply is never observed before the slot that accounts for it.
  • A denial synthesizes an ErrorResponse the client can read; the transaction status in the following ReadyForQuery reflects the upstream's last report.

Safety and limits

  • Authentication (including SASL/SCRAM) is relayed verbatim; the proxy never sees credentials in a form it interprets.
  • Memory is bounded: statement bodies are read incrementally, the batch buffer is capped (8 MiB; larger batches forward early), and the response queue is capped (512; overflow closes the session).
  • The proxy is plaintext on both sides and answers SSLRequest with N, so sslmode=prefer clients fall back to plaintext. The CLI warns when listening outside loopback. TLS termination is a later step.

Testing

  • make test and make lint pass; the protocol tests were also run under -race -count=100.
  • internal/pg: password and SASL handshakes, cancel-request pass-through, upstream rejection, unknown protocol, allowed and atomically-rejected batches, denial-then-recovery, oversized statements, FunctionCall refusal, copy-data pass-through, and transaction-status tracking.
  • internal/cli: an end-to-end test that runs the command against a fake PostgreSQL over TCP.
  • Verified by hand against postgres:16 and postgres:18 with psql: simple multi-statement queries, extended \bind, pipeline mode with a mid-pipeline server error and recovery, and COPY in and out.

Not in this PR

Statement classification and policy (the handler is always-allow), the access ledger, TLS, and MySQL.

@mickamy
mickamy requested a balanced review from Copilot August 26, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mickamy
mickamy merged commit 85407b1 into main Aug 26, 2026
8 checks passed
@mickamy
mickamy deleted the feat/pg-wire branch August 30, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants