# Staff Member can Get POS Access Without User Interaction

Staff member with only Manage Locations permission used retailUserDataUpdate GraphQL mutation to grant themselves POS access and set a PIN, bypassing admin approval.

- Payout: hidden
- Severity: medium
- Program: Shopify
- Found by: ngalog
- Disclosed: 2020-11-19
- Write-up quality: 6/10
- Original: https://hackerone.com/reports/1018094
- Library page: https://bugbountylibrary.com/r/h1%3A1018094
- Tags: area:web3 domain:web impact:privesc kind:bounty platform:hackerone sev:medium tech:graphql technique:source-review vuln:access-control vuln:auth-bypass vuln:privilege-escalation

**Target:** GraphQL Admin API mutation retailUserDataUpdate
**Root cause:** The retailUserDataUpdate mutation does not verify that the caller has permission to modify POS access settings, allowing a staff member with only Manage Locations permission to set posAccess and pin on their own StaffMember record.
**Steps:**
1. Login as staff member with only `Manage Locations` permission in a shop with POS channel active
2. Ensure the staff account has no POS access enabled
3. Send GraphQL mutation `{"query":"mutation {retailUserDataUpdate(id:\"gid://shopify/StaffMember/63779504283\",retailUserData:{posAccess:true,pin:\"1423\"}){staffMember{name canAccessPrivateApps authenticationSettings{tfaEnabled}}userErrors{message}}}"}` replacing the id with own staff member id (found in admin/settings/account URL)
4. Staff account now has POS enabled with PIN set
**How found:** Researcher noticed POS access is normally admin-controlled, then tested whether the retailUserDataUpdate mutation enforced authorization when called by a staff member holding only Manage Locations permission.
**Exploitation:** A low-privileged staff member submits the mutation against their own StaffMember GID, setting posAccess:true and a chosen PIN, gaining POS terminal access without any admin approval or interaction.
**Insight:** When a GraphQL mutation accepts an object GID and privileged fields (posAccess, pin), test calling it as a lower-privileged staff member against your own GID to check for missing field-level authorization rather than just object-level access.

