CVE-2026-17346 Overview
CVE-2026-17346 is a SQL injection vulnerability [CWE-89] in pgAdmin 4 that stems from an incomplete fix for CVE-2026-12044. The earlier patch hardened sixteen SQL template sinks with qtLiteral, but left several sinks in the test_sql_string_literal_lint.py ALLOWLIST under the assumption that schema, table, publication, and subscription names from pg_catalog could not contain apostrophes. PostgreSQL permits arbitrary characters in quoted identifiers, so a low-privileged user with CREATE TABLE, CREATE PUBLICATION, or CREATE SUBSCRIPTION rights can plant a booby-trapped object name. When any user opens that object's Statistics or Dependencies tab in pgAdmin, arbitrary SQL executes in the viewer's database session.
Critical Impact
A low-privileged attacker can inject arbitrary SQL into a higher-privileged administrator's database session by planting apostrophe-containing object names, escalating privileges through cross-user session hijack in pgAdmin.
Affected Products
- pgAdmin 4 Index Statistics sink: versions from 1.0 before 9.17
- pgAdmin 4 Publications sinks (pg and ppas/EPAS dialects): versions from 5.0 before 9.17
- pgAdmin 4 Subscriptions sinks: versions from 5.0 before 9.17
Discovery Timeline
- 2026-07-31 - CVE-2026-17346 published to NVD
- 2026-08-05 - Last updated in NVD database
Technical Details for CVE-2026-17346
Vulnerability Analysis
The vulnerability is a second-order SQL injection in pgAdmin 4's Jinja-rendered SQL templates. When a user opens the Statistics or Dependencies tab for an index, publication, or subscription, pgAdmin renders a query that interpolates the target object's name directly into a single-quoted SQL literal. Because the object name is treated as trusted, no escaping is applied.
An attacker who owns any schema or database object can supply a name containing an apostrophe followed by stacked SQL. When a database administrator later inspects that object in pgAdmin, the injected statements run under the administrator's privileges. The affected sinks include coll_stats.sql for the all-indexes listing (both 16_plus and default PostgreSQL template variants), the publication dependencies.sql and get_position.sql templates (pg and ppas/EPAS dialects), and the subscription dependency templates.
Root Cause
The root cause is misplaced trust in identifiers returned by pg_catalog through the browser tree. Maintainers added the affected sinks to an ALLOWLIST in test_sql_string_literal_lint.py under the assumption that catalog-sourced names were safe. PostgreSQL, however, allows arbitrary characters in quoted identifiers, invalidating the assumption. The templates interpolated '{{ name }}' as a raw string literal instead of routing the value through the qtLiteral(conn) filter.
Attack Vector
Exploitation requires only low-privileged authenticated access to a PostgreSQL server managed by pgAdmin. The attacker creates a table, publication, or subscription with an apostrophe-embedded name. When any pgAdmin user with higher privileges opens the corresponding Statistics or Dependencies tab, the crafted SQL executes in that user's session.
pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename) AS pubtable
FROM pg_catalog.pg_publication_tables pgb_table
LEFT JOIN pg_catalog.pg_class cl ON pgb_table.tablename = cl.relname
-WHERE pubname = '{{ pname }}' AND pgb_table.schemaname NOT LIKE 'pgagent';
+WHERE pubname = {{ pname|qtLiteral(conn) }} AND pgb_table.schemaname NOT LIKE 'pgagent';
The diff above shows the vulnerable raw interpolation replaced with the qtLiteral(conn) filter. Source: pgAdmin security commit 73b3218.
Detection Methods for CVE-2026-17346
Indicators of Compromise
- Presence of PostgreSQL objects with names containing apostrophes, such as tables, publications, or subscriptions returned by queries against pg_class, pg_publication, or pg_subscription.
- Unexpected SQL statements in PostgreSQL server logs executed under an administrator role immediately after opening a Statistics or Dependencies tab in pgAdmin.
- pgAdmin instances running versions prior to 9.17 that permit low-privileged user access to shared PostgreSQL servers.
Detection Strategies
- Audit pg_catalog for object identifiers containing single-quote characters: SELECT relname FROM pg_class WHERE relname LIKE '%''%'; and equivalent queries against pg_publication and pg_subscription.
- Enable PostgreSQL log_statement = 'all' on managed servers and correlate stacked-statement patterns with pgAdmin session activity.
- Review commits 73b3218 and f75452b to identify vulnerable template paths and confirm patch application.
Monitoring Recommendations
- Alert on PostgreSQL DDL events that create objects whose names contain quote characters or SQL keywords such as DROP, GRANT, or COPY.
- Monitor pgAdmin web server access logs for repeated requests to stats and dependencies endpoints from administrator sessions.
- Track privilege changes and role modifications occurring shortly after Statistics or Dependencies tab access.
How to Mitigate CVE-2026-17346
Immediate Actions Required
- Upgrade pgAdmin 4 to version 9.17 or later on all workstations and shared deployments.
- Rename any existing PostgreSQL objects whose identifiers contain apostrophes until patched pgAdmin builds are deployed.
- Restrict CREATE TABLE, CREATE PUBLICATION, and CREATE SUBSCRIPTION privileges to trusted users on shared servers.
Patch Information
The fix is delivered in pgAdmin 4 version 9.17. The patch routes all previously unescaped name interpolations through qtLiteral(conn) and updates publications/__init__.py and subscriptions/__init__.py to pass conn=self.conn into the dependencies.sqlrender_template call. ALLOWLIST entries in test_sql_string_literal_lint.py were removed, and a behavioral regression test now renders each fixed template with a stacked-statement apostrophe payload to verify escaping. See the pgAdmin issue tracker #10193 and commits 73b3218 and f75452b.
Workarounds
- Instruct administrators to avoid opening Statistics and Dependencies tabs for untrusted or user-created objects until pgAdmin 9.17 is installed.
- Revoke object-creation privileges from untrusted roles: REVOKE CREATE ON DATABASE <db> FROM PUBLIC; and grant explicitly to trusted users only.
- Use dedicated pgAdmin instances per privilege tier so that low-privileged users and administrators do not share the same browser session context.
# Verify no apostrophe-containing identifiers exist before upgrading
psql -c "SELECT nspname, relname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE relname LIKE '%''%' OR nspname LIKE '%''%';"
psql -c "SELECT pubname FROM pg_publication WHERE pubname LIKE '%''%';"
psql -c "SELECT subname FROM pg_subscription WHERE subname LIKE '%''%';"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

