Overview
I found this while comparing HackerOne’s program administration interface with the requests sent to its GraphQL API.
My limited Program Admin account could not open the group-management page. The API behaved differently: calling the addAsProgramAdmin mutation directly let me add my account—or another organization member—to privileged default groups.
Those groups included report and reward permissions, so the result was privilege escalation rather than a simple interface bypass.
Where the check was missing
HackerOne returned a forbidden response when an account without Group Manager or organization administrator access opened the group-management page. That was the expected behavior.
The GraphQL mutation did not apply the same restriction. It accepted a user ID and a program ID, then added the selected user to the program’s default administrative groups. If those groups did not exist, the mutation created them first.
The server completed the request without checking whether the caller was allowed to manage groups or change another user’s membership.
How I reproduced it
I tested the issue in a HackerOne sandbox program with two accounts:
- As an organization administrator, I created a group that granted only Program Admin access.
- I invited a second account and assigned it only to that group.
- From the second account, I opened the group-management page and received the expected forbidden response.
- Still signed in to the restricted account, I sent the following mutation with the sandbox user and program IDs.
The mutation was:
mutation {
addAsProgramAdmin(
input: {
user_ids: [<TEST_USER_ID>]
team_id: "<ENCODED_TEST_PROGRAM_ID>"
}
) {
was_successful
}
}
The request succeeded. HackerOne created the missing default administrative groups and added the selected user to them.
The impact went beyond group creation
The first successful request showed that a restricted user could create groups. I kept testing and found that the same mutation also worked with existing privileged groups.
The default administrative groups already had report-management and reward permissions. By joining them, a Program Admin could read inbox reports, modify report data, and award bounties without being granted those capabilities directly.
Removing the account from a privileged group did not close the path. The user could run the mutation again and regain the group’s permissions, including permissions added by an organization administrator later.
Impact
The restricted Program Admin account could:
- Create groups without Group Manager permission.
- Add my account or another organization member to privileged groups.
- Read and modify inbox reports through inherited permissions.
- Award bounties through the default reward permissions.
- Add my account back to a privileged group after an administrator removed it.
This could move a limited account close to full administrative control over a program, putting private report data, report integrity, and bounty decisions at risk.
Resolution
I reported the issue to HackerOne on February 13, 2024. On February 18, I updated the report after confirming the wider report and reward impact. HackerOne rated the issue High, fixed it, and resolved the report.
A limited-disclosure version is available in HackerOne report 2372018.
The permission check needed to run inside the mutation. Restricting the group-management page alone could not stop a direct API request.
Takeaway
A forbidden page does not mean the action behind it is protected. Every mutation that changes permissions needs its own server-side authorization check.
Group membership is especially sensitive: if a user can add themselves to a privileged group, every permission attached to that group becomes part of the impact.