GHSA-6XC5-4R68-67FC

Vulnerability from github – Published: 2026-07-06 20:39 – Updated: 2026-07-06 20:39
VLAI
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

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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.65.0"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "langroid"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.65.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54760"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22",
      "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-06T20:39:02Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "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\u0027s 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(\u0027/etc/passwd\u0027)\" -\u003e REJECTED: matches \u0027\\\\bpg_(read|stat|ls|current_logfile)[A-Za-z0-9_]*\\\\s*\\\\(\u0027\nBYPASS    \u0027SELECT \"pg_read_file\"(\\\u0027/etc/passwd\\\u0027)\u0027 -\u003e ALLOWED (validator returned None -\u003e would execute)\nBYPASS    \"SELECT pg_read_file/**/(\u0027/etc/passwd\u0027)\" -\u003e ALLOWED (validator returned None -\u003e would execute)\nBYPASS    \u0027SELECT pg_catalog.\"pg_read_file\"(\\\u0027/etc/passwd\\\u0027)\u0027 -\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 \u0027SELECT \"pg_read_file\"(\\\u0027\u003cfile\u003e\\\u0027)\u0027 -\u003e file contents returned: \u0027LANGROID_SAFE_MARKER_...\u0027\n  executed bypass \"SELECT pg_read_file/**/(\u0027\u003cfile\u003e\u0027)\" -\u003e file contents returned: \u0027LANGROID_SAFE_MARKER_...\u0027\n  executed bypass \u0027SELECT pg_catalog.\"pg_read_file\"(\\\u0027\u003cfile\u003e\\\u0027)\u0027 -\u003e file contents returned: \u0027LANGROID_SAFE_MARKER_...\u0027\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`.",
  "id": "GHSA-6xc5-4r68-67fc",
  "modified": "2026-07-06T20:39:02Z",
  "published": "2026-07-06T20:39:02Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/langroid/langroid/security/advisories/GHSA-6xc5-4r68-67fc"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/langroid/langroid"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Langroid: SQLChatAgent dangerous-function blocklist can be bypassed with quoted or schema-qualified pg_read_file calls"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…