# auth: Patreon provider assigns the same local user ID to every authenticated Patreon account, enabling cross‑user impersonation

go-pkgz/auth Patreon provider hashes an uninitialized field, so every Patreon login maps to the same local user ID, collapsing all accounts into one identity.

- Payout: Research
- Severity: critical
- Program: go-pkgz/auth
- Found by: Nadav0077
- Disclosed: 2026-04-30
- CVE: CVE-2026-42560
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-f6qq-3m3h-4g42
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-f6qq-3m3h-4g42
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:critical tech:go tech:oauth technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass

**Target:** Patreon OAuth provider user-mapping logic in Go auth library
**Root cause:** mapUser hashes userInfo.ID (still empty) instead of uinfoJSON.Data.ID, so every Patreon account yields patreon_+sha1("") = patreon_da39a3ee...
**Steps:**
1. Create `v2/provider/patreon_repro_test.go` calling `NewPatreon(Params{URL,Cid,Csecret})` and `r.mapUser(UserData{}, payload)` with two payloads differing in `data.id` (1111111 vs 9999999)
2. Run `cd v2 && go test ./provider -run TestPatreonSharedIdentity -v`
3. Observe both return `patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709`
**How found:** Code reading of the Patreon mapUser closure revealed the hash input was the destination struct field rather than the parsed Patreon ID; existing tests asserting the constant hash confirmed the broken behavior.
**Exploitation:** Any attacker authenticating with their own Patreon account receives the shared local ID, so an app keying accounts/roles/entitlements on token.User.ID merges them with other Patreon users, exposing or overwriting their data and inheriting any privileges granted to that key.
**Escalation:** If the shared local account holds elevated privileges or paid subscription state (is_paid_sub), every Patreon login inherits them.
**Insight:** When auditing OAuth provider mappers, check that the hashed/derived ID comes from the parsed provider response, not the freshly-allocated output struct; tests asserting a fixed hash constant are a red flag for identity collisions.

## Proof of concept (copied exactly from the original report)

**Go test proving two Patreon users share one ID**

```
package provider

import "testing"

func TestPatreonSharedIdentity(t *testing.T) {
    r := NewPatreon(Params{
        URL:     "http://example.com",
        Cid:     "cid",
        Csecret: "secret",
    })

    a := r.mapUser(UserData{}, []byte(`{
        "data": {
            "attributes": {
                "full_name": "Alice",
                "image_url": "https://example.com/alice.png"
            },
            "id": "1111111"
        }
    }`))

    b := r.mapUser(UserData{}, []byte(`{
        "data": {
            "attributes": {
                "full_name": "Bob",
                "image_url": "https://example.com/bob.png"
            },
            "id": "9999999"
        }
    }`))

    if a.ID != b.ID {
        t.Fatalf("expected IDs to collide, got %q and %q", a.ID, b.ID)
    }

    t.Logf("Alice -> %s", a.ID)
    t.Logf("Bob   -> %s", b.ID)
}
```

**Command to run the reproduction test**

```
cd v2
go test ./provider -run TestPatreonSharedIdentity -v
```

