Skip to main content
A mysql lane decodes the MySQL client/server protocol: the SQL a client sends over COM_QUERY, the statements behind every prepared-statement id, and the result sets the server returns in both the text and the binary encoding.
config.yaml

What the codec reads

A MySQL byte means different things depending on what the handshake negotiated and on the command in flight, where a pgwire message declares its own type and length. The codec tracks both, one instance per connection, across both directions:
  • Capabilities. The codec latches the negotiated set from the client’s handshake response. CLIENT_DEPRECATE_EOF alone decides whether a result set ends with an EOF packet or an OK packet that begins with the same byte.
  • Prepared statements. COM_STMT_EXECUTE carries no SQL, only the numeric id the server assigned in its reply to COM_STMT_PREPARE. The codec keeps that map and attributes each execute to its text, which is the path ORMs take.
  • Multi-statements. Connector/J and most ORMs negotiate CLIENT_MULTI_STATEMENTS by default, so SELECT 1; DROP TABLE users arrives as one COM_QUERY. The codec splits it with MySQL’s own lexical rules (backtick identifiers, # comments, backslash escapes) before classifying, so a statement hidden after a string literal cannot ride through under a select classification.

What it refuses

Three negotiated features would make later bytes unreadable, and the codec refuses the stream instead of forwarding what it cannot inspect. Each refusal closes the connection with an operator-facing reason: A codec that forwards what it cannot parse turns into a bypass: the session works, no policy runs, the audit trail records nothing, and the gap stays invisible until an audit finds it. A refusal surfaces one clear error at the moment of the negotiation.

TLS on each leg

MySQL negotiates TLS in-band, and unlike pgwire the server greets first: the client’s upgrade request is a truncated handshake response rather than a self-describing packet. The lane terminates neither side of that exchange: Both hops carry the protocol in the clear by design, so keep them where that is acceptable: loopback, a unix socket, one pod, or a network a NetworkPolicy narrows. The Transport section covers the boundary each choice draws.

Masking

The codec re-frames result sets in both encodings, because the two share nothing:
  • Text protocol rows are length-encoded strings, and the codec rewrites them value by value.
  • Binary protocol rows, the encoding prepared statements return, carry a NULL bitmap and type-driven values. The codec rewrites string-typed columns and leaves a numeric column alone rather than corrupting it.
A NULL survives masking as a NULL: re-encoding it as an empty string would turn “no value” into “the empty string” and change what the client computes. Column rules match the names the server declared in the result set’s column definitions.

Denials

A denied statement returns a native ERR_Packet, and the session stays usable afterwards:
Dropping the socket instead would print “Lost connection to MySQL server during query”: an outage message for a policy decision, which sends the developer to support instead of to their own query.

The Envoy lane

Envoy’s MySQL filter parses no SQL, so the lane is plain tcp_proxy, same shape as the Postgres one with the cluster pointed at the mysql listener’s port:
envoy.yaml
Verify the lane from a client:

Next

Config File Reference

Every listener field, inheritance between lanes, and what startup refuses.

Data Masking

Strategies, entity types, and the column-versus-detection tradeoff.