At a glance

Impact
An unauthorized account could read pending join requests for a private Direct group if it knew the thread ID.
Cause
The GraphQL resolver returned request data without verifying thread access and the required role.
Fix / outcome
Meta triaged and awarded the report. The resolver needs an access and role check before returning requests.

Instagram keeps pending Direct group requests out of view unless the current account is allowed to manage them. The mobile GraphQL API did not consistently enforce the same rule.

The affected operation was IGDirectGetThreadParticipantRequestsQuery. It accepted an ig_thread_id from the client and returned participant-request data for that thread. In my tests, the authenticated account was neither a member nor an admin of the target group.

Program outcome

Meta triaged the report and issued a bounty. The award amount is not included here.

The authorization gap

The interesting part was not that the client could send a thread ID. Object identifiers have to come from somewhere. The problem was what happened after the server received it.

The resolver viewer_thread_from_ig_thread_id returned participant-request information without adequately checking the authenticated user's relationship with the selected thread. A valid identifier was enough in the vulnerable case.

The Instagram UI already enforced the expected boundary. A normal user who was not a group admin could not open pending requests. An account outside the group had even less reason to see them. That access rule needed to exist in the resolver, not only in the client.

Request structure

I intercepted a legitimate request from the iOS app and changed only variables.ig_thread_id. The GraphQL document ID, Bloks version, and other app metadata remained untouched.

The test used Instagram iOS version 381.1.2.26.83. The captured request identified the device profile as iPhone10,4 running iOS 16.1.1.

Redacted mobile request
POST /graphql_www HTTP/2
Host: i.instagram.com
X-FB-Friendly-Name: IGDirectGetThreadParticipantRequestsQuery
X-Root-Field-Name: viewer_thread_from_ig_thread_id

variables={"ig_thread_id":"<known_target_thread_id>"}

Authentication headers, session values, device identifiers, document IDs, and real user information are intentionally omitted.

Reproduction

  1. Sign in to a controlled Instagram account in the iOS app.
  2. Confirm the account is not a member or admin of the target Direct group.
  3. Capture a legitimate IGDirectGetThreadParticipantRequestsQuery request.
  4. Replace the original ig_thread_id with the ID of a known, controlled group.
  5. Forward the request without changing the surrounding client metadata.
  6. Observe participant-request fields in the response.

I reproduced the behavior with two known thread IDs. I did not brute-force identifiers, test random groups, or attempt to enumerate Instagram threads.

What came back

The response contained group-level counts and information about pending requesters:

  • total_participant_requests
  • total_thread_participants
  • participant_requests_added_by_map
  • requester_igid
  • added_by_username
  • participant_requests
  • Instagram user ID
  • interop_messaging_user_fbid
  • username and full name
  • verification status
  • profile picture URL

No message contents were exposed. The privacy issue was the membership activity around a private group: who wanted to join, who added them, and the identifiers attached to those accounts.

Classification and impact

CWE-862, Missing Authorization, describes the missing server-side check. CWE-639, Authorization Bypass Through User-Controlled Key, also fits because ig_thread_id selected the object whose data was returned.

In API terms, this was broken object-level authorization. An unauthorized account could read private group activity when it already knew a valid thread identifier. The report did not claim that identifiers were guessable or enumerable.

Meta timeline

  • 8 June 2026, 10:28 PM: report submitted
  • 17 July 2026, 4:55 PM: received a reply from Meta
  • 22 July 2026, 7:06 PM: report triaged
  • 29 July 2026, 12:10 PM: bounty paid

The fix belongs at the resolver boundary. Before returning participant requests, the backend should verify that the authenticated account can access the target thread and has the role required to view pending members.