
CVE-2026-39154, Stored XSS in CometChat JS SDK
This directory holds an honest, self-contained reproduction of the render sink
behind CVE-2026-39154: a stored cross-site scripting defect in the CometChat
JavaScript SDK 3.0.12, where a group message's data.text field is rendered into
other members' clients without contextual output encoding.
It runs entirely locally with synthetic data. It does not contact, probe, or authenticate to the live CometChat service or any third party. It is a local demonstration of the vulnerable client render path only.
Matching PoC repository (named in the writeup): https://github.com/defineid/Wildfire
Writeup: writeup-cometchat-xss.html in this portfolio.
| Field | Value |
|---|
| CVE | CVE-2026-39154 |
| Vendor / Product | CometChat · JavaScript SDK 3.0.12 |
| Class | Stored Cross-Site Scripting (CWE-79) |
| Endpoint | POST /v3.0/messages · injection field data.text |
| Receiver | receiverType: group |
| Interaction | None; the stored payload auto-executes when a member opens the group |
| Disclosure | Reported to CometChat Feb 2026 (request 42660); vendor marked Solved; CVE assigned by MITRE |
| Reference | https://www.cve.org/CVERecord?id=CVE-2026-39154 |
Note on the fixed version: the published writeup states the vendor resolved the issue ("VENDOR: SOLVED") but does not name a specific fixed SDK/build number, and no separate vendor advisory URL is given beyond the CVE record above. No fixed version number is asserted here because the writeup does not provide one.
A CometChat TextMessage carries its text in data.text, exposed to UI code as
message.getText(). An authenticated member can send, to a group, a message whose
data.text contains HTML markup, for example:
POST /v3.0/messages
{
"receiverType": "group",
"receiver": "<groupId>",
"category": "message",
"type": "text",
"data": { "text": "" }
}
The platform stores that string verbatim. When any group member opens the
conversation, the client's message-list render path takes data.text and inserts
it into the DOM as HTML, without contextual output encoding (an innerHTML-style
write rather than a textContent write). The `` markup is therefore parsed;
its src=x fails to load, which fires the onerror handler, and the attacker's
script runs in the victim's session. Because the message is stored, this is
persistent and requires no victim interaction beyond viewing the group.
The disclosed fix sanitizes / contextually encodes this field, so the same input renders as inert text instead of executing.
An authenticated group member is the attacker; the victims are the other members of that group. The payload is delivered once, stored server-side, and executes automatically in every member's browser when they view the conversation. No click, permission grant, or additional interaction is required. Impact in the original report: arbitrary script execution in each member's session (session/token theft, actions on behalf of the victim, disclosure of conversation data).
repro.html — a bare local page (plain white, minimal CSS, a simple message-list
area) that stands in for only the vulnerable SDK 3.0.12 render path. It holds
a faithful local copy of the unsanitized sink:
function getText(message){ return message.data.text; } // mirrors TextMessage#getText()
...
body.innerHTML = getText(message); // *** the SDK 3.0.12 sink, reproduced ***
It renders a synthetic stored-message object through that sink so the injected script executes locally. An inline honest note on the page states that this is a local reproduction of the SDK 3.0.12 sink, that the fixed SDK sanitizes the field, and that no live CometChat service is involved.
payload.json — the synthetic stored TextMessage object. Its data.text field
is the injection point (annotated with _injection_point / _note keys, since
JSON has no comments). The injection vector (``) is
identical to the disclosed report; the action is changed to a harmless in-page
marker.
payload.txt — the same injection documented in plain text, including the
original crafted request, with an explicit note that it is not sent anywhere.
This repro.html is a minimal stand-in written from the mechanism in the writeup.
It is not an extract of the vendor's source code; the real SDK was not obtained
or disassembled for this reproduction.
The payload in this repro is benign: instead of alert(document.domain), the
onerror handler sets window.__xss_fired = true, writes RESULT: XSS-FIRED into
the page, and sets the document title. Nothing is exfiltrated.
cd poc/CVE-2026-39154
python3 -m http.server 5599
# then open http://localhost:5599/repro.html in a browser
repro.html renders an embedded copy of the stored message synchronously (so the
sink fires deterministically even under headless capture) and additionally fetches
payload.json to confirm the on-disk artifact loads and carries the same field.
RESULT: XSS-FIRED in red — proof the
injected onerror script executed via the unsanitized innerHTML sink.data.text would be encoded and would appear as
the literal text `` with no script execution.payload.json
was validated as well-formed JSON and confirmed to carry the injection in
data.text (an vector). The directory was served with `python3 -m http.server 5599`, and `repro.html` was loaded in **Firefox 140.11.0esr headless** via `--screenshot` (saved here as `repro-screenshot.png`). The captured screenshot shows the rendered as a broken-image element (i.e. data.text was parsed as HTML,
not shown as text) and RESULT: XSS-FIRED written into the DOM. This confirms
the unsanitized innerHTML sink executes injected script from the message field./v3.0/messages, no account or
group was used, and no real SDK build was downloaded, executed, or disassembled.
The sink here is a faithful hand-written stand-in based on the writeup's described
mechanism, not the vendor's shipped code. The exact fixed SDK version was not
confirmed because the writeup does not state one.This is a local, offline reproduction using only synthetic, self-generated data, built to document an already-reported and vendor-resolved CVE. It targets a local stand-in of the vulnerable render path, not any live or third-party system. The payload is a harmless in-page marker; it contains no exfiltration or weaponization beyond demonstrating that the field is rendered unsanitized.