SQLChatAgent _validate_query dangerous-pattern regex is bypassable via quoted/commented/qualified function names
Summary
The SQLChatAgent SQL-injection mitigation, with default allow_dangerous_operations=False, combines a raw-text regex blocklist (_DANGEROUS_SQL_PATTERNS) with a sqlglot SELECT-only statement allowlist. The blocklist entries that target callable functions require the function name to be immediately followed by \s*\(.
PostgreSQL accepts the same call with the name separated from ( by a quoted identifier, an inline comment, or schema qualification. These forms evade the regex, still parse as SELECT, and execute the same PostgreSQL function. This restores the pg_read_file server-side file-read primitive that the prior CVE-2026-25879 / GHSA-pmch-g965-grmr fix was meant to block: the parent advisory fixed a missing pg_read_file blocklist entry, while this report shows that the added regex is bypassable.
Affected Code
Tested against current main commit:
6e8e7b2bb23ec04c1c25be479f16b8cc9a4f8796
The current source still contains:
re.compile(r"\bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\s*\(", re.IGNORECASE)
_validate_query checks the raw query against _DANGEROUS_SQL_PATTERNS, then parses with sqlglot and allows SELECT statements. The dangerous-call check is raw text, not normalized AST function-name matching.
Root Cause
The current mitigation treats dangerous PostgreSQL function calls as a raw-text regex problem. The regex requires the pg_... function token to be followed directly by optional whitespace and (, but PostgreSQL accepts equivalent calls through quoted identifiers, comments, and schema-qualified names. Because _validate_query only uses sqlglot to enforce the top-level statement type, those normalized function names are never checked after parsing.
Auth Boundary
The boundary is the default SQLChatAgent safety policy between attacker-influenced SQL generation and database operations that can read server-side files. With allow_dangerous_operations=False, a user or prompt that influences generated SQL should not be able to bypass the guard and execute PostgreSQL file-read functions such as pg_read_file.
This is not a new unauthenticated endpoint or product-wide SQL injection; it applies when untrusted user content can influence SQLChatAgent's generated SQL.
Reproduction
The local harness uses the current sql_chat_agent.py, extracts the real shipped dangerous regex list, validates the queries with real sqlglot==30.8.0, then executes the accepted bypasses against a local throwaway PostgreSQL 16 container.
Transcript excerpt:
CONTROL "SELECT pg_read_file('/etc/passwd')" -> REJECTED: matches '\\bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\\s*\\('
BYPASS 'SELECT "pg_read_file"(\'/etc/passwd\')' -> ALLOWED (validator returned None -> would execute)
BYPASS "SELECT pg_read_file/**/('/etc/passwd')" -> ALLOWED (validator returned None -> would execute)
BYPASS 'SELECT pg_catalog."pg_read_file"(\'/etc/passwd\')' -> ALLOWED (validator returned None -> would execute)
=== Part B: real PostgreSQL execution of the bypass ===
connected; is_superuser=t
executed bypass 'SELECT "pg_read_file"(\'<file>\')' -> file contents returned: 'LANGROID_SAFE_MARKER_...'
executed bypass "SELECT pg_read_file/**/('<file>')" -> file contents returned: 'LANGROID_SAFE_MARKER_...'
executed bypass 'SELECT pg_catalog."pg_read_file"(\'<file>\')' -> file contents returned: 'LANGROID_SAFE_MARKER_...'
RESULT: VULNERABLE
The control query is blocked by the current regex, while all three equivalent PostgreSQL forms are allowed by the validator and return the mounted proof file contents from a real PostgreSQL server. The LANGROID_SAFE_MARKER_... value is a harmless marker generated inside the throwaway local container for this proof.
Impact
On a deployment using SQLChatAgent against PostgreSQL with a role able to call pg_read_file (superuser, or a role granted pg_read_server_files), an attacker who can influence LLM-generated SQL can coerce the agent into emitting one of the obfuscated queries and read files accessible to the PostgreSQL server process through pg_read_file.
This is the same impact and precondition shape as the published pg_read_file advisory, but it targets the bypassability of the current regex-based fix rather than the pre-fix absence of a pg_read_file block.
Severity: High by parity with the published parent advisory; not Critical. CWE-184 leading to server-side file read.
Suggested Fix
Do not rely on raw-text regex matching for dangerous-call detection. After the existing sqlglot parse, walk the AST and reject any function invocation whose normalized, unquoted, schema-stripped, case-folded name is in a dangerous set such as pg_read_file, pg_read_binary_file, pg_ls_dir, pg_stat_file, lo_import, lo_export, load_file, or load_extension.
Also recommend running SQLChatAgent with a least-privilege database role that lacks pg_read_server_files.
References
SQLChatAgent
_validate_querydangerous-pattern regex is bypassable via quoted/commented/qualified function namesSummary
The
SQLChatAgentSQL-injection mitigation, with defaultallow_dangerous_operations=False, combines a raw-text regex blocklist (_DANGEROUS_SQL_PATTERNS) with asqlglotSELECT-only statement allowlist. The blocklist entries that target callable functions require the function name to be immediately followed by\s*\(.PostgreSQL accepts the same call with the name separated from
(by a quoted identifier, an inline comment, or schema qualification. These forms evade the regex, still parse asSELECT, and execute the same PostgreSQL function. This restores thepg_read_fileserver-side file-read primitive that the prior CVE-2026-25879 / GHSA-pmch-g965-grmr fix was meant to block: the parent advisory fixed a missingpg_read_fileblocklist entry, while this report shows that the added regex is bypassable.Affected Code
Tested against current
maincommit:6e8e7b2bb23ec04c1c25be479f16b8cc9a4f8796The current source still contains:
_validate_querychecks the raw query against_DANGEROUS_SQL_PATTERNS, then parses withsqlglotand allowsSELECTstatements. The dangerous-call check is raw text, not normalized AST function-name matching.Root Cause
The current mitigation treats dangerous PostgreSQL function calls as a raw-text regex problem. The regex requires the
pg_...function token to be followed directly by optional whitespace and(, but PostgreSQL accepts equivalent calls through quoted identifiers, comments, and schema-qualified names. Because_validate_queryonly usessqlglotto enforce the top-level statement type, those normalized function names are never checked after parsing.Auth Boundary
The boundary is the default
SQLChatAgentsafety policy between attacker-influenced SQL generation and database operations that can read server-side files. Withallow_dangerous_operations=False, a user or prompt that influences generated SQL should not be able to bypass the guard and execute PostgreSQL file-read functions such aspg_read_file.This is not a new unauthenticated endpoint or product-wide SQL injection; it applies when untrusted user content can influence SQLChatAgent's generated SQL.
Reproduction
The local harness uses the current
sql_chat_agent.py, extracts the real shipped dangerous regex list, validates the queries with realsqlglot==30.8.0, then executes the accepted bypasses against a local throwaway PostgreSQL 16 container.Transcript excerpt:
The control query is blocked by the current regex, while all three equivalent PostgreSQL forms are allowed by the validator and return the mounted proof file contents from a real PostgreSQL server. The
LANGROID_SAFE_MARKER_...value is a harmless marker generated inside the throwaway local container for this proof.Impact
On a deployment using
SQLChatAgentagainst PostgreSQL with a role able to callpg_read_file(superuser, or a role grantedpg_read_server_files), an attacker who can influence LLM-generated SQL can coerce the agent into emitting one of the obfuscated queries and read files accessible to the PostgreSQL server process throughpg_read_file.This is the same impact and precondition shape as the published
pg_read_fileadvisory, but it targets the bypassability of the current regex-based fix rather than the pre-fix absence of apg_read_fileblock.Severity: High by parity with the published parent advisory; not Critical. CWE-184 leading to server-side file read.
Suggested Fix
Do not rely on raw-text regex matching for dangerous-call detection. After the existing
sqlglotparse, walk the AST and reject any function invocation whose normalized, unquoted, schema-stripped, case-folded name is in a dangerous set such aspg_read_file,pg_read_binary_file,pg_ls_dir,pg_stat_file,lo_import,lo_export,load_file, orload_extension.Also recommend running SQLChatAgent with a least-privilege database role that lacks
pg_read_server_files.References