VYPR
Critical severity9.1GHSA Advisory· Published May 9, 2026· Updated May 13, 2026

CVE-2026-42560

CVE-2026-42560

Description

auth provides authentication via oauth2, direct and email. From versions 1.18.0 to before 1.25.2 and 2.0.0 to before 2.1.2, the Patreon OAuth provider maps every authenticated Patreon account to the same local user.ID, instead of deriving a unique ID from the Patreon account returned by Patreon. In practice, this means all Patreon-authenticated users of an application using this library are collapsed into a single local identity. Any application that trusts token.User.ID as the stable account key can end up mixing or fully merging unrelated Patreon users, which can lead to cross-account access, privilege confusion, and subscription-state leakage. This issue has been patched in versions 1.25.2 and 2.1.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/go-pkgz/authGo
>= 1.18.0, < 1.25.21.25.2
github.com/go-pkgz/auth/v2Go
>= 2.0.0, < 2.1.22.1.2

Affected products

1

Patches

1
c0b15ee72a84

Merge commit from fork

https://github.com/go-pkgz/authNadavApr 22, 2026via ghsa
4 files changed · +40 6
  • provider/providers.go+1 1 modified
    @@ -254,7 +254,7 @@ func NewPatreon(p Params) Oauth2Handler {
     
     			uinfoJSON := uinfo{}
     			if err := json.Unmarshal(bdata, &uinfoJSON); err == nil {
    -				userInfo.ID = "patreon_" + token.HashID(sha1.New(), userInfo.ID)
    +				userInfo.ID = "patreon_" + token.HashID(sha1.New(), uinfoJSON.Data.ID)
     				userInfo.Name = uinfoJSON.Data.Attributes.FullName
     				userInfo.Picture = uinfoJSON.Data.Attributes.ImageURL
     
    
  • provider/providers_test.go+19 2 modified
    @@ -176,7 +176,7 @@ func TestProviders_NewPatreon(t *testing.T) {
     			},
     			"id": "0000000"
     		}}`))
    -	assert.Equal(t, token.User{Name: "Corgi The Dev", ID: "patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709",
    +	assert.Equal(t, token.User{Name: "Corgi The Dev", ID: "patreon_4e079d0555e5a2b460969c789d3ad968a795921f",
     		Picture: "https://c8.patreon.com/2/400/0000000", IP: ""}, user, "got %+v", user)
     
     	udata = UserData{}
    @@ -201,14 +201,31 @@ func TestProviders_NewPatreon(t *testing.T) {
     		}}`))
     	assert.Equal(
     		t,
    -		token.User{Name: "Corgi The Dev", ID: "patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709",
    +		token.User{Name: "Corgi The Dev", ID: "patreon_4e079d0555e5a2b460969c789d3ad968a795921f",
     			Picture: "https://c8.patreon.com/2/400/0000000", IP: "", Attributes: map[string]interface{}{"is_paid_sub": true}},
     		user,
     		"got %+v",
     		user,
     	)
     }
     
    +// distinct Patreon accounts must produce distinct local user IDs.
    +// the previous implementation hashed the uninitialized userInfo.ID, so every
    +// Patreon user collapsed into the same identity.
    +func TestProviders_NewPatreon_DistinctIDs(t *testing.T) {
    +	r := NewPatreon(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"})
    +
    +	alice := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Alice"},"id":"1111111"}}`))
    +	bob := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Bob"},"id":"9999999"}}`))
    +
    +	assert.NotEqual(t, alice.ID, bob.ID, "different Patreon ids must map to different local user IDs")
    +	assert.Equal(t, "patreon_2ea6201a068c5fa0eea5d81a3863321a87f8d533", alice.ID)
    +	assert.Equal(t, "patreon_22067cb54a7b24764186f1e48cb4586772733cd7", bob.ID)
    +
    +	empty := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Nobody"},"id":""}}`))
    +	assert.NotEqual(t, alice.ID, empty.ID, "empty Patreon id must not collide with a real one")
    +}
    +
     func TestProviders_NewMicrosoft(t *testing.T) {
     	t.Run("default tenant", func(t *testing.T) {
     		r := NewMicrosoft(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"})
    
  • v2/provider/providers.go+1 1 modified
    @@ -254,7 +254,7 @@ func NewPatreon(p Params) Oauth2Handler {
     
     			uinfoJSON := uinfo{}
     			if err := json.Unmarshal(bdata, &uinfoJSON); err == nil {
    -				userInfo.ID = "patreon_" + token.HashID(sha1.New(), userInfo.ID)
    +				userInfo.ID = "patreon_" + token.HashID(sha1.New(), uinfoJSON.Data.ID)
     				userInfo.Name = uinfoJSON.Data.Attributes.FullName
     				userInfo.Picture = uinfoJSON.Data.Attributes.ImageURL
     
    
  • v2/provider/providers_test.go+19 2 modified
    @@ -176,7 +176,7 @@ func TestProviders_NewPatreon(t *testing.T) {
     			},
     			"id": "0000000"
     		}}`))
    -	assert.Equal(t, token.User{Name: "Corgi The Dev", ID: "patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709",
    +	assert.Equal(t, token.User{Name: "Corgi The Dev", ID: "patreon_4e079d0555e5a2b460969c789d3ad968a795921f",
     		Picture: "https://c8.patreon.com/2/400/0000000", IP: ""}, user, "got %+v", user)
     
     	udata = UserData{}
    @@ -201,14 +201,31 @@ func TestProviders_NewPatreon(t *testing.T) {
     		}}`))
     	assert.Equal(
     		t,
    -		token.User{Name: "Corgi The Dev", ID: "patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709",
    +		token.User{Name: "Corgi The Dev", ID: "patreon_4e079d0555e5a2b460969c789d3ad968a795921f",
     			Picture: "https://c8.patreon.com/2/400/0000000", IP: "", Attributes: map[string]interface{}{"is_paid_sub": true}},
     		user,
     		"got %+v",
     		user,
     	)
     }
     
    +// distinct Patreon accounts must produce distinct local user IDs.
    +// the previous implementation hashed the uninitialized userInfo.ID, so every
    +// Patreon user collapsed into the same identity.
    +func TestProviders_NewPatreon_DistinctIDs(t *testing.T) {
    +	r := NewPatreon(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"})
    +
    +	alice := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Alice"},"id":"1111111"}}`))
    +	bob := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Bob"},"id":"9999999"}}`))
    +
    +	assert.NotEqual(t, alice.ID, bob.ID, "different Patreon ids must map to different local user IDs")
    +	assert.Equal(t, "patreon_2ea6201a068c5fa0eea5d81a3863321a87f8d533", alice.ID)
    +	assert.Equal(t, "patreon_22067cb54a7b24764186f1e48cb4586772733cd7", bob.ID)
    +
    +	empty := r.mapUser(UserData{}, []byte(`{"data":{"attributes":{"full_name":"Nobody"},"id":""}}`))
    +	assert.NotEqual(t, alice.ID, empty.ID, "empty Patreon id must not collide with a real one")
    +}
    +
     func TestProviders_NewMicrosoft(t *testing.T) {
     	t.Run("default tenant", func(t *testing.T) {
     		r := NewMicrosoft(Params{URL: "http://demo.remark42.com", Cid: "cid", Csecret: "cs"})
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.