CVE-2026-17351
pgAdmin 4: AI Assistant read-only transaction bypass via sqlparse/PostgreSQL lexer disagreement (incomplete fix for CVE-2026-12045)
Record summary
CVE-2026-17351 has a selected CVSS score of 9.4 (critical); EIP currently links 1 repository PoC.
Description
The fix for CVE-2026-12045 in pgAdmin 4 9.16 required the LLM-supplied query passed to the AI Assistant's execute_sql_query tool to parse, via sqlparse, as exactly one non-transaction-control statement before running it inside a BEGIN TRANSACTION READ ONLY wrapper. sqlparse's string-literal lexing can disagree with PostgreSQL's own parser: under standard_conforming_strings = on (PostgreSQL's default since 9.1), a backslash immediately before a quote is an ordinary character to PostgreSQL, but sqlparse treats it as escaping the quote. A payload such as SELECT '\';COMMIT;CREATE TABLE pwn(x int);SELECT 1 --' therefore parses as a single SELECT to sqlparse's validator, while PostgreSQL executes it as four statements: the smuggled COMMIT ends the wrapping read-only transaction, and the trailing ROLLBACK becomes a no-op. This reintroduces the same write/RCE bypass CVE-2026-12045 was meant to close, reachable via the same indirect prompt-injection delivery (an attacker plants the payload in any object the AI Assistant may read; the LLM emits it as a tool call). An initial candidate fix ran the query with psycopg's execute(..., prepare=True), intending to force PostgreSQL's own Parse step (extended query protocol) to reject multi-statement text regardless of sqlparse's classification. This candidate fix does not work as submitted: psycopg3's PrepareManager silently ignores the prepare argument whenever the connection's prepare_threshold is None, which is pgAdmin's default for every server connection (the per-server "Prepare threshold" field is blank unless an administrator explicitly sets it) -- psycopg3 falls back to the simple query protocol, the same multi-statement-capable path the bypass exploits, so the candidate fix closes nothing on any real-world default configuration. The corrected fix sets conn.prepare_threshold = 0 directly on the dedicated, single-use read-only connection the AI Assistant tool opens, structurally forcing the extended query protocol independent of any server-level configuration. Verified against a live PostgreSQL 18 instance: the payload executes successfully under the prepare_threshold=None (default) behavior, and is rejected with "cannot insert multiple commands into a prepared statement" once prepare_threshold=0 is set on that connection. This issue affects pgAdmin 4: from 9.13 before 9.17.
Exploitation context
Available material
- Repository PoCs
- 1
CISA SSVC decision
CISA Coordinator · SSVC 2.0.3 · Evaluated Jul 31, 2026 · Source: CVE List
Affected products and versions
1| Product | Source | Version range | Status |
|---|---|---|---|
pgAdmin 4Browse pgadmin.org / pgAdmin 4Default status: unaffected | CVE List | 9.13 to < 9.17 | affected |
Proofs of concept
1Repository PoCs
GitHubHunt-Benito/pgadmin-ai-assistant-sql-injection-cve-2026-17351-lexer-differential-bypassRepository PoCby Hunt-BenitoStars: 0Exploit3 files
Analysis
Technical assessment
PoC exploit for CVE-2026-17351, a SQL injection bypass in pgAdmin 4's AI Assistant. The code demonstrates a lexer differential between sqlparse and PostgreSQL to smuggle a COMMIT and CREATE TABLE past a read-only transaction guard. It executes the payload against a live PostgreSQL instance, verifies the table was created, and then demonstrates the fix.
Backdoor review
No backdoor observed in reviewed code
The PoC demonstrates a legitimate CVE-2026-17351 exploit: a lexer differential between sqlparse and PostgreSQL that bypasses a read-only transaction guard. The code connects to a user-supplied PostgreSQL instance, executes the documented payload, and verifies the bypass and its fix. No concealed operator-directed harm, credential theft, persistence, or unrelated payload is present.
Classification basis and observed behavior
Classification basis
The artifact contains code that actively exercises the vulnerability by executing a crafted SQL payload against a live PostgreSQL database to bypass a read-only transaction guard and create a table, which constitutes exploitation.
poc.py:130-143Requirements
- Requires a running PostgreSQL instance with valid credentials.
poc.py:15-21 - Requires psycopg and sqlparse Python libraries.
poc.py:13
Observed behavior
- Validates the payload using a replicated pgAdmin 9.16 sqlparse-based validator, which incorrectly classifies the multi-statement payload as a single SELECT.
poc.py:104-112 - Connects to PostgreSQL, executes the payload via the simple query protocol inside a BEGIN TRANSACTION READ ONLY block, and checks if the smuggled CREATE TABLE succeeded.
poc.py:118-157 - Demonstrates the fix by connecting with prepare_threshold=0 and showing that the extended query protocol rejects the multi-statement payload.
poc.py:164-181
Behaviors behind the backdoor verdict
Observables
- Payload
- SELECT '\';COMMIT;CREATE TABLE pwn(x int);SELECT 1 --'The documented SQL injection payload that exploits the lexer differential.
poc.py:48 - Network Connection
- User-supplied PostgreSQL host/port (default localhost:5433)The PoC connects to a PostgreSQL instance specified by the user to demonstrate the vulnerability.
poc.py:121poc.py:206-210 - File Operation
- DROP TABLE IF EXISTS pwn; CREATE TABLE pwn(x int);The PoC creates and drops a table named 'pwn' on the target database to demonstrate the bypass.
poc.py:126poc.py:133poc.py:157
What the analysis did not establish
- The evidence packet does not include the file prompt_injection_demo.py, which is referenced in the README.
- The analysis is based solely on the provided text files; the code was not executed.
- One file (prompt_injection_demo.py) was omitted from the evidence packet and not reviewed. Its absence limits completeness but does not indicate a backdoor in the reviewed files.
This review is limited to the supplied PoC code and context. It does not assert that the code works or is safe to execute.