# GraphQL private data without ownership check

Resolvers returning members or scopes by ID without an owner, privacy, or tenant filter

_Area: Web access control and IDOR. Seen in 2 disclosed reports; latest 2020-10-09. Updated 2026-10-23._

## How it works
A GraphQL resolver trusts the identifier it is handed. The caller supplies a list ID, program ID or scope path, and the resolver fetches the object and its members without asking whether this caller may see it. The assumption that breaks: that knowing an ID implies entitlement, or that the UI only shows IDs the user already owns. Persisted queries hide the schema but not the operation; the query stays callable by anyone who can replay it.

## Where it shows up
- Resolvers that take an object ID and return members or children with no ownership or privacy predicate in the query.
- Persisted-query operation names harvested from client JS bundles (web, mobile, Tweetdeck, Wayback snapshots) — a delivery mechanism, not the root cause.
- Feature paths appended to object URLs (e.g. a policy-scopes tab) exposing cross-tenant queries.
- Endpoints reachable unauthenticated that resolve data across all tenants.
- Search backed by an external index (Elasticsearch) where group-scoped queries return documents from other private groups.

## How to review
For every resolver returning private or member data, find the authorization predicate and confirm it runs before data is returned: the caller must be the owner, an authorized member, or hold a role granting access, and private/invite-only objects must be filtered for non-owners. Trace the resolver's data-layer query itself for an owner, privacy or tenant filter — a resolver-level guard that the underlying query bypasses is not enough. For unauthenticated or cross-tenant queries, confirm the result set is scoped to the caller's tenant. Treat any ID-taking query as untrusted input regardless of how the ID was generated.

## Variants seen
- Private list members returned for any listId without a privacy check [E1](https://bugbountylibrary.com/r/blog%3Ablog-ryotak-net-post-twitter-list-chain.md)[E2](https://bugbountylibrary.com/r/h1%3A885539.md).
- Persisted GraphQL query replayed after harvesting operation names from shipped bundles [E2](https://bugbountylibrary.com/r/h1%3A885539.md).
- Unauthenticated cross-tenant query leaking private program names and scopes [E3](https://bugbountylibrary.com/r/h1%3A708820.md).
- Elasticsearch-backed group search returning merge requests, issues and activity from unrelated private groups [E3](https://bugbountylibrary.com/r/h1%3A708820.md).

## Impact and severity
Disclosed private list members, private program names and scopes, and private group metadata [E1](https://bugbountylibrary.com/r/blog%3Ablog-ryotak-net-post-twitter-list-chain.md)[E2](https://bugbountylibrary.com/r/h1%3A885539.md)[E3](https://bugbountylibrary.com/r/h1%3A708820.md). Severity rises when the endpoint is unauthenticated or cross-tenant, and when the missing predicate sits in a shared data layer that many resolvers reuse.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ablog-ryotak-net-post-twitter-list-chain.md
- E2: https://bugbountylibrary.com/r/h1%3A885539.md
- E3: https://bugbountylibrary.com/r/h1%3A708820.md
