pgAdmin 4: Stored XSS via untrusted error and plan-node text rendered through html-react-parser
Description
Stored cross-site scripting in pgAdmin 4's error-rendering and plan-node-rendering paths. Text returned by a PostgreSQL server (ErrorResponse messages, including object names quoted back inside relation-does-not-exist errors and inside EXPLAIN Recheck Cond / Exact Heap Blocks fields) was passed verbatim through html-react-parser at every user-facing sink — the notifier toasts, FormFooterMessage / FormInput help and error areas, FormNote, ModalProvider AlertContent and confirmDelete, ToolErrorView, the Explain visualiser's NodeText panel, the SQL editor confirm dialogs, ConfirmSaveContent, PreferencesHelper modal alerts, and SelectThemes helper text. A PostgreSQL server an attacker controls — or any server returning attacker-influenced text such as a table or column name a low-privilege database user can create — could inject arbitrary HTML (including ) into the pgAdmin DOM the moment the victim's pgAdmin connected to that server or viewed an Explain plan that referenced the crafted object.
The injected iframe's srcdoc could fetch attacker-served JavaScript and, by writing to parent.location, redirect the victim's top-level pgAdmin browser tab to an attacker-controlled URL. Because the injection originates from inside pgAdmin's own interface, standard anti-clickjacking controls (X-Frame-Options, Content-Security-Policy: frame-ancestors) do not mitigate it. A phishing page rendered inside the legitimate pgAdmin window is indistinguishable from a genuine pgAdmin dialog.
Fix combines three complementary layers. (1) DOMPurify sanitisation is wrapped around every html-react-parser call site reachable from notifier, alert, form-error, Explain, and SQL-editor flows. (2) A new plain-text rendering contract — SafeMessage / SafeHtmlMessage components plus Notifier.errorText / alertText / warningText / infoText / successText helpers — is introduced; around fifty callers across browser, tools, dashboard, debugger, misc, llm, preferences, schema diff, and the SQL editor that previously interpolated backend-derived strings are migrated to the plain-text variants. (3) Backend HTML-escape is applied at the post-connection-SQL handler (execute_post_connection_sql) via a new sanitize_external_text helper, so third-party JSON consumers (audit logs, API clients) never receive raw markup either; the Explain plan-info renderer is also patched to _.escape Recheck Cond and Exact Heap Blocks at construction (matching every sibling field), giving defence in depth even before DOMPurify runs.
This issue affects pgAdmin 4: from 6.0 before 9.16.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Affected products
2- Range: >=6.0, <9.16
Patches
Vulnerability mechanics
Root cause
"Text returned by a PostgreSQL server (ErrorResponse messages and EXPLAIN plan fields) was passed verbatim through html-react-parser without sanitisation, allowing stored cross-site scripting."
Attack vector
An attacker who controls a PostgreSQL server — or who can create database objects (tables, columns) whose names contain HTML — can inject arbitrary HTML, including an `<iframe>` with a `srcdoc` attribute, into the pgAdmin DOM. The injection occurs when the victim's pgAdmin connects to the malicious server and the server returns an `ErrorResponse` message that quotes back the crafted object name, or when the victim views an `EXPLAIN` plan that references the crafted object [patch_id=6590908]. Because pgAdmin's default Content-Security-Policy allows inline script and an iframe `srcdoc` inherits the embedding origin, the injected JavaScript runs same-origin to the victim's authenticated pgAdmin session and can read every saved server connection credential and issue arbitrary SQL against every server the victim was connected to [patch_id=6590909]. Standard anti-clickjacking controls (X-Frame-Options, CSP frame-ancestors) do not mitigate this because the injection originates from inside pgAdmin's own interface [patch_id=6590909].
Affected code
The vulnerability spans every user-facing sink that passes PostgreSQL server text through `html-react-parser` without sanitisation: the notifier toasts, `FormFooterMessage` / `FormInput` help and error areas, `FormNote`, `ModalProvider` `AlertContent` and `confirmDelete`, `ToolErrorView`, the Explain visualiser's `NodeText` panel, the SQL editor confirm dialogs, `ConfirmSaveContent`, `PreferencesHelper` modal alerts, and `SelectThemes` helper text [patch_id=6590908]. The fix adds DOMPurify sanitisation at every `HTMLReactParse` call site, introduces a plain-text rendering contract (`SafeMessage` / `SafeHtmlMessage` components and `Notifier.errorText` / `alertText` helpers), and applies backend HTML-escape in `execute_post_connection_sql` via `sanitize_driver_message` [patch_id=6590908].
What the fix does
The fix combines three complementary layers [patch_id=6590908]. First, DOMPurify sanitisation is wrapped around every `html-react-parser` call site reachable from notifier, alert, form-error, Explain, and SQL-editor flows. Second, a new plain-text rendering contract — `SafeMessage` / `SafeHtmlMessage` components plus `Notifier.errorText` / `alertText` / `warningText` / `infoText` / `successText` helpers — is introduced; around fifty callers that previously interpolated backend-derived strings are migrated to the plain-text variants. Third, backend HTML-escape is applied at the post-connection-SQL handler (`execute_post_connection_sql`) via a new `sanitize_driver_message` helper, and the Explain plan-info renderer is patched to `_.escape` `Recheck Cond` and `Exact Heap Blocks` at construction, giving defence in depth even before DOMPurify runs [patch_id=6590908].
Preconditions
- configThe victim's pgAdmin must connect to a PostgreSQL server that the attacker controls, or to a server where the attacker can create database objects (tables, columns) with names containing HTML markup.
- inputThe victim must view an EXPLAIN plan that references the crafted object, or the server must return an ErrorResponse message quoting back the crafted object name.
Generated on Jun 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.