Repository navigation
[FEATURE] Fuzz the pgwire and MCP server interfaces as part of build CI #803
Description
Feature Description
Add fuzz testing for the two protocol-facing server surfaces of stackql and run it in build.yml:
- the PostgreSQL wire protocol server (
stackql srv, backed bygithub.com/stackql/psql-wire) - the MCP server (
stackql mcp, stdio and HTTP transports, backed bygithub.com/modelcontextprotocol/go-sdk)
Expected behaviour under malformed, truncated, oversized or out-of-sequence input: the server returns a protocol-level error (pgwire ErrorResponse; JSON-RPC -32700 / -32600 / -32601 / -32602) and stays available for subsequent requests. It must not panic, exit, hang, or grow memory without bound.
Proposed CI shape:
- PRs and pushes: short, time-boxed fuzz run per target (for example 30-60s each), failing the job on any crasher
- Scheduled (nightly or weekly): longer run with a persisted corpus
- Crashing inputs uploaded as artifacts, then committed to the seed corpus as regression cases once fixed
Example(s)
Both servers parse input from clients that stackql does not control - SQL drivers and BI tools on pgwire, agents and LLM-generated tool calls on MCP. Current CI exercises both through the robot suites using psql and the MCP client, which only send well-formed traffic, so parser and state-machine edge cases are not covered.
Inputs of interest:
- pgwire: startup /
SSLRequest/CancelRequestpackets, message length fields that disagree with the payload, simple query and extended query (Parse/Bind/Describe/Execute/Sync) messages out of order, unknown message types, mid-message disconnects - MCP: malformed or partial JSON-RPC frames, invalid UTF-8, requests before
initialize, unknown methods,tools/callwith missing, mistyped or oversized arguments for each tool, large payloads
A previous defect of this class: a malformed JSON-RPC frame on the stdio transport caused the read loop to exit on the decode error instead of returning -32700.
Possible Approaches or Libraries to Consider
- Go native fuzzing (
testing.F,go test -fuzz=... -fuzztime=...) for in-process targets: pgwire message decoding and session handling, MCP request handling. Seed corpus undertestdata/fuzz.go testaccepts one-fuzztarget per invocation, so CI loops over targets. mcp-server-fuzzerfor black-box protocol and tool-argument fuzzing against the built binary over stdio and HTTP, extending the earlier integration work rather than starting again.- A pgwire black-box harness against a running
stackql srv: build valid frames withgithub.com/jackc/pgx/v5/pgproto3(pgx is already a dependency) and mutate them, or useboofuzzfor a stateful protocol grammar, which fits alongside the existing Python test tooling. - Liveness assertion after each iteration or batch: a known-good query on pgwire and
tools/liston MCP must still succeed. - ClusterFuzzLite (runs in GitHub Actions) or OSS-Fuzz for continuous fuzzing with corpus management, reusing the same Go fuzz targets.
Additional context
- Placement: a
fuzzjob inbuild.ymlthat depends onlinuxbuildand consumes its binary artifact for the black-box runs, plus thego test -fuzztargets. Linux amd64 only is enough to start. - Protocol-level fuzz targets for pgwire may belong in
stackql/psql-wire, with this repo covering the integrated server. - The fuzz job should run with no provider credentials, against the mock provider setup the robot tests already use, so no fuzzed call reaches a real API.
- Keep the PR-time budget small (a few minutes total) so it does not materially extend the build.
- Done when: fuzz targets exist for both surfaces, they run on every PR and on a schedule, crashers fail the build and are retained as artifacts, and the approach is covered in the developer docs.
Activity
jeffreyaven commentedon Oct 12, 2026
closed with #835 and stackql/psql-wire#20