# An attacker can archive and unarchive any structured scope object on HackerOne

GraphQL ArchiveScope/UnarchiveStructuredScope mutations lacked ownership checks, letting any authenticated user archive or unarchive any program's structured scope by global ID.

- Payout: hidden
- Severity: high
- Program: HackerOne
- Found by: ahacker1
- Disclosed: 2022-04-18
- Write-up quality: 6/10
- Original: https://hackerone.com/reports/1501611
- Library page: https://bugbountylibrary.com/r/h1%3A1501611
- Tags: area:web3 domain:web impact:integrity kind:bounty platform:hackerone sev:high tech:graphql technique:source-review vuln:access-control vuln:idor

**Target:** GraphQL API mutation /graphql (archiveStructuredScope, unarchiveStructuredScope)
**Root cause:** The archive/unarchive GraphQL mutations accepted a global structured_scope_id and performed the mutation without verifying the caller owned or administered the target program's scope.
**Steps:**
1. Obtain target structured_scope_id: base64 of `gid://hackerone/StructuredScope/<NUMBER>` (e.g. `Z2lkOi8vaGFja2Vyb25lL1N0cnVjdHVyZWRTY29wZS85NDc3Mw==`), or intercept the program profile page response.
2. Send `POST /graphql` with `X-Auth-Token` and body `{"operationName":"ArchiveScope","variables":{"structured_scope_id":"<id>"},"query":"mutation ArchiveScope($structured_scope_id: ID!) { archiveStructuredScope(input: {structured_scope_id: $structured_scope_id}) { was_successful structured_scope { id archived_at } } }"}`.
3. To unarchive, send the same request with operationName `UnarchiveStructuredScope` and mutation `unarchiveStructuredScope`.
4. Response reports no structured scope exists, but the scope is still archived/unarchived.
**How found:** Reporter inspected the program scope edit page, noticed the structured_scope_id was a base64 global ID, and tested the archive mutation against a scope belonging to a program they did not control; the misleading not-found response masked the successful mutation.
**Exploitation:** Any authenticated user with a valid X-Auth-Token submits the mutation with a victim program's structured_scope_id; the backend mutates the record despite returning a not-found error, allowing mass archiving/unarchiving of all 90000+ scopes.
**Escalation:** Enumerate all scope IDs to archive/unarchive every program's scopes at scale.
**Insight:** When a mutation returns 'not found' but the UI state changes, the authorization check may be missing while the response serializer fails — verify side effects (re-fetch the object) rather than trusting the error response. Global IDs (base64 gid://) are enumerable and often trusted server-side.
