Every CVE Search/ Get API key

CVE-2026-12044

pgAdmin 4: SQL injection in COMMENT ON ... IS '<description>' rendering across dialog templates

8.7 High

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
High

Exploit likelihood

0.71%Up 0.2 points since Jun 23, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.7

pgAdmin 4: SQL injection in COMMENT ON ... IS '<description>' rendering across dialog templates

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Record
CVE-2026-12044
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89, CWE-116
Updated
2026-11-02
EUVDEUVD-2026-37963 8.7

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Record
EUVD-2026-37963
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-hrvw-6gvm-9x99 8.7

SQL injection in pgAdmin 4 across every dialog template that renders ``COMMENT ON ... IS '...

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Record
GHSA-hrvw-6gvm-9x99
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02
NVD 8.7

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Record
CVE-2026-12044
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89, CWE-116
Updated
2026-11-02
OSV 8.7

pgAdmin 4: SQL injection in COMMENT ON ... IS '<description>' rendering across dialog templates

SQL injection in pgAdmin 4 across every dialog template that renders COMMENT ON ... IS '<description>' for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- '{{ data.description }}' -- instead of passing it through the qtLiteral escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with COPY ... TO/FROM PROGRAM (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured.

The defect was originally reported against the Domain Dialog description field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- pgstattuple('{{schema}}.{{table}}') and the matching pgstatindex shape -- where qtIdent escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named foo'bar and a later stats viewer would render an unbalanced literal.

Fix is layered:

  1. Sites: replace every '{{ x.description }}' with {{ x.description|qtLiteral(conn) }} (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb conn=self.conn through every render_template call that loads one of these templates. Also corrects a { % elif Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ::oid::regclass cast (e.g. pgstattuple({{ tid }}::oid::regclass)), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there.

  2. Driver hardening: qtLiteral (in utils/driver/psycopg3/__init__.py) used to silently return the raw unescaped value when its conn argument was falsy. It now raises ValueError -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in schemas/__init__.py, schemas/functions/__init__.py, schemas/tables/utils.py, foreign_servers/__init__.py, and seven sites in roles/__init__.py) -- all fixed as part of this patch. The inner except block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values.

  3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every *.sql template flagging any '{{ ... }}' single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths.

This issue affects pgAdmin 4: from 1.0 before 9.16.

Record
CVE-2026-12044
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-116, CWE-89
Updated
2026-11-02
OSVopenSUSE-SU-2026:11508-1 No score

pgadmin4-9.17-1.1 on GA media

These are all security issues fixed in the pgadmin4-9.17-1.1 package on the GA media of openSUSE Tumbleweed.

Record
openSUSE-SU-2026:11508-1
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
pgadmin4
openSUSE:Tumbleweed
From an advisory covering 21 CVEs
before 9.17-1.1
9.17-1.1

History

History
RecordedChangedSources
affected, facts
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-12044 is a high vulnerability (CVSS 8.7) in Pgadmin 4, published June 18, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.71%, the 53rd percentile. No source names a fixed version.

References

Show 22 moreShow less
MoveEnter Open