CVE-2026-50180 is a high-severity path traversal vulnerability in langroid (pip), affecting versions <= 0.63.0. It is fixed in 0.64.0.
Summary SQLChatAgent in langroid ships a validatequery defense-in-depth layer whose DANGEROUSSQLPATTERNS regex blocklist enumerates dangerous SQL primitives by specific function name. The list misses the canonical PostgreSQL filesystem-disclosure family pgreadfile(), pgstatfile(), pglslogdir(), pglswaldir(), pgcurrentlogfile() (and similar SELECT-shaped functions in the same family). It also leaves SQL Server OPENDATASOURCE and SQLite ATTACH '<file>' AS x (DATABASE keyword omitted) unblocked. An attacker able to shape the LLM's generated SQL (directly via prompt input or transitively via prompt-injection in data the LLM ingests) can read arbitrary files from the PostgreSQL host through ordinary SELECT queries, even with the agent's strict default configuration (allowdangerousoperations=False, allowedstatementtypes=['SELECT']). The payloads survive the statement-type allowlist (each is a SELECT) and pass through the regex blocklist (none of the function names match), then reach the live SQLAlchemy engine via SQLChatAgent.runquery. Affected versions langroid <= 0.63.0 (latest at the time of this report; PyPI release 2026-05-27). The vulnerable code path is langroid/agent/special/sql/sqlchatagent.py::validatequery, which consults the module-level DANGEROUSSQLPATTERNS literal at sqlchatagent.py:113-141. Privilege required Any caller able to influence the LLM-generated RunQueryTool.query string that reaches SQLChatAgent.runquery. In a typical deployment this is any client of a SQLChatAgent-backed service, or any upstream data source whose content the LLM is asked to read and summarise. No PostgreSQL credentials are required from the attacker; the agent holds them. Vulnerable code langroid/agent/special/sql/sqlchatagent.py:113-141 (the DANGEROUSSQLPATTERNS literal) and sqlchatagent.py:546-615 (the validatequery method that consults it): The blocklist is a list of \b<exact-token>\b literals. PostgreSQL ships several near-name functions on the same primitive that none of these match: | Function | What it returns | Matched by blocklist? | |---|---|---| | pgreadserverfile('/path') | file contents | yes (pgreadserverfiles?) | | pgreadbinaryfile('/path') | binary contents | yes | | pglsdir('/path') | directory listing | yes | | pgreadfile('/path') | file contents | no (no server infix) | | pgstatfile('/path') | size, mtime, ctime, atime, isdir | no | | pglslogdir() | filenames in PostgreSQL log dir | no | | pglswaldir() | WAL filenames and sizes | no | | pglstmpdir() | temp-dir listing | no | | pglsarchivestatusdir() | archive-status directory listing | no | | pgcurrentlogfile() | active server log path | no | Each of these is a SELECT-shaped function call. They pass the sqlglotexp.Select-only statement-type allowlist applied at sqlchatagent.py:583-614, then evade the regex blocklist (their names contain no token the blocklist enumerates), then reach the SQLAlchemy session.execute(text(query)) sink inside SQLChatAgent.runquery (line 631 onwards). Two non-PostgreSQL secondary gaps with the same regex-enumeration shape: The SQLite pattern \battach\s+database\b requires the literal DATABASE keyword. Per the SQLite grammar (https://www.sqlite.org/langattach.html) the keyword is optional: ATTACH '/path/to/db' AS x is valid syntax and matches no entry in the blocklist. Whether the agent rejects this via the statement-type allowlist depends on how the configured sqlglot dialect parses it; on PostgreSQL dialect parsing fails (sqlglot returns no Select) and the statement-type check rejects, but a SQLite-dialect SQLChatAgent (databaseuri="sqlite:///...") returns the statement as sqlglotexp.Attach, which is not in the agent's kindmap, so the generic type(stmt).name.upper() branch produces "ATTACH". That string is not in DEFAULTALLOWEDSTATEMENTS so the allowlist saves it here; however any deployment that extends allowedstatementtypes to include "ATTACH" (e.g. to permit cross-schema connectivity) loses this fallback and the regex misses. The MSSQL pattern \bopenrowset\b blocks OPENROWSET but not the closely-related OPENDATASOURCE function. Both can read remote/UNC files and execute remote queries via an ad-hoc connection string, e.g. a SELECT against OPENDATASOURCE('SQLNCLI11','Server=remote;TrustedConnection=yes') qualified down to master.sys.tables. Attack scenario SQLChatAgent.runquery (line 617 of sqlchatagent.py) calls self.validatequery(query) (line 631) on the LLM-generated SQL. The LLM-generated SQL is shaped by upstream prompt content that crosses the trust boundary: the user message, any tool result the LLM is asked to summarise, any document the agent retrieves, and any row the agent reads back from its own database (the RunQueryTool result is fed back into the LLM history at sqlchatagent.py:712-720 of the same release). The default config in SQLChatAgentConfig (lines 183-184) sets allowdangerousoperations=False and allowedstatementtypes=["SELECT"], which is the configuration validatequery was added to support. The bypass primitives below are reachable under this default config because each is a syntactic SELECT whose function-call argument is the disclosure vector. Proof of concept poc.py (single-file, no external services beyond a transient PostgreSQL spawned via testing.postgresql): End-to-end reproduction Run against the latest published langroid release from PyPI; no external LLM provider, no API key, no Docker, just a transient pgctl-managed PostgreSQL spawned in-process by testing.postgresql. Captured transcript of the run is below. Observed transcript (abridged to bypass results; the run also verifies that the four primitives the current blocklist already covers (COPY ... TO PROGRAM, pgreadserverfile, pgreadbinaryfile, pglsdir) continue to be REJECTED, confirming the proposed fix is strictly broader, not narrower): The headline payload SELECT pgreadfile('langroidbypassvictim.txt') returns the marker string verbatim from the file on disk. The same SQL, issued by an LLM under prompt-injection through any data source the agent reads, would land identically, the validator is purely a function of the SQL string and is consulted before the SQLAlchemy execute. validatequery is invoked directly rather than through a fully initialised SQLChatAgent because the agent's init builds the LLM stack and demands a working LLM API key (or a stub). The security control under test is purely a function of (query, patterns, allowedstatements, dialect), so the direct call is observationally equivalent to a call via runquery. Patterns and allowed-statements are loaded by reading the pinned sqlchatagent.py source out of the venv, guaranteeing no drift between PoC and shipped binary. Impact Arbitrary file read from the PostgreSQL host: pgreadfile() reads files from PGDATA-relative paths by default and can take absolute paths when the DB role holds pgreadserverfiles (or equivalent in managed-Postgres setups). For self-managed PostgreSQL deployments the DB role is frequently a superuser, in which case absolute paths are always accepted and the impact extends to postgresql.conf, pghba.conf, ~/.pgpass, TLS keys, and any other file readable by the PostgreSQL OS user. Filesystem reconnaissance via pgstatfile() (file existence, size, mtime, isdir), pglslogdir(), pglswaldir(), pglstmpdir(), pglsarchivestatusdir(), pgcurrentlogfile(). MSSQL extension: OPENDATASOURCE reaches remote SQL Servers and UNC paths, providing arbitrary outbound read + intranet pivot on MSSQL deployments. SQLite extension: ATTACH '<path>' AS schemaname (DATABASE keyword omitted) allows reading/writing arbitrary SQLite files on deployments whose allowedstatementtypes include "ATTACH". Suggested fix Patch DANGEROUSSQLPATTERNS to cover the full family rather than individual function names. Two compatible approaches; either is enough. Approach 1, family-prefix regex (minimal change, simplest to review): Approach 2, sqlglot AST walk in addition to regex. sqlglot is already imported by sqlchatagent.py; iterate every function-call node (sqlglotexp.Anonymous / sqlglotexp.Func) inside the parsed statements and reject when the lower-cased name starts with pgread, pgstat, pgls, pgcurrentlogfile, lo, or matches the MSSQL extended-procedure prefixes (xp, spoa). AST matching is robust to whitespace, comments, and case games inside identifiers, at the cost of broader per-dialect maintenance. For closing the immediate gap, Approach 1 is sufficient. Regression-test the additions in tests/main/sqlchat/testsqlchat_security.py alongside the existing security tests. A natural 7-case extension covers the 5 PostgreSQL bypass payloads, the SQLite ATTACH ... AS x form, and the MSSQL OPENDATASOURCE form. Fix PR A private temp-fork PR applying the Suggested fix Approach 1 diff, plus the regression tests described above, accompanies this advisory: https://github.com/langroid/langroid-ghsa-pmch-g965-grmr/pull/1 Credit Reported by tonghuaroot.
Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.
pip
langroid (<= 0.63.0)langroid → 0.64.0 (pip)Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter instead of chasing every advisory.
Kodem's runtime-powered SCA identifies whether CVE-2026-50180 is reachable in your applications. Explore open-source security for your team.
See if CVE-2026-50180 is reachable in your applications. Get a demo
Already deployed Kodem? See CVE-2026-50180 in your environment →Upgrade langroid to 0.64.0 or later to resolve this vulnerability.
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
CVE-2026-50180 is a high-severity path traversal vulnerability in langroid (pip), affecting versions <= 0.63.0. It is fixed in 0.64.0. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
langroid (pip) versions <= 0.63.0 is affected.
Yes. CVE-2026-50180 is fixed in 0.64.0. Upgrade to this version or later.
Whether CVE-2026-50180 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
Upgrade langroid to 0.64.0 or later.