OpenReplay: Cross-user IDOR in notes and dashboard widgets

A logged-in user deletes or modifies other users' private notes and dashboard widgets because three write operations skip the ownership check their read counterparts apply.

Advisory ID: TP-2026-024
Product: OpenReplay (session replay and product analytics platform)
Vulnerability type: Authorization bypass through a user-controlled key (CWE-639)
CVE: CVE-2026-55880
CVSS 3.1: 7.1 (High) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L
Affected versions: <= 1.27.0
Fixed in: 1.25.0
Vendor advisory: GHSA-9xfv-p2fx-vmx9
Reported: 29 May 2026

Summary

OpenReplay is a session replay and product analytics platform in which notes and dashboards can be private per user. Three write operations skip the ownership check that their read and edit counterparts perform. The notes.delete function filters only by note and project ID and omits the user ID check that notes.edit and notes.get_note apply. Likewise, dashboards.update_widget and dashboards.remove_widget bypass the ownership predicate that dashboards.add_widget and the dashboard read paths apply. Because the objects carry sequential integer IDs, any logged-in user can delete or modify other users' private notes and widgets by ID enumeration without being allowed to read them. turingpoint verified the flow and reported it responsibly; the vendor fixed it in 1.25.0.

Root cause

The read path carries the ownership check: notes.edit filters with AND user_id (notes.py:162) and the dashboard paths with the (user_id OR is_public) predicate (dashboards.py:128). notes.delete, however, runs only UPDATE sessions_notes SET deleted_at WHERE note_id AND project_id with no user or is_public scope (notes.py:175), and the handler drops the user ID entirely (core_dynamic.py:524 delete_note). Likewise, dashboards.update_widget (dashboards.py:173) and dashboards.remove_widget (:186) write to dashboard_widgets using only dashboard_id and widget_id, unlike the scoped add_widget (:160). All affected routes run under OR_context, so they require only any authenticated context and no elevated role. Because the IDs are assigned sequentially, another user's target object is enumerable with no guessing effort.

Proof of Concept

As a logged-in low-role user (another user's private note with a sequential note_id):

PUT /<projectId>/notes/<other_note_id>/delete
Authorization: Bearer <user JWT>
-> 200 {state: success}, deleted_at is set (even though the note is not readable)

The write operation checks neither user_id nor is_public, so the condition matches on the guessed note_id alone; the same missing check applies to update_widget and remove_widget on other users' private dashboards.

Impact

  • Logged-in users delete other users' private session notes.
  • Logged-in users remove or modify widgets on other users' private dashboards.
  • No data disclosure; integrity and availability of other users' private objects are affected.
  • Sequential IDs enable target enumeration without an audit trail.

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.