Skip to content

fix(analyzer): adopt sqlglot-go v0.21.0 parser fixes, pin every MySQL statement resolution - #89

Merged
sjincho merged 2 commits into
mainfrom
sjcho/test/mysql-statement-coverage
Aug 4, 2026
Merged

fix(analyzer): adopt sqlglot-go v0.21.0 parser fixes, pin every MySQL statement resolution#89
sjincho merged 2 commits into
mainfrom
sjcho/test/mysql-statement-coverage

Conversation

@sjincho

@sjincho sjincho commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Adopts sqlglot-go v0.21.0 and pins the resolution of every MySQL statement kind
so the two stay honest with each other.

The parser fixes (sqlglot-go v0.21.0, ridi-oss/sqlglot-go#59)

Three MySQL parse deviations proxy-monster's enforcement depended on:

  • START REPLICA / START SLAVE / START GROUP_REPLICATION parsed as a
    Transaction node — the word after START taken as a transaction mode,
    indistinguishable from START TRANSACTION. The analyzer classified them
    SESSION and passed them through with connect only, so a principal who could
    connect could start replication with no grant. They now parse as Command and
    fail closed. This is a real authorization gap, closed.
  • STOP / FLUSH / UNLOCK INSTANCE / XA / BINLOG / HELP / RESTART /
    SHUTDOWN were coerced into expression nodes (Alias/Column); now Command.
    Already fail-closed here, now robustly so.
  • TABLE t (the 8.0.19 SELECT * shorthand) parsed as Alias; now Select, so
    a real read is enforced and masked instead of denied.

Verified against the actual PR branch before merge, then re-measured after the
version bump.

The coverage test

analyzer/probe/mysql_statement_coverage_test.go enumerates every MySQL 8.0/8.4
statement kind a client can send — the list taken from the reference manual's
§15, not the analyzer's own dispatch — runs each through the real analyzer, and
asserts its resolution. A statement the analyzer never names is caught here, not
silently absent. TestPrivilegedStatementsAreGated is the security invariant: a
privileged kind that resolves to a connect-only passthrough fails the build.

docs/mysql-statement-coverage.md is the human table, generated from the test.

Enforcement effect

Exactly four of 173 resolutions change, all intended: TABLE gains
result.read + sql.select (denied read → masked read); the three START forms
move from connect-only passthrough to fail-closed. The other sixteen Command
conversions keep their resolution.

Known gap left standing

ANALYZE TABLE still resolves connect-only — but that is a proxy-monster-side
classification (KindAnalyze in the analyzer's session-passthrough set), not a
parse issue, and it is tracked separately. The coverage test pins it in
knownConnectOnlyGaps so it cannot be forgotten.

Verification

mise run verify green — the c-shared analyzer lib is rebuilt from the new pin
and the full JVM + Go + web suite passes, so the parser bump is safe product-wide.

@sjincho
sjincho requested a review from a team as a code owner August 4, 2026 05:23
sjincho and others added 2 commits August 4, 2026 15:58
A unit test enumerating the MySQL 8.0/8.4 statement kinds a client can send,
curated from the reference manual's §15 (broad, not a machine-verified extract).
Each runs through the real analyzer and its resolution is asserted, so a
statement that stops failing closed breaks the build.

Auditing the output found four privileged statements that resolve to a
connect-only passthrough instead of failing closed, all parser artifacts:
START REPLICA / START SLAVE / START GROUP_REPLICATION parse like START
TRANSACTION and classify SESSION (their STOP/RESET counterparts, which do not
collide, fail closed); ANALYZE TABLE sits in the analyzer's session-passthrough
set (CHECK/OPTIMIZE/REPAIR do not). None exposes row data — the gap is
authorization, not masking. TestPrivilegedStatementsAreGated enumerates the four
so the set cannot grow silently and closing one is noticed.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AK83USBkxzao4uFfjxzy8n
…verage audit

sqlglot-go v0.21.0 fixes three MySQL parse deviations proxy-monster's
enforcement depended on:

- START REPLICA / START SLAVE / START GROUP_REPLICATION parsed as a Transaction
  node (the word after START taken as a transaction mode), indistinguishable
  from START TRANSACTION. The analyzer classified them SESSION and passed them
  through with connect only — a principal who could connect could start
  replication with no grant. They now parse as Command and fail closed.
- STOP/FLUSH/UNLOCK INSTANCE/XA/BINLOG/HELP/RESTART/SHUTDOWN were coerced into
  expression nodes (Alias/Column); they now parse as Command. Already
  fail-closed here, now robustly so.
- TABLE t (the 8.0.19 SELECT * shorthand) parsed as an Alias; it now parses as
  Select, so a real read is enforced and masked instead of denied.

Bump effect, measured by the statement-coverage test: exactly four resolutions
change — TABLE gains result.read+sql.select (denied read → masked read), and the
three START forms move from a connect-only passthrough to fail-closed.

Completing and hardening the audit alongside the bump:

- The coverage set gains the replication/admin/SET kinds the audit was missing
  (SET sql_log_bin, SET PERSIST[_ONLY], SELECT INTO DUMPFILE, SHOW REPLICAS /
  SLAVE HOSTS / SLAVE STATUS / BINARY LOG STATUS, SHOW CREATE *). Four more
  pre-existing under-gatings surface and are recorded in knownConnectOnlyGaps
  next to ANALYZE TABLE: SET sql_log_bin, SHOW MASTER STATUS, SHOW BINARY LOGS,
  SHOW REPLICAS/SLAVE HOSTS — all the same benign-catch-all passthrough the
  statement-typing redesign (docs/statement-typing.md) closes by construction.
- privilegedNeedingGate is widened to the full privileged set (including the
  utility-gated rows), so a regression from `… + utility:X` back to a bare
  passthrough now fails the invariant.
- resolve() models decideQuery's short-circuit order (INADMISSIBLE, then the
  datasource-grant loop, then the unanalyzable gate), removing an unreachable
  relay label on CALL; its comments no longer overstate it as a decideQuery
  replica or the list as proven-exhaustive.
- go.sum is tidied (stale v0.20.0 checksums dropped) and the analyzer README pin
  updated to v0.21.0.

The c-shared analyzer lib is rebuilt from the new pin by the JVM build; no
committed artifact changes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AK83USBkxzao4uFfjxzy8n
@sjincho
sjincho force-pushed the sjcho/test/mysql-statement-coverage branch from 79430a3 to 6ccc56e Compare August 4, 2026 07:07
@sjincho
sjincho merged commit 9b8b02b into main Aug 4, 2026
25 of 27 checks passed
@sjincho
sjincho deleted the sjcho/test/mysql-statement-coverage branch August 4, 2026 07:59
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.

1 participant