Skip to content

fix(incidents): Sync Slack participants before visibility check for private incidents#253

Draft
rgibert wants to merge 1 commit into
mainfrom
rgibert/fix-private-incident-visibility-sync
Draft

fix(incidents): Sync Slack participants before visibility check for private incidents#253
rgibert wants to merge 1 commit into
mainfrom
rgibert/fix-private-incident-visibility-sync

Conversation

@rgibert

@rgibert rgibert commented Jun 11, 2026

Copy link
Copy Markdown
Member

The UI views checked filter_visible_to_user() before syncing participants from Slack, causing 404s for channel members whose participant records hadn't been synced to the DB yet. Meanwhile, Slack commands (e.g. /inc update) use get_incident_from_channel() which has no visibility check at all -- so a user could interact with a private incident via Slack but get a 404 when trying to view it in Firetower.

Adds a _get_visible_incident() helper that tries the normal visibility filter first (fast path), then for private incidents, force-syncs participants from Slack and re-checks visibility before returning 404. This ensures channel members are recognised on their first UI visit. The fallback path only fires for authenticated users on private incidents where they aren't already a participant/captain/reporter, so the Slack API cost is minimal.

Updated views: IncidentDetailUIView, ActionItemListView, SyncActionItemsView, SyncIncidentParticipantsView.

Refs LINEAR-RELENG-832

Agent transcript: https://claudescope.sentry.dev/share/vrooxZ-Kyb0pEjwNrvGOcI0W8sq0EcAuXNWsrfmrCKQ

…rivate incidents

The UI views checked visibility before syncing participants from Slack,
causing 404s for channel members who hadn't been synced to the DB yet.
Meanwhile, Slack commands had no visibility check at all, so channel
members could interact with private incidents via /inc but not view
them in the Firetower UI.

Add a _get_visible_incident helper that tries the normal visibility
filter first, then falls back to a force-sync from Slack for private
incidents before re-checking visibility. This ensures channel members
are recognised on their first UI visit.

Refs LINEAR-RELENG-832
Co-Authored-By: Claude <noreply@anthropic.com>

Agent transcript: https://claudescope.sentry.dev/share/Z34J4fwlHa2-tleFcG8vgI7ko2aMzMxOpyE6yfvdj5Y
queryset = filter_visible_to_user(self.get_queryset(), self.request.user)

obj = get_object_or_404(queryset, id=numeric_id)
obj = _get_visible_incident(self.get_queryset(), numeric_id, self.request.user)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unauthenticated-actor-scoped abuse: any authenticated user can trigger unlimited force Slack syncs on private incidents via SyncIncidentParticipantsView

Any authenticated user who knows a private incident's numeric ID can POST to /api/incidents/{id}/sync-participants/ and trigger Slack API calls unconditionally — _get_visible_incident force-syncs participants before confirming the caller has access, and no throttle class is configured on this view. For a newly-visible caller, two force syncs fire per request (one inside _get_visible_incident, one in post()); for an unrelated caller the Slack sync still fires before returning 404.

Evidence
  • _get_visible_incident (line 82) calls sync_incident_participants_from_slack(incident, force=True) for every private incident where the caller fails the initial visibility filter — before confirming the caller has any legitimate access.
  • SyncIncidentParticipantsView.post() (line 368) calls sync_incident_participants_from_slack(incident, force=True) again after get_object() returns, so a newly-synced caller triggers two force syncs in one request.
  • SyncIncidentParticipantsView declares permission_classes = [IncidentPermission] (authenticated only) but defines no throttle_classes, and the view docstring confirms it 'bypasses throttle (force=True)'.
  • A caller who does not become visible after the first sync still gets a 404, but the Slack API call already occurred — this path can be triggered by any authenticated user for any private incident ID via brute-force.
Also found at 2 additional locations
  • src/firetower/incidents/views.py:103-106
  • src/firetower/incidents/tests/test_views.py:1893

Identified by Warden security-review · 5H9-RY9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant