Connector

MySQL access governance — every statement reviewed before it runs

Last updated

AccessFlow governs MySQL as a full query proxy: your team connects to AccessFlow, not to the database, and every statement is parsed, checked against a schema allow-list, risk-scored, routed to an approver, and only then executed — under column masking, row-level security and a row cap. The pipeline itself — parse, risk-score, route, approve, execute, audit — is the same for every relational engine and is described in full on the database access governance page. What follows is what is specific to MySQL.

Family
Relational
Query language
SQL
Runs as
In-process pooled JDBC
Default port
3306
Default SSL mode
REQUIRE
Install
One-click from the connector catalog

The driver and the connection

The MySQL driver is com.mysql:mysql-connector-j, resolved from the connector catalog rather than bundled — one click, fetched on first use, and verified before it loads.

The datasource takes host, port (3306 by default), database, username and password. The password is AES-256-GCM encrypted at rest and decrypted exactly once, at pool initialisation, or replaced entirely by a Vault, AWS Secrets Manager or Azure Key Vault reference. SSL defaults to REQUIRE, and a configured read replica takes the reads while writes stay on the primary.

AccessFlow builds the connection string from the datasource fields using the template the connector manifest declares, so what actually reaches the driver is predictable and reviewable:

connector manifest
jdbc:mysql://{host}:{port}/{database_name}
com.mysql.cj.jdbc.Driver

Drivers are resolved on demand — the first time a datasource of this type is created or tested — and each one gets its own classloader. Two datasources pointing at different driver versions, even two MySQL datasources, load disjoint copies and cannot interfere with each other through static state.

What AccessFlow parses

Statements are parsed to a syntax tree, and it is that tree — not the submitted text — the schema allow-list is checked against, so a table name buried in a comment or a quoted literal cannot smuggle access past it. MySQL accepts backtick-quoted identifiers, which the parser handles as identifiers rather than strings.

What it refuses

Multi-statement input is refused. That matters more on MySQL than most engines, because the driver has a connection flag that enables stacked queries — the classic SQL-injection amplifier — and AccessFlow simply never gives a submission the shape that would need it. The one exception is a BEGIN; … COMMIT; envelope wrapping a homogeneous write batch, which runs in a single transaction and rolls back as a unit.

Row-level security and masking

A policy is spliced into the WHERE clause with its value bound as a parameter, never concatenated. Joins, subqueries and set operations the rewriter cannot prove it has narrowed are refused with HTTP 422 rather than run unfiltered. Masks are applied after fetch, so a masked column never leaves the proxy in the clear.

What the rewrite actually does

A policy restricting invoices to the caller's business unit narrows the statement before MySQL ever parses it:

SQL
-- submitted
SELECT `id`, `total` FROM `invoices` WHERE `paid` = 0;

-- executed (policy: invoices.unit = the caller's business unit)
SELECT `id`, `total` FROM `invoices` WHERE (`paid` = 0) AND `invoices`.`unit` = ?;

Note what did not happen: the two predicates were not merged into one expression. The original is parenthesised and the policy ANDed onto it, so operator precedence in the user's own WHERE cannot widen what the policy was supposed to narrow.

Introspection, dry-run and cost estimates

A dry-run plans the governed statement with EXPLAIN FORMAT=JSON — the row-security predicate already spliced in — and maps the output into a plan tree without executing anything. The same path runs automatically on submission, and an UPDATE or DELETE additionally gets an exact affected-row count from a SELECT COUNT(*) rewrite that is itself subject to row security, so a reviewer sees the real blast radius rather than a guess.