June 29, 2026
IDOR in a Session Replay API: One Sequential ID From Wiping Any Customer’s Data
Hi everyone : )

By 0xlumi
3 min read
While testing the Session Replay feature of an enterprise analytics platform, I found a Broken Object-Level Authorization (BOLA / IDOR) bug that let any authenticated user rename — and permanently delete — the saved recording collections of any other customer on the platform.
It scored 8.2 (High) on CVSS. Full disclosure: it also came back as a duplicate — another hunter had already reported the same issue. But the finding itself is a textbook cross-tenant IDOR, and the way it scales from "edit my own folder" to "wipe the entire platform" is worth breaking down. Let's get into it.
Who Am I?
I'm 0xlumi, a security researcher focused on identifying and responsibly disclosing vulnerabilities in web applications.
My work mainly revolves around:
- Access control issues
- Authentication flaws
- Logical vulnerabilities
I enjoy digging into how systems actually enforce permissions — not just how they appear to.
Understanding the Target
The platform offers a functionality called Session Replay. It is a video-like recreation of user journeys that capture actions like clicks and scrolls, and the data is used to optimize user experiences. Teams record the user sessions, then save the interesting ones into Collections (marked recordings). Think of them as curated folders of evidence: conversion studies and UX research.
Each customer lives in their own project (their tenant). Collections are scoped to a project. Access control is supposed to guarantee:
- A user can only see their own project's collections
- A user can only rename or delete collections inside their own project
From the UI, that's exactly how it behaved. On the surface, multi-tenant isolation looked solid.
The Endpoints
Two operations manage a collection's lifecycle rename (PUT) and delete (DELETE):
PUT /api/sessions/v2/projects/{projectId}/marked-recordings/collections/{collectionId}
DELETE /api/sessions/v2/projects/{projectId}/marked-recordings/collections/{collectionId}PUT /api/sessions/v2/projects/{projectId}/marked-recordings/collections/{collectionId}
DELETE /api/sessions/v2/projects/{projectId}/marked-recordings/collections/{collectionId}Two identifiers sit in the path: projectId (which tenant) and collectionId (which collection).
The Thought That Triggered the Bug
So I started testing the api but the backend clearly checks projectId against my session token, I can't just point at another tenant's project and act on it.
But that raised the obvious question:
"Does it actually verify that
collectionIdbelongs to my project? Or does it trust the ID just because myprojectIdand token are valid?"
If the server validates the tenant but not the object inside it, the collectionId becomes a free-for-all.
The Setup: Two Real Accounts
To prove a genuine cross-account boundary, I used two separate accounts in two separate projects:
- User A — the attacker (project
100200) - User B — the victim (project
200900)
Each created a marked-recordings collection in their own Session Replay area, so there was a real object on each side to target.
The Flaw: Object Reference Without Object Authorization
I captured User A's "rename collection" request and sent it to Repeater:
PUT /api/sessions/v2/projects/100200/marked-recordings/collections/500111
Authorization: Bearer <User A's token>
Content-Type: application/json
{"name":"User A Collection"}PUT /api/sessions/v2/projects/100200/marked-recordings/collections/500111
Authorization: Bearer <User A's token>
Content-Type: application/json
{"name":"User A Collection"}The only thing identifying which collection gets edited is that trailing collectionId. So I left everything authenticated as User A: A's token, A's own projectId and swapped only the collection ID to one belonging to User B (500108, a neighbouring collection):
PUT /api/sessions/v2/projects/100200/marked-recordings/collections/500108
Authorization: Bearer <User A's token>
Content-Type: application/json
{"name":"HIJACKED"}PUT /api/sessions/v2/projects/100200/marked-recordings/collections/500108
Authorization: Bearer <User A's token>
Content-Type: application/json
{"name":"HIJACKED"}Note what didn't change: the project in the path is still User A's. I'm not impersonating User B's tenant, I'm using my own, and reaching across it.
What I Expected vs What Happened
Expected: 403 Forbidden "that collection isn't in your project."
Actual: 200 OK and the response body showed the modified object belonging to User B's project.
Using nothing but their own session, User A renamed User B's collection.
Escalating: From Rename to Permanent Delete
The exact same swap works on DELETE:
DELETE /api/sessions/v2/projects/100200/marked-recordings/collections/500108
Authorization: Bearer <User A's token>DELETE /api/sessions/v2/projects/100200/marked-recordings/collections/500108
Authorization: Bearer <User A's token>200 OK. Reload User B's collection list → it's gone. Not hidden, not archived — permanently deleted.
So this isn't just "rename someone's folder." It's "destroy another customer's curated work, irreversibly."
The Missing Piece: Sequential IDs
Here's what turns a single cross-account bug into a platform-wide one.
The collection IDs are globally sequential — 500108, 500111, and so on. They're not per-tenant, not random, not UUIDs. They're a single global counter shared across every customer.
That means:
- Neighbouring IDs belong to other tenants
- An attacker can simply walk the range
- No GUID guessing, no leak required — just count
Loop the ID range against the DELETE endpoint, and you can wipe the collections of every customer on the platform, from one ordinary account.
Why This Matters
- Highly automatable — sequential IDs mean a ~20-line script is enough to enumerate and act on every collection in the database.
- Permanent data destruction — these are non-disposable work products: fraud cases, conversion studies. No undo, no recycle bin.
- Cross-tenant trust collapse — for an analytics vendor, customers paying for private analytics having their data silently editable and deletable by any other customer is an existential trust problem. That's lost renewals and lost contracts.
Program Response
- Reported through the program with a full PoC: two accounts, rename + delete, and a video.
- Triaged and confirmed as a real issue — then closed as a duplicate; another hunter had already reported the same bug. Points awarded accordingly.
Duplicates sting, but they're part of the game. The bug was real, the severity was High, and the methodology is what carries over to the next target.
I really appreciate you reading until this point
Till next time. Keep Hacking, Keep Evolving :)