Repository: google/go-github (Go)

Issue:
Access token leakage when reusing the client between users
## Description

Reusing the `github.Client` instance between different users' sessions leads to leaking the access_token between sessions.

## How to reproduce

This scenario works in a hosted service environment.

1. Create a global client configured WithEnterpriseURLs
2. For client requests, create a clone using WithAuthToken(userToken)
3. All underlying API calls will be performed with the first used access_token, ignoring the other tokens

This test reproduces the issue

```golang
func Test_github_access_token(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		accessToken := r.Header.Get("Authorization")
		_, _ = fmt.Fprintf(w, `{"login": "%s"}`, accessToken)
	}))
	clientPreconfiguredWithURLs, err := github.NewClient(nil).WithEnterpriseURLs(srv.URL, srv.URL)
	require.NoError(t, err)

	aliseClient := clientPreconfiguredWithURLs.WithAuthToken("alise")
	bobClient := clientPreconfiguredWithURLs.WithAuthToken("bob")

	alise, _, err := aliseClient.Users.Get(context.Background(), "")
	require.NoError(t, err)
	assert.Equal(t, "Bearer alise", alise.GetLogin())

	bob, _, err := bobClient.Users.Get(context.Background(), "")
	require.NoError(t, err)
	assert.Equal(t, "Bearer bob", bob.GetLogin())
}
```

and the result
```
Error:      	Not equal: 
            	expected: "Bearer bob"
            	actual  : "Bearer alise"
```

## Used environment

- go version go1.21.3 darwin/amd64  
- github.com/google/go-github/v57 v57.0.0
- github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
- github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=


Discussion:
Have you tried the latest version after #3011?
So, I tried the new version, and the test still fails

```
github.com/google/go-github/v58 v58.0.0
github.com/google/go-github/v58 v58.0.0 h1:Una7GGERlF/37XfkPwpzYJe0Vp4dt2k1kCjlxwjIvzw=
github.com/google/go-github/v58 v58.0.0/go.mod h1:k4hxDKEfoWpSqFlc8LTpGd9fu2KrV1YAa6Hi6FmDNY4=
```
Also, I've tested it on `master` ref https://github.com/google/go-github/commit/e9f52699f5e55a4791567bce668f56446e6343ef and test still fails with unexpected assertions
Thank you for the reproducible test case, @legigor !
When you get a chance, please review then LGTM+Approve #3051 if it fixes the problem you are seeing.
👍  So, I confirm that the test PASSED being runned on the branch https://github.com/gmlewis/go-github/tree/i3043-leaky-client-transport-copy

Thank you!
Thanks, but #3051 is blocked until I can get an LGTM+Approval from any other contributor to this repo - would you mind doing that, @legigor so I can merge it, please?
@gmlewis 👍  LGTM

I know it's too late, but I just wanted to express my appreciation :)



Patch:
diff --git a/github/github.go b/github/github.go
index 8bc35e5efe2..a7326a7bd5d 100644
--- a/github/github.go
+++ b/github/github.go
@@ -450,15 +450,18 @@ func (c *Client) copy() *Client {
 	c.clientMu.Lock()
 	// can't use *c here because that would copy mutexes by value.
 	clone := Client{
-		client:                  c.client,
+		client:                  &http.Client{},
 		UserAgent:               c.UserAgent,
 		BaseURL:                 c.BaseURL,
 		UploadURL:               c.UploadURL,
 		secondaryRateLimitReset: c.secondaryRateLimitReset,
 	}
 	c.clientMu.Unlock()
-	if clone.client == nil {
-		clone.client = &http.Client{}
+	if c.client != nil {
+		clone.client.Transport = c.client.Transport
+		clone.client.CheckRedirect = c.client.CheckRedirect
+		clone.client.Jar = c.client.Jar
+		clone.client.Timeout = c.client.Timeout
 	}
 	c.rateMu.Lock()
 	copy(clone.rateLimits[:], c.rateLimits[:])


Test patch:
diff --git a/github/github_test.go b/github/github_test.go
index 45ac57c0019..9a369a25fd4 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -2599,3 +2599,32 @@ func TestParseTokenExpiration(t *testing.T) {
 		}
 	}
 }
+
+func TestClientCopy_leak_transport(t *testing.T) {
+	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		accessToken := r.Header.Get("Authorization")
+		_, _ = fmt.Fprintf(w, `{"login": "%s"}`, accessToken)
+	}))
+	clientPreconfiguredWithURLs, err := NewClient(nil).WithEnterpriseURLs(srv.URL, srv.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	aliceClient := clientPreconfiguredWithURLs.WithAuthToken("alice")
+	bobClient := clientPreconfiguredWithURLs.WithAuthToken("bob")
+
+	alice, _, err := aliceClient.Users.Get(context.Background(), "")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assertNoDiff(t, "Bearer alice", alice.GetLogin())
+
+	bob, _, err := bobClient.Users.Get(context.Background(), "")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assertNoDiff(t, "Bearer bob", bob.GetLogin())
+}