{"id":"PYSEC-2026-2577","summary":"Langroid: SQLChatAgent dangerous-function blocklist can be bypassed with quoted or schema-qualified pg_read_file calls","details":"# SQLChatAgent `_validate_query` dangerous-pattern regex is bypassable via quoted/commented/qualified function names\n\n## Summary\n\nThe `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*\\(`.\n\nPostgreSQL 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.\n\n## Affected Code\n\nTested against current `main` commit:\n\n`6e8e7b2bb23ec04c1c25be479f16b8cc9a4f8796`\n\nThe current source still contains:\n\n```python\nre.compile(r\"\\bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\\s*\\(\", re.IGNORECASE)\n```\n\n`_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.\n\n## Root Cause\n\nThe 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.\n\n## Auth Boundary\n\nThe 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`.\n\nThis is not a new unauthenticated endpoint or product-wide SQL injection; it applies when untrusted user content can influence SQLChatAgent's generated SQL.\n\n## Reproduction\n\nThe 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.\n\nTranscript excerpt:\n\n```text\nCONTROL   \"SELECT pg_read_file('/etc/passwd')\" -\u003e REJECTED: matches '\\\\bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\\\\s*\\\\('\nBYPASS    'SELECT \"pg_read_file\"(\\'/etc/passwd\\')' -\u003e ALLOWED (validator returned None -\u003e would execute)\nBYPASS    \"SELECT pg_read_file/**/('/etc/passwd')\" -\u003e ALLOWED (validator returned None -\u003e would execute)\nBYPASS    'SELECT pg_catalog.\"pg_read_file\"(\\'/etc/passwd\\')' -\u003e ALLOWED (validator returned None -\u003e would execute)\n\n=== Part B: real PostgreSQL execution of the bypass ===\nconnected; is_superuser=t\n  executed bypass 'SELECT \"pg_read_file\"(\\'\u003cfile\u003e\\')' -\u003e file contents returned: 'LANGROID_SAFE_MARKER_...'\n  executed bypass \"SELECT pg_read_file/**/('\u003cfile\u003e')\" -\u003e file contents returned: 'LANGROID_SAFE_MARKER_...'\n  executed bypass 'SELECT pg_catalog.\"pg_read_file\"(\\'\u003cfile\u003e\\')' -\u003e file contents returned: 'LANGROID_SAFE_MARKER_...'\n\nRESULT: VULNERABLE\n```\n\nThe 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.\n\n## Impact\n\nOn 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`.\n\nThis 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.\n\nSeverity: High by parity with the published parent advisory; not Critical. CWE-184 leading to server-side file read.\n\n## Suggested Fix\n\nDo 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`.\n\nAlso recommend running SQLChatAgent with a least-privilege database role that lacks `pg_read_server_files`.","aliases":["CVE-2026-54760","GHSA-6xc5-4r68-67fc"],"modified":"2026-07-13T16:31:49.866824662Z","published":"2026-07-13T15:46:28.525610Z","references":[{"type":"WEB","url":"https://github.com/langroid/langroid/security/advisories/GHSA-6xc5-4r68-67fc"},{"type":"PACKAGE","url":"https://github.com/langroid/langroid"},{"type":"PACKAGE","url":"https://pypi.org/project/langroid"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-6xc5-4r68-67fc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54760"}],"affected":[{"package":{"name":"langroid","ecosystem":"PyPI","purl":"pkg:pypi/langroid"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.65.1"}]}],"versions":["0.1.100","0.1.101","0.1.102","0.1.103","0.1.104","0.1.105","0.1.106","0.1.107","0.1.108","0.1.109","0.1.11","0.1.110","0.1.111","0.1.112","0.1.113","0.1.114","0.1.117","0.1.118","0.1.119","0.1.12","0.1.120","0.1.121","0.1.122","0.1.123","0.1.124","0.1.125","0.1.126","0.1.127","0.1.128","0.1.129","0.1.13","0.1.130","0.1.131","0.1.132","0.1.133","0.1.134","0.1.135","0.1.136","0.1.137","0.1.138","0.1.139","0.1.140","0.1.141","0.1.142","0.1.143","0.1.144","0.1.145","0.1.147","0.1.148","0.1.149","0.1.15","0.1.150","0.1.151","0.1.152","0.1.153","0.1.154","0.1.155","0.1.156","0.1.157","0.1.158","0.1.159","0.1.160","0.1.161","0.1.162","0.1.163","0.1.164","0.1.165","0.1.166","0.1.167","0.1.168","0.1.169","0.1.17","0.1.170","0.1.171","0.1.172","0.1.173","0.1.174","0.1.175","0.1.176","0.1.177","0.1.178","0.1.179","0.1.18","0.1.181","0.1.182","0.1.183","0.1.184","0.1.185","0.1.186","0.1.187","0.1.188","0.1.189","0.1.19","0.1.190","0.1.191","0.1.192","0.1.193","0.1.194","0.1.195","0.1.196","0.1.197","0.1.198","0.1.199","0.1.20","0.1.200","0.1.201","0.1.202","0.1.203","0.1.205","0.1.206","0.1.207","0.1.208","0.1.209","0.1.21","0.1.210","0.1.211","0.1.212","0.1.213","0.1.214","0.1.215","0.1.217","0.1.218","0.1.219","0.1.22","0.1.221","0.1.222","0.1.224","0.1.225","0.1.226","0.1.227","0.1.228","0.1.229","0.1.23","0.1.230","0.1.231","0.1.233","0.1.234","0.1.235","0.1.236","0.1.237","0.1.238","0.1.239","0.1.24","0.1.240","0.1.241","0.1.243","0.1.244","0.1.245","0.1.246","0.1.247","0.1.248","0.1.249","0.1.25","0.1.250","0.1.251","0.1.252","0.1.253","0.1.254","0.1.256","0.1.257","0.1.258","0.1.26","0.1.260","0.1.261","0.1.262","0.1.263","0.1.265","0.1.27","0.1.28","0.1.29","0.1.30","0.1.31","0.1.32","0.1.33","0.1.34","0.1.35","0.1.36","0.1.37","0.1.38","0.1.39","0.1.40","0.1.41","0.1.42","0.1.43","0.1.44","0.1.46","0.1.47","0.1.48","0.1.49","0.1.50","0.1.51","0.1.52","0.1.53","0.1.54","0.1.55","0.1.56","0.1.57","0.1.58","0.1.59","0.1.60","0.1.61","0.1.62","0.1.63","0.1.64","0.1.65","0.1.66","0.1.67","0.1.68","0.1.69","0.1.72","0.1.73","0.1.76","0.1.77","0.1.78","0.1.79","0.1.8","0.1.80","0.1.81","0.1.83","0.1.84","0.1.85","0.1.86","0.1.87","0.1.88","0.1.89","0.1.9","0.1.90","0.1.91","0.1.92","0.1.93","0.1.94","0.1.95","0.1.96","0.1.97","0.1.98","0.1.99","0.10.0","0.10.1","0.10.2","0.11.0","0.12.0","0.13.0","0.14.0","0.15.0","0.15.1","0.15.2","0.16.0","0.16.1","0.16.2","0.16.3","0.16.4","0.16.5","0.16.6","0.16.7","0.17.0","0.17.1","0.18.0","0.18.1","0.18.2","0.18.3","0.19.0","0.19.1","0.19.2","0.19.3","0.19.4","0.19.5","0.2.0","0.2.10","0.2.11","0.2.12","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.2.9","0.20.0","0.20.1","0.21.0","0.22.0","0.22.1","0.22.2","0.22.3","0.22.4","0.22.5","0.22.6","0.22.7","0.23.0","0.23.1","0.23.2","0.23.3","0.24.1","0.25.0","0.26.0","0.26.1","0.26.2","0.27.1","0.27.2","0.27.3","0.27.4","0.28.0","0.28.1","0.28.2","0.28.3","0.28.4","0.28.5","0.28.6","0.28.7","0.29.0","0.3.0","0.3.1","0.30.0","0.30.1","0.31.0","0.31.1","0.31.2","0.31.3","0.32.0","0.32.1","0.32.2","0.33.10","0.33.11","0.33.12","0.33.13","0.33.3","0.33.4","0.33.6","0.33.7","0.33.8","0.33.9","0.34.0","0.34.1","0.35.0","0.35.1","0.36.0","0.36.1","0.37.0","0.37.1","0.37.2","0.37.3","0.37.4","0.37.5","0.37.6","0.37.7","0.38.0","0.39.0","0.39.1","0.39.2","0.39.3","0.39.4","0.39.5","0.40.0","0.41.0","0.41.1","0.41.2","0.41.3","0.41.4","0.41.5","0.42.0","0.42.1","0.42.10","0.42.2","0.42.3","0.42.4","0.42.5","0.42.6","0.42.7","0.42.8","0.42.9","0.43.0","0.43.1","0.44.0","0.45.0","0.45.1","0.45.10","0.45.2","0.45.3","0.45.4","0.45.5","0.45.6","0.45.7","0.45.8","0.46.0","0.47.0","0.47.1","0.47.2","0.48.0","0.48.1","0.48.2","0.48.3","0.49.0","0.49.1","0.5.0","0.5.1","0.50.0","0.50.1","0.50.10","0.50.11","0.50.12","0.50.2","0.50.3","0.50.4","0.50.5","0.50.6","0.50.7","0.50.8","0.50.9","0.51.0","0.51.1","0.51.2","0.52.0","0.52.1","0.52.2","0.52.3","0.52.4","0.52.5","0.52.6","0.52.7","0.52.8","0.52.9","0.53.0","0.53.1","0.53.10","0.53.11","0.53.12","0.53.13","0.53.14","0.53.15","0.53.16","0.53.2","0.53.4","0.53.5","0.53.6","0.53.7","0.53.8","0.54.0","0.54.1","0.54.2","0.55.0","0.55.1","0.56.0","0.56.1","0.56.10","0.56.11","0.56.12","0.56.13","0.56.14","0.56.15","0.56.16","0.56.17","0.56.18","0.56.19","0.56.2","0.56.3","0.56.4","0.56.5","0.56.6","0.56.7","0.56.8","0.56.9","0.57.0","0.58.0","0.58.1","0.58.2","0.58.3","0.59.0","0.59.0b1","0.59.0b2","0.59.0b3","0.59.1","0.59.10","0.59.11","0.59.12","0.59.13","0.59.14","0.59.15","0.59.16","0.59.17","0.59.18","0.59.19","0.59.2","0.59.20","0.59.21","0.59.22","0.59.23","0.59.24","0.59.25","0.59.26","0.59.27","0.59.28","0.59.29","0.59.3","0.59.30","0.59.31","0.59.32","0.59.33","0.59.34","0.59.35","0.59.36","0.59.37","0.59.38","0.59.39","0.59.4","0.59.5","0.59.6","0.59.7","0.59.8","0.59.9","0.6.0","0.6.1","0.6.3","0.6.4","0.6.5","0.6.6","0.6.7","0.60.0","0.60.1","0.60.2","0.60.3","0.61.0","0.61.1","0.62.0","0.63.0","0.64.0","0.65.0","0.8.0","0.9.0","0.9.1","0.9.2","0.9.3","0.9.4","0.9.5"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/langroid/PYSEC-2026-2577.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"}]}