Coordinated Disclosure Timeline
- 2025-11-19: Reported via email.
- 2025-12-04: Issue was fixed, but Sentry decided it was not a security vulnerability.
Summary
Sentry version 25.11.0 contains a privilege escalation vulnerability where a scope mismatch issue could allow unauthorized users to delete events, potentially compromising the integrity of event data.
Project
Sentry
Tested Version
Details
Privilege escalation via scope mismatch allows deletion of events (GHSL-2025-120)
A privilege escalation vulnerability was identified in the group reprocessing flow of Sentry. Users with only event:write scope (not event:admin) can trigger mass deletion of issue (group) events by invoking the reprocessing endpoint with remainingEvents=”delete”. This bypasses the intended stricter permission for destructive deletion operations (which normally require event:admin for DELETE actions).
Vulnerability details
The vulnerability stems from the GroupPermission model only requiring event:write as the minimum permission for POST actions, while DELETE actions require event:admin:
- As a result, the issue delete endpoint (DELETE,
/api/0/issues/<ISSUE-ID>) correctly requiresevent:adminpermission. - However, the issue reprocessing endpoint (POST
/api/0/issues/<ISSUE-ID>/reprocessing/) incorrectly requires onlyevent:writepermission, even though it allows deletion of events.
A request to post on the GroupReprocessingEndpoint leads to checks whether the provided max_events number is at least 1 and whether remainingEvents is either keep or delete:
class GroupReprocessingEndpoint(GroupEndpoint):
[..]
def post(self, request: Request, group) -> Response:
[..]
max_events = request.data.get("maxEvents")
if max_events:
max_events = int(max_events)
if max_events <= 0:
return self.respond({"error": "maxEvents must be at least 1"}, status=400)
else:
max_events = None
remaining_events = request.data.get("remainingEvents")
if remaining_events not in ("delete", "keep"):
return self.respond({"error": "remainingEvents must be delete or keep"}, status=400)
After that the reprocess_group task is called which in turn calls buffered_handle_remaining_events, which then calls the handle_remaining_events task.
The handle_remaining_events task then deletes the events if remainingEvents was set to delete:
def handle_remaining_events
[..]
if remaining_events == "delete":
for cls in EVENT_MODELS_TO_MIGRATE:
cls.objects.filter(project_id=project_id, event_id__in=event_ids).delete()
# Remove from nodestore
node_ids = [Event.generate_node_id(project_id, event_id) for event_id in event_ids]
nodestore.backend.delete_multi(node_ids)
# Tell Snuba to delete the event data.
eventstream.backend.tombstone_events_unsafe(
project_id, event_ids, from_timestamp=from_timestamp, to_timestamp=to_timestamp
)
Impact
This issue may lead to privilege escalation and might allow unauthorized users to delete events.
CWEs
- CWE-269: Improper Privilege Management
- CWE-863: Incorrect Authorization
Credit
These issues were discovered with the GitHub Security Lab Taskflow Agent and verified by GHSL team members @m-y-mo (Man Yue Mo) and @p- (Peter Stöckli).
Contact
You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2025-120 in any communication regarding this issue.
Disclosure Policy
This report is subject to a 90-day disclosure deadline, as described in more detail in our coordinated disclosure policy.