OpenReplay: Unauthenticated stored XSS via tracker event data leads to dashboard account takeover

An anonymous attacker plants event data through the tracking SDK that executes JavaScript in the dashboard when opened and, via the JWT in localStorage, leads to account takeover.

Advisory ID: TP-2026-023
Product: OpenReplay (session replay and product analytics platform)
Vulnerability type: Stored cross-site scripting (CWE-79)
CVE: CVE-2026-55879
CVSS 3.1: 9.3 (Critical) · CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Affected versions: >= 1.24.0
Fixed in: 1.25.0
Vendor advisory: GHSA-3mfc-7hf4-jfxh
Reported: 29 May 2026

Summary

OpenReplay is a session replay and product analytics platform. The tracking SDK accepts events using only the public project key, that is, without authenticating the sending user. Custom event names and page URLs are stored without output encoding and later rendered in the dashboard, where the TextEllipsis component assigns the value via innerHTML and thereby permits markup injection. When a logged-in user opens the event in Data Management, the embedded script executes in the dashboard origin. Because the session JWT lives in localStorage and no Content-Security-Policy is served, the script reads the token and takes over the account. turingpoint verified the flow and reported it responsibly; the vendor fixed it in 1.25.0.

Root cause

The analytics backend stores the tracker-supplied event fields unchanged: the event name (backend/pkg/db/clickhouse/connector.go:850), the properties (:835) and the page URL (:460), and the Go API returns them raw (backend/pkg/analytics/events/events.go:201 GetEventByID). In the dashboard, the event details modal renders the values in the default view through <TextEllipsis> (frontend/app/components/DataManagement/Activity/EventDetailsModal.tsx:234). Its width measurement sets tag.innerHTML = text and appends the element to document.body (frontend/app/components/ui/TextEllipsis/TextEllipsis.js:20), and it is called with element.innerText, so the React-escaped text is parsed back into live HTML and the onerror handler fires. A second sink, <pre dangerouslySetInnerHTML> in the JSON view (EventDetailsModal.tsx:253), renders the same raw data with no sanitiser. OpenReplay serves no Content-Security-Policy, so the inline script runs in the dashboard origin and can read the JWT from localStorage.UserStore.jwt.

Proof of Concept

Through the tracking SDK with the public project key (anonymous):

tracker.event("<img src=x onerror=window.__XSS_FIRED=document.domain>", {})

Victim (logged in) opens:
GET /<projectId>/data-management/activity?event_id=<id>
-> onerror fires in the dashboard origin, reads localStorage.UserStore.jwt

The missing output encoding stores the event name unchanged, and on open TextEllipsis parses the text recovered via innerText through innerHTML into a live element whose onerror handler runs with no CSP to block it.

Impact

  • Anonymous attackers execute scripts in the session of any logged-in dashboard user who opens the crafted event.
  • Full account takeover by stealing the JWT from localStorage.
  • Access to the victim's session recordings, dashboards, project settings and member management.
  • Requires only knowledge of the public project key; the payload triggers via a ?event_id= deep link with no further interaction.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.