Skip to content

Read-only enforcement bypassed by leading SQL comment — write statements execute #2101

Closed

Description

@udbhav-si

Version: 0.51.1 (build 99), macOS

Summary

A connection's read-only enforcement (the Forbidden: Connection is read only for external clients guard, hit via the MCP server) classifies a statement as read vs. write by inspecting its leading keyword, but only after trimming leading whitespace — not leading SQL comments. Prefixing any write statement with a /* … */ block comment or a -- … line comment causes it to be misclassified as read-only and executed.

Steps to reproduce

On a read-only connection (via the MCP execute_query):

  1. UPDATE some_table SET id = id WHERE 1=0; → correctly blocked (Forbidden: Connection is read only…).
  2. /* x */ UPDATE some_table SET id = id WHERE 1=0; → executes (not blocked).
  3. -- x⏎UPDATE some_table SET id = id WHERE 1=0; → executes (not blocked).
  4. /* x */ CREATE TEMP TABLE probe (id int); → executes; table is created (confirmed via to_regclass).

For contrast, these are all correctly blocked: plain INSERT/UPDATE/DELETE/CREATE TEMP TABLE, a write hidden in a CTE (WITH x AS (INSERT … RETURNING) SELECT …), and a write with only leading whitespace/newlines.

Expected

A read-only connection rejects any statement that modifies data or schema, regardless of leading comments.

Actual

Leading /* */ or -- comments bypass the check; the write reaches the database.

Likely root cause

Statement-type detection strips leading whitespace but not leading comments before matching the first keyword. A comment shifts the keyword out of the inspected position.

Suggested fix

Strip/skip leading SQL comments (both /* */ and --) before classifying the statement, or classify using a tokenizer/parser rather than a leading-string match. Ideally also enforce read-only at the session/transaction level (e.g. SET default_transaction_read_only = on / a read-only DB session) as defense-in-depth so client-side misclassification can't reach the database.

Environment: PostgreSQL 13; tested via the MCP server with a read-only connection.

Activity

  1. udbhav-si commented on Aug 13, 2026

    @udbhav-si
    Author

    Apologies for the noise — this is already fixed. I was on v0.51.1. QueryClassifier.isWriteQuery strips leading SQL comments as of 2478dc55 (first released in v0.55.0), with regression tests covering comment-prefixed INSERT/DELETE. I've since updated to v0.64.0 and confirmed all the comment-prefixed write cases are now correctly blocked. Closing as already-fixed — thanks for the great tool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions