CVE-2026-54760: langroid SQLChatAgent pg_read_file Blocklist Bypass
Langroid's SQLChatAgent blocks dangerous PostgreSQL file-read functions by regex, but quoting the function name, inserting an inline comment, or schema-qualifying it bypasses the check entirely…
The problem
SQLChatAgent's _validate_query runs LLM-generated SQL through a raw-text regex blocklist (_DANGEROUS_SQL_PATTERNS) and then a sqlglot SELECT-only allowlist. The blocklist entry for pg_read_file and related functions requires the token to be immediately followed by optional whitespace and (, written as \bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\s*\(.
PostgreSQL accepts the same call when the function name is double-quoted, separated from ( by an inline /**/ comment, or prefixed with pg_catalog. schema qualification. All three forms parse as valid SELECT statements, so they pass the sqlglot allowlist check too.
The result is that pg_read_file calls that the regex was meant to block reach the database and return file contents.
Proof of concept
A working proof-of-concept for CVE-2026-54760 in langroid, with the exact payload below.
-- three confirmed bypass forms, all pass _validate_query and execute on PostgreSQL:
-- 1. quoted identifier
SELECT "pg_read_file"('/etc/passwd');
-- 2. inline comment between name and paren
SELECT pg_read_file/**/('/etc/passwd');
-- 3. schema-qualified quoted name
SELECT pg_catalog."pg_read_file"('/etc/passwd');The regex anchors on the literal token pg_read_file followed by \s*\(. PostgreSQL's parser normalises quoted identifiers and strips comments before function resolution, so the database sees the same call in all three forms while the Python regex sees none of them as matching.
Because _validate_query only uses sqlglot to enforce the top-level statement type (SELECT) and never inspects the parsed AST for function names, the normalised call is never checked after parsing. The root cause is CWE-184 (Incomplete List of Disallowed Inputs) combined with relying on raw-text pattern matching instead of AST-level analysis for a security gate.
The fix replaces the regex check with a sqlglot AST walk that strips quoting and schema prefixes before comparing the function name against a denylist.
The fix
Upgrade to langroid 0.65.1. The patch replaces the vulnerable raw-text regex approach with a sqlglot AST traversal that normalises (unquotes, lowercases, strips schema prefixes) every function invocation in the parsed tree and rejects any whose bare name appears in the dangerous-function denylist.
As a defence-in-depth measure, also connect SQLChatAgent to a PostgreSQL role that does not hold the pg_read_server_files privilege or superuser status, so pg_read_file calls are rejected at the database level even if the application-layer check is bypassed.
Related research
- criticalCVE-2026-55615CVE-2026-55615: langroid Neo4jChatAgent Prompt-to-Cypher Injection (RCE)
- high · 8.1CVE-2026-54771CVE-2026-54771: langroid Unauthorized Tool Invocation via User-Supplied JSON
- high · 7.7mcp-atlassian: Arbitrary Server-Side File Read via Attachment Upload Path Traversal
- high · 7.7phantom-audio: Arbitrary File Write and Decode-Bomb DoS via Unconfined MCP Tool Paths