From eaa661983d1cee08140f2fe9dd5eddc849b92699 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Thu, 20 Aug 2026 09:22:23 -0500 Subject: [PATCH 1/6] prevent git write operations --- internal/handlers/git_server.go | 50 +++++++++- internal/handlers/git_server_test.go | 137 ++++++++++++++++++++++++++- internal/handlers/github_api_test.go | 32 +++++++ 3 files changed, 214 insertions(+), 5 deletions(-) diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index d76bca4..754becc 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -2,8 +2,10 @@ package handlers import ( "bytes" + "encoding/json" "fmt" "io" + "mime" "net/http" "strings" "sync" @@ -11,6 +13,7 @@ import ( "github.com/elazarl/goproxy" "github.com/dependabot/proxy/internal/config" + "github.com/dependabot/proxy/internal/gitproto" "github.com/dependabot/proxy/internal/helpers" "github.com/dependabot/proxy/internal/logging" "github.com/dependabot/proxy/internal/proxyctx" @@ -271,6 +274,10 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } + if !isReadOnlyGitRequest(req) { + return req, nil + } + if _, pw, ok := req.BasicAuth(); ok && pw != "" { return req, nil } @@ -304,6 +311,44 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } +func isReadOnlyGitRequest(req *http.Request) bool { + if helpers.MethodPermitted(req, http.MethodGet, http.MethodHead) { + return req.URL.Query().Get("service") != "git-receive-pack" + } + if gitproto.IsUploadPackRequest(req) { + return true + } + return isLFSDownloadRequest(req) +} + +func isLFSDownloadRequest(req *http.Request) bool { + if req.Method != http.MethodPost { + return false + } + mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")) + if err != nil || mediaType != "application/vnd.git-lfs+json" { + return false + } + if !strings.HasSuffix(req.URL.Path, "/objects/batch") || req.Body == nil { + return false + } + + var body bytes.Buffer + originalBody := req.Body + var batch struct { + Operation string `json:"operation"` + } + err = json.NewDecoder(io.TeeReader(originalBody, &body)).Decode(&batch) + req.Body = struct { + io.Reader + io.Closer + }{ + Reader: io.MultiReader(&body, originalBody), + Closer: originalBody, + } + return err == nil && batch.Operation == "download" +} + // extracts the org and repo from the expected path type extractor func(path string) (org string, repo string, found bool) @@ -537,8 +582,5 @@ func (h *GitServerHandler) isGitHubAPIRequest(req *http.Request) bool { } func (h *GitServerHandler) isGitUploadPackPost(req *http.Request) bool { - if req.Method != "POST" { - return false - } - return strings.HasSuffix(req.URL.Path, "/git-upload-pack") + return gitproto.IsUploadPackRequest(req) } diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index 346cf86..3984d03 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -96,6 +96,10 @@ func TestGitServerHandler(t *testing.T) { gheCred.GetString("password"), "valid ghe request") + req = httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://ghe.some-corp.com/api/v3/repos/account/repo/issues", nil) + req = handleRequestAndClose(handler, req, nil) + assertUnauthenticated(t, req, "ghe api mutation") + // Special GHE dependabot-api endpoint req = httptest.NewRequestWithContext(t.Context(), "GET", "https://ghe.some-corp.com/_dependabot/update_jobs/123/details", nil) req = handleRequestAndClose(handler, req, nil) @@ -127,6 +131,135 @@ func TestGitServerHandler(t *testing.T) { "valid github request") } +func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "github_pat_fakefakefakesuperfake") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + + tests := []struct { + name string + method string + url string + contentType string + body string + authenticated bool + }{ + { + name: "get", + method: http.MethodGet, + url: "https://github.com/account/repo/info/refs?service=git-upload-pack", + authenticated: true, + }, + { + name: "head", + method: http.MethodHead, + url: "https://github.com/account/repo", + authenticated: true, + }, + { + name: "git upload pack", + method: http.MethodPost, + url: "https://github.com/account/repo/git-upload-pack", + contentType: "application/x-git-upload-pack-request", + body: "upload-pack request", + authenticated: true, + }, + { + name: "submodule checkout", + method: http.MethodPost, + url: "https://github.com/account/submodule.git/git-upload-pack", + contentType: "application/x-git-upload-pack-request", + body: "upload-pack request", + authenticated: true, + }, + { + name: "git upload pack without content type", + method: http.MethodPost, + url: "https://github.com/account/repo/git-upload-pack", + body: "upload-pack request", + }, + { + name: "git receive pack discovery", + method: http.MethodGet, + url: "https://github.com/account/repo/info/refs?service=git-receive-pack", + }, + { + name: "git receive pack", + method: http.MethodPost, + url: "https://github.com/account/repo/git-receive-pack", + contentType: "application/x-git-receive-pack-request", + body: "receive-pack request", + }, + { + name: "lfs download", + method: http.MethodPost, + url: "https://github.com/account/repo.git/info/lfs/objects/batch", + contentType: "application/vnd.git-lfs+json; charset=utf-8", + body: `{"operation":"download","objects":[{"oid":"abc","size":3}]}`, + authenticated: true, + }, + { + name: "custom lfs download endpoint", + method: http.MethodPost, + url: "https://github.com/custom-lfs/objects/batch", + contentType: "application/vnd.git-lfs+json", + body: `{"operation":"download","objects":[{"oid":"abc","size":3}]}`, + authenticated: true, + }, + { + name: "lfs upload", + method: http.MethodPost, + url: "https://github.com/account/repo.git/info/lfs/objects/batch", + contentType: "application/vnd.git-lfs+json", + body: `{"operation":"upload","objects":[{"oid":"abc","size":3}]}`, + }, + { + name: "lfs lock creation", + method: http.MethodPost, + url: "https://github.com/account/repo.git/info/lfs/locks", + contentType: "application/vnd.git-lfs+json", + body: `{"path":"file.bin"}`, + }, + { + name: "lfs unlock", + method: http.MethodPost, + url: "https://github.com/account/repo.git/info/lfs/locks/123/unlock", + contentType: "application/vnd.git-lfs+json", + body: `{}`, + }, + { + name: "arbitrary post", + method: http.MethodPost, + url: "https://github.com/account/repo/hooks", + contentType: "application/json", + body: `{}`, + }, + { + name: "delete", + method: http.MethodDelete, + url: "https://github.com/account/repo", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequestWithContext(t.Context(), tt.method, tt.url, strings.NewReader(tt.body)) + req.Header.Set("Content-Type", tt.contentType) + req = handleRequestAndClose(handler, req, nil) + + if tt.authenticated { + assertHasBasicAuth(t, req, credential.GetString("username"), credential.GetString("password"), "authenticated") + } else { + assertUnauthenticated(t, req, "unauthenticated") + } + + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.NoError(t, req.Body.Close()) + assert.Equal(t, tt.body, string(body)) + }) + } +} + func TestGitServerHandler_AuthenticatedAccessToGitHubRepos(t *testing.T) { installationToken1 := "v1.token1" privateRepo1Cred := testGitSourceCred("github.com", "x-access-token", installationToken1, withAccessibleRepos([]string{"github/private-repo-1"})) @@ -406,7 +539,7 @@ func TestGitServerHandler_TokenFallbackWithPost(t *testing.T) { "https://github.com/github/dependabot-action", 404, 404, - []string{userToken}, + []string{""}, }, } @@ -429,6 +562,7 @@ func TestGitServerHandler_TokenFallbackWithPost(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), "POST", tt.url, io.NopCloser(strings.NewReader("test body"))) require.NoError(t, err, "failed to create request") + req.Header.Set("Content-Type", "application/x-git-upload-pack-request") proxyCtx := &goproxy.ProxyCtx{Req: req, RoundTripper: roundTripper} rsp := &http.Response{StatusCode: tt.respCode, Body: io.NopCloser(strings.NewReader(""))} @@ -458,6 +592,7 @@ func TestGitServerHandler_NoCloneWithSingleCredPost(t *testing.T) { req, err := http.NewRequestWithContext(context.Background(), "POST", "https://github.com/github/dependabot-action/git-upload-pack", io.NopCloser(strings.NewReader("test body"))) require.NoError(t, err, "failed to create request") + req.Header.Set("Content-Type", "application/x-git-upload-pack-request") proxyCtx := &goproxy.ProxyCtx{Req: req} _ = handleRequestAndClose(handler, req, proxyCtx) buffer, found := proxyctx.GetBuffer(proxyCtx, reqBodyCtxKey) diff --git a/internal/handlers/github_api_test.go b/internal/handlers/github_api_test.go index 7541f87..6a0c780 100644 --- a/internal/handlers/github_api_test.go +++ b/internal/handlers/github_api_test.go @@ -103,6 +103,38 @@ func TestGitHubAPIHandler(t *testing.T) { } } +func TestGitHubAPIHandler_OnlyAuthenticatesReadRequests(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "github_pat_fakefakefakesuperfake") + handler := NewGitHubAPIHandler(config.Credentials{credential}) + + tests := []struct { + method string + url string + authenticated bool + }{ + {method: http.MethodGet, url: "https://api.github.com/repos/account/repo", authenticated: true}, + {method: http.MethodHead, url: "https://api.github.com/repos/account/repo", authenticated: true}, + {method: http.MethodPost, url: "https://api.github.com/graphql"}, + {method: http.MethodPost, url: "https://api.github.com/repos/account/repo/issues"}, + {method: http.MethodPut, url: "https://api.github.com/repos/account/repo/contents/file"}, + {method: http.MethodPatch, url: "https://api.github.com/repos/account/repo"}, + {method: http.MethodDelete, url: "https://api.github.com/repos/account/repo"}, + } + + for _, tt := range tests { + t.Run(tt.method+" "+tt.url, func(t *testing.T) { + req := httptest.NewRequestWithContext(t.Context(), tt.method, tt.url, nil) + req = handleRequestAndClose(handler, req, nil) + + if tt.authenticated { + assertHasTokenAuth(t, req, "token", credential.GetString("password"), "authenticated") + } else { + assertUnauthenticated(t, req, "unauthenticated") + } + }) + } +} + func TestGitHubAPIHandler_AuthenticatedAccessToGitHubRepos(t *testing.T) { installationToken1 := "v1.token1" privateRepo1Cred := testGitSourceCred("github.com", "x-access-token", installationToken1, withAccessibleRepos([]string{"github/private-repo-1"})) From d66cd7cfbf797b50881fc654bf85a0295769b6f9 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Thu, 20 Aug 2026 10:33:09 -0500 Subject: [PATCH 2/6] leave previous functionality alone --- internal/handlers/git_server.go | 11 +++-- internal/handlers/git_server_test.go | 65 +++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index 754becc..913cc37 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -36,6 +36,8 @@ type jitAccessConfig struct { password string } +const blockedGitRequestMessage = "Dependabot proxy blocked authentication for a non-read-only Git request\n" + type gitCredentialsMap struct { sync.RWMutex // data is a nested map structure to store credentials. @@ -274,10 +276,6 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } - if !isReadOnlyGitRequest(req) { - return req, nil - } - if _, pw, ok := req.BasicAuth(); ok && pw != "" { return req, nil } @@ -287,6 +285,11 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } + if !isReadOnlyGitRequest(req) { + logging.RequestLogf(proxyCtx, "* blocked authentication for non-read-only git request (method: %s, host: %s, path: %s)", req.Method, helpers.GetHost(req), req.URL.Path) + return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, blockedGitRequestMessage) + } + logging.RequestLogf(proxyCtx, "* authenticating git server request (host: %s)", helpers.GetHost(req)) credsToUse := creds[0] helpers.SetBasicAuthorization(req, credsToUse.username, credsToUse.password) diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index 3984d03..a847a67 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -142,6 +142,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { contentType string body string authenticated bool + blocked bool }{ { name: "get", @@ -172,15 +173,17 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { authenticated: true, }, { - name: "git upload pack without content type", - method: http.MethodPost, - url: "https://github.com/account/repo/git-upload-pack", - body: "upload-pack request", + name: "git upload pack without content type", + method: http.MethodPost, + url: "https://github.com/account/repo/git-upload-pack", + body: "upload-pack request", + blocked: true, }, { - name: "git receive pack discovery", - method: http.MethodGet, - url: "https://github.com/account/repo/info/refs?service=git-receive-pack", + name: "git receive pack discovery", + method: http.MethodGet, + url: "https://github.com/account/repo/info/refs?service=git-receive-pack", + blocked: true, }, { name: "git receive pack", @@ -188,6 +191,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { url: "https://github.com/account/repo/git-receive-pack", contentType: "application/x-git-receive-pack-request", body: "receive-pack request", + blocked: true, }, { name: "lfs download", @@ -211,6 +215,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { url: "https://github.com/account/repo.git/info/lfs/objects/batch", contentType: "application/vnd.git-lfs+json", body: `{"operation":"upload","objects":[{"oid":"abc","size":3}]}`, + blocked: true, }, { name: "lfs lock creation", @@ -218,6 +223,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { url: "https://github.com/account/repo.git/info/lfs/locks", contentType: "application/vnd.git-lfs+json", body: `{"path":"file.bin"}`, + blocked: true, }, { name: "lfs unlock", @@ -225,6 +231,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { url: "https://github.com/account/repo.git/info/lfs/locks/123/unlock", contentType: "application/vnd.git-lfs+json", body: `{}`, + blocked: true, }, { name: "arbitrary post", @@ -232,11 +239,13 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { url: "https://github.com/account/repo/hooks", contentType: "application/json", body: `{}`, + blocked: true, }, { - name: "delete", - method: http.MethodDelete, - url: "https://github.com/account/repo", + name: "delete", + method: http.MethodDelete, + url: "https://github.com/account/repo", + blocked: true, }, } @@ -244,13 +253,24 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequestWithContext(t.Context(), tt.method, tt.url, strings.NewReader(tt.body)) req.Header.Set("Content-Type", tt.contentType) - req = handleRequestAndClose(handler, req, nil) + req, resp := handler.HandleRequest(req, nil) if tt.authenticated { assertHasBasicAuth(t, req, credential.GetString("username"), credential.GetString("password"), "authenticated") } else { assertUnauthenticated(t, req, "unauthenticated") } + if tt.blocked { + require.NotNil(t, resp) + assert.Equal(t, http.StatusForbidden, resp.StatusCode) + assert.Equal(t, goproxy.ContentTypeText, resp.Header.Get("Content-Type")) + responseBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + assert.Equal(t, "Dependabot proxy blocked authentication for a non-read-only Git request\n", string(responseBody)) + } else { + assert.Nil(t, resp) + } body, err := io.ReadAll(req.Body) require.NoError(t, err) @@ -260,6 +280,29 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { } } +func TestGitServerHandler_DoesNotBlockIndependentlyAuthenticatedRequests(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://github.com/account/repo/git-receive-pack", nil) + req.SetBasicAuth("caller", "caller-token") + + req, resp := handler.HandleRequest(req, nil) + + assert.Nil(t, resp) + assertHasBasicAuth(t, req, "caller", "caller-token", "caller authentication") +} + +func TestGitServerHandler_DoesNotBlockRequestsWithoutMatchingCredentials(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://gitlab.com/account/repo/git-receive-pack", nil) + + req, resp := handler.HandleRequest(req, nil) + + assert.Nil(t, resp) + assertUnauthenticated(t, req, "unmatched host") +} + func TestGitServerHandler_AuthenticatedAccessToGitHubRepos(t *testing.T) { installationToken1 := "v1.token1" privateRepo1Cred := testGitSourceCred("github.com", "x-access-token", installationToken1, withAccessibleRepos([]string{"github/private-repo-1"})) From e614fa6d4dc56c5465bf33b4980febc824b1ff62 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Fri, 21 Aug 2026 08:23:26 -0500 Subject: [PATCH 3/6] when a write request is blocked, don't retry with credentials --- internal/handlers/git_server.go | 8 ++++++++ internal/handlers/git_server_test.go | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index 913cc37..ed5dd06 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -219,6 +219,7 @@ type gitCredentials struct { const ( addedAuthCtxKey = "git-server.added-auth" + blockedAuthCtxKey = "git-server.blocked-auth" reqBodyCtxKey = "git-server.req-body" allReposScopeIdentifier = "" ) @@ -287,6 +288,9 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr if !isReadOnlyGitRequest(req) { logging.RequestLogf(proxyCtx, "* blocked authentication for non-read-only git request (method: %s, host: %s, path: %s)", req.Method, helpers.GetHost(req), req.URL.Path) + if proxyCtx != nil { + proxyctx.SetValue(proxyCtx, blockedAuthCtxKey, true) + } return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, blockedGitRequestMessage) } @@ -421,6 +425,10 @@ func (h *GitServerHandler) HandleResponse(rsp *http.Response, proxyCtx *goproxy. return rsp } + if blocked, ok := proxyctx.GetBool(proxyCtx, blockedAuthCtxKey); ok && blocked { + return rsp + } + // Make sure we treat GHES requests like GitHub API requests. Do not retry if h.isGitHubAPIRequest(proxyCtx.Req) { return rsp diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index a847a67..84c4463 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -292,6 +292,29 @@ func TestGitServerHandler_DoesNotBlockIndependentlyAuthenticatedRequests(t *test assertHasBasicAuth(t, req, "caller", "caller-token", "caller authentication") } +func TestGitServerHandler_DoesNotRetryBlockedRequests(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://github.com/account/repo/info/refs?service=git-receive-pack", nil) + roundTrips := 0 + roundTripper := goproxy.RoundTripperFunc(func(*http.Request, *goproxy.ProxyCtx) (*http.Response, error) { + roundTrips++ + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))}, nil + }) + proxyCtx := &goproxy.ProxyCtx{Req: req, RoundTripper: roundTripper} + + req, blockedResponse := handler.HandleRequest(req, proxyCtx) + proxyCtx.Req = req + response := handler.HandleResponse(blockedResponse, proxyCtx) + defer func() { + require.NoError(t, response.Body.Close()) + }() + + assert.Same(t, blockedResponse, response) + assert.Equal(t, http.StatusForbidden, response.StatusCode) + assert.Zero(t, roundTrips) +} + func TestGitServerHandler_DoesNotBlockRequestsWithoutMatchingCredentials(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") handler := NewGitServerHandler(config.Credentials{credential}, nil) From c1833de535fefbd49dad379d54c8f16015f0e57e Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Fri, 21 Aug 2026 09:02:30 -0500 Subject: [PATCH 4/6] fix feedback issues --- internal/handlers/git_server.go | 111 +++++++++++++++++++++++---- internal/handlers/git_server_test.go | 74 ++++++++++++++++++ 2 files changed, 168 insertions(+), 17 deletions(-) diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index ed5dd06..be02503 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -218,10 +218,10 @@ type gitCredentials struct { } const ( - addedAuthCtxKey = "git-server.added-auth" - blockedAuthCtxKey = "git-server.blocked-auth" - reqBodyCtxKey = "git-server.req-body" - allReposScopeIdentifier = "" + addedAuthCtxKey = "git-server.added-auth" + nonReadOnlyRequestCtxKey = "git-server.non-read-only-request" + reqBodyCtxKey = "git-server.req-body" + allReposScopeIdentifier = "" ) type ScopeRequester interface { @@ -277,20 +277,22 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } - if _, pw, ok := req.BasicAuth(); ok && pw != "" { + creds := getCredentialsForRequest(req, h.credentials, gitExtractOrgAndRepo) + if len(creds) == 0 { return req, nil } - creds := getCredentialsForRequest(req, h.credentials, gitExtractOrgAndRepo) - if len(creds) == 0 { + readOnly := isReadOnlyGitRequest(req) + if !readOnly && proxyCtx != nil { + proxyctx.SetValue(proxyCtx, nonReadOnlyRequestCtxKey, true) + } + + if _, pw, ok := req.BasicAuth(); ok && pw != "" { return req, nil } - if !isReadOnlyGitRequest(req) { + if !readOnly { logging.RequestLogf(proxyCtx, "* blocked authentication for non-read-only git request (method: %s, host: %s, path: %s)", req.Method, helpers.GetHost(req), req.URL.Path) - if proxyCtx != nil { - proxyctx.SetValue(proxyCtx, blockedAuthCtxKey, true) - } return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, blockedGitRequestMessage) } @@ -342,10 +344,7 @@ func isLFSDownloadRequest(req *http.Request) bool { var body bytes.Buffer originalBody := req.Body - var batch struct { - Operation string `json:"operation"` - } - err = json.NewDecoder(io.TeeReader(originalBody, &body)).Decode(&batch) + isDownload := isLFSDownloadBatch(io.TeeReader(originalBody, &body)) req.Body = struct { io.Reader io.Closer @@ -353,7 +352,85 @@ func isLFSDownloadRequest(req *http.Request) bool { Reader: io.MultiReader(&body, originalBody), Closer: originalBody, } - return err == nil && batch.Operation == "download" + return isDownload +} + +func isLFSDownloadBatch(reader io.Reader) bool { + decoder := json.NewDecoder(reader) + token, err := decoder.Token() + if err != nil || token != json.Delim('{') { + return false + } + + var operation string + operationSeen := false + for decoder.More() { + token, err = decoder.Token() + key, ok := token.(string) + if err != nil || !ok { + return false + } + + if strings.EqualFold(key, "operation") { + if key != "operation" || operationSeen { + return false + } + token, err = decoder.Token() + operation, ok = token.(string) + if err != nil || !ok { + return false + } + operationSeen = true + continue + } + + if !skipJSONValue(decoder) { + return false + } + } + + token, err = decoder.Token() + if err != nil || token != json.Delim('}') { + return false + } + if _, err = decoder.Token(); err != io.EOF { + return false + } + return operationSeen && operation == "download" +} + +func skipJSONValue(decoder *json.Decoder) bool { + token, err := decoder.Token() + if err != nil { + return false + } + + delim, ok := token.(json.Delim) + if !ok { + return true + } + + switch delim { + case '{': + for decoder.More() { + token, err = decoder.Token() + if _, ok = token.(string); err != nil || !ok || !skipJSONValue(decoder) { + return false + } + } + token, err = decoder.Token() + return err == nil && token == json.Delim('}') + case '[': + for decoder.More() { + if !skipJSONValue(decoder) { + return false + } + } + token, err = decoder.Token() + return err == nil && token == json.Delim(']') + default: + return false + } } // extracts the org and repo from the expected path @@ -425,7 +502,7 @@ func (h *GitServerHandler) HandleResponse(rsp *http.Response, proxyCtx *goproxy. return rsp } - if blocked, ok := proxyctx.GetBool(proxyCtx, blockedAuthCtxKey); ok && blocked { + if nonReadOnly, ok := proxyctx.GetBool(proxyCtx, nonReadOnlyRequestCtxKey); ok && nonReadOnly { return rsp } diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index 84c4463..101c45d 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -280,6 +280,51 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { } } +func TestGitServerHandler_RejectsAmbiguousLFSOperations(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + tests := []struct { + name string + body string + }{ + { + name: "case variant", + body: `{"Operation":"download","objects":[{"oid":"abc","size":3}]}`, + }, + { + name: "upload followed by case variant download", + body: `{"operation":"upload","Operation":"download","objects":[{"oid":"abc","size":3}]}`, + }, + { + name: "duplicate operation", + body: `{"operation":"upload","operation":"download","objects":[{"oid":"abc","size":3}]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequestWithContext( + t.Context(), + http.MethodPost, + "https://github.com/account/repo.git/info/lfs/objects/batch", + strings.NewReader(tt.body), + ) + req.Header.Set("Content-Type", "application/vnd.git-lfs+json") + + req, response := handler.HandleRequest(req, nil) + + assertUnauthenticated(t, req, "ambiguous LFS operation") + require.NotNil(t, response) + assert.Equal(t, http.StatusForbidden, response.StatusCode) + require.NoError(t, response.Body.Close()) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.NoError(t, req.Body.Close()) + assert.Equal(t, tt.body, string(body)) + }) + } +} + func TestGitServerHandler_DoesNotBlockIndependentlyAuthenticatedRequests(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") handler := NewGitServerHandler(config.Credentials{credential}, nil) @@ -315,6 +360,35 @@ func TestGitServerHandler_DoesNotRetryBlockedRequests(t *testing.T) { assert.Zero(t, roundTrips) } +func TestGitServerHandler_DoesNotRetryNonReadOnlyRequestsWithCallerAuth(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") + handler := NewGitServerHandler(config.Credentials{credential}, nil) + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://github.com/account/repo/info/refs?service=git-receive-pack", nil) + req.SetBasicAuth("caller", "caller-token") + roundTrips := 0 + roundTripper := goproxy.RoundTripperFunc(func(*http.Request, *goproxy.ProxyCtx) (*http.Response, error) { + roundTrips++ + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))}, nil + }) + proxyCtx := &goproxy.ProxyCtx{Req: req, RoundTripper: roundTripper} + + req, blockedResponse := handler.HandleRequest(req, proxyCtx) + require.Nil(t, blockedResponse) + proxyCtx.Req = req + upstreamResponse := &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: io.NopCloser(strings.NewReader("unauthorized")), + } + response := handler.HandleResponse(upstreamResponse, proxyCtx) + defer func() { + require.NoError(t, response.Body.Close()) + }() + + assert.Same(t, upstreamResponse, response) + assert.Equal(t, http.StatusUnauthorized, response.StatusCode) + assert.Zero(t, roundTrips) +} + func TestGitServerHandler_DoesNotBlockRequestsWithoutMatchingCredentials(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") handler := NewGitServerHandler(config.Credentials{credential}, nil) From 2e39b4aa1a57ce076f9d3e650ae45d20cba57a87 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Mon, 24 Aug 2026 09:02:57 -0500 Subject: [PATCH 5/6] gate read-only git credentials behind experiment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d67c233c-df77-4f0b-8665-d41da121ddb6 --- internal/config/config.go | 10 +++++ internal/handlers/git_server.go | 61 ++++++++++++++++++++-------- internal/handlers/git_server_test.go | 20 +++++++++ proxy.go | 8 +++- 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 0423970..8b92ba4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,16 @@ type Config struct { Credentials Credentials `json:"all_credentials"` CA CaDetails `json:"ca"` ProxyAuth BasicAuthCredentials `json:"proxy_auth"` + Experiments Experiments `json:"experiments"` +} + +// Experiments contains job experiments passed to the proxy. +type Experiments map[string]any + +// Enabled reports whether an experiment is explicitly enabled. +func (e Experiments) Enabled(name string) bool { + enabled, ok := e[name].(bool) + return ok && enabled } // Credential is a wrapper around map[string]any, which is the format diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index be02503..5a9c8b2 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -23,13 +23,19 @@ import ( // GitServerHandler handles requests destined remote git servers such as // github.com or private git servers type GitServerHandler struct { - credentials *gitCredentialsMap - jitAccessByHost map[string]jitAccessConfig - client ScopeRequester + credentials *gitCredentialsMap + jitAccessByHost map[string]jitAccessConfig + client ScopeRequester + readOnlyGitCredentials bool reposAlreadyTried *threadsafe.Map[string, struct{}] } +// GitServerHandlerOptions controls optional Git credential restrictions. +type GitServerHandlerOptions struct { + ReadOnlyGitCredentials bool +} + type jitAccessConfig struct { endpoint string username string @@ -231,11 +237,23 @@ type ScopeRequester interface { // NewGitServerHandler returns a new GitServerHandler, adding basic auth to // requests to hosts for which we have credentials func NewGitServerHandler(creds config.Credentials, client ScopeRequester) *GitServerHandler { + return NewGitServerHandlerWithOptions(creds, client, GitServerHandlerOptions{ + ReadOnlyGitCredentials: true, + }) +} + +// NewGitServerHandlerWithOptions returns a configured Git server handler. +func NewGitServerHandlerWithOptions( + creds config.Credentials, + client ScopeRequester, + options GitServerHandlerOptions, +) *GitServerHandler { handler := GitServerHandler{ - credentials: newGitCredentialsMap(), - jitAccessByHost: map[string]jitAccessConfig{}, - client: client, - reposAlreadyTried: threadsafe.NewMap[string, struct{}](), + credentials: newGitCredentialsMap(), + jitAccessByHost: map[string]jitAccessConfig{}, + client: client, + readOnlyGitCredentials: options.ReadOnlyGitCredentials, + reposAlreadyTried: threadsafe.NewMap[string, struct{}](), } for _, cred := range creds { @@ -282,18 +300,22 @@ func (h *GitServerHandler) HandleRequest(req *http.Request, proxyCtx *goproxy.Pr return req, nil } - readOnly := isReadOnlyGitRequest(req) - if !readOnly && proxyCtx != nil { - proxyctx.SetValue(proxyCtx, nonReadOnlyRequestCtxKey, true) - } + if h.readOnlyGitCredentials { + readOnly := isReadOnlyGitRequest(req) + if !readOnly && proxyCtx != nil { + proxyctx.SetValue(proxyCtx, nonReadOnlyRequestCtxKey, true) + } - if _, pw, ok := req.BasicAuth(); ok && pw != "" { - return req, nil - } + if _, pw, ok := req.BasicAuth(); ok && pw != "" { + return req, nil + } - if !readOnly { - logging.RequestLogf(proxyCtx, "* blocked authentication for non-read-only git request (method: %s, host: %s, path: %s)", req.Method, helpers.GetHost(req), req.URL.Path) - return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, blockedGitRequestMessage) + if !readOnly { + logging.RequestLogf(proxyCtx, "* blocked authentication for non-read-only git request (method: %s, host: %s, path: %s)", req.Method, helpers.GetHost(req), req.URL.Path) + return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, blockedGitRequestMessage) + } + } else if _, pw, ok := req.BasicAuth(); ok && pw != "" { + return req, nil } logging.RequestLogf(proxyCtx, "* authenticating git server request (host: %s)", helpers.GetHost(req)) @@ -670,5 +692,8 @@ func (h *GitServerHandler) isGitHubAPIRequest(req *http.Request) bool { } func (h *GitServerHandler) isGitUploadPackPost(req *http.Request) bool { - return gitproto.IsUploadPackRequest(req) + if req.Method != http.MethodPost { + return false + } + return strings.HasSuffix(req.URL.Path, "/git-upload-pack") } diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index 101c45d..61a0ea6 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -280,6 +280,26 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { } } +func TestGitServerHandler_AuthenticatesWriteRequestsWhenReadOnlyCredentialsAreDisabled(t *testing.T) { + credential := testGitSourceCred("github.com", "x-access-token", "secret") + handler := NewGitServerHandlerWithOptions( + config.Credentials{credential}, + nil, + GitServerHandlerOptions{ReadOnlyGitCredentials: false}, + ) + request := httptest.NewRequestWithContext( + t.Context(), + http.MethodPost, + "https://github.com/dependabot/proxy/git-receive-pack", + strings.NewReader("write request"), + ) + + request, response := handler.HandleRequest(request, nil) + + require.Nil(t, response) + assertHasBasicAuth(t, request, "x-access-token", "secret", "write request") +} + func TestGitServerHandler_RejectsAmbiguousLFSOperations(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") handler := NewGitServerHandler(config.Credentials{credential}, nil) diff --git a/proxy.go b/proxy.go index 40bb2ea..0c10359 100644 --- a/proxy.go +++ b/proxy.go @@ -94,7 +94,13 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi azureDevOpsAPIHandler := handlers.NewAzureDevOpsAPIHandler(cfg.Credentials) proxy.OnRequest().DoFunc(azureDevOpsAPIHandler.HandleRequest) - gitServerHandler := handlers.NewGitServerHandler(cfg.Credentials, apiClient) + gitServerHandler := handlers.NewGitServerHandlerWithOptions( + cfg.Credentials, + apiClient, + handlers.GitServerHandlerOptions{ + ReadOnlyGitCredentials: cfg.Experiments.Enabled("proxy_read_only_git_credentials"), + }, + ) proxy.OnRequest().DoFunc(gitServerHandler.HandleRequest) proxy.OnResponse().DoFunc(gitServerHandler.HandleResponse) From 96f90e43fb5a59eda3c6cda9aa44da18a7d0ac7a Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Mon, 24 Aug 2026 09:18:29 -0500 Subject: [PATCH 6/6] simplify --- internal/handlers/git_server.go | 18 ++---------- internal/handlers/git_server_test.go | 42 +++++++++++++--------------- proxy.go | 6 ++-- 3 files changed, 24 insertions(+), 42 deletions(-) diff --git a/internal/handlers/git_server.go b/internal/handlers/git_server.go index 5a9c8b2..98f171c 100644 --- a/internal/handlers/git_server.go +++ b/internal/handlers/git_server.go @@ -31,11 +31,6 @@ type GitServerHandler struct { reposAlreadyTried *threadsafe.Map[string, struct{}] } -// GitServerHandlerOptions controls optional Git credential restrictions. -type GitServerHandlerOptions struct { - ReadOnlyGitCredentials bool -} - type jitAccessConfig struct { endpoint string username string @@ -236,23 +231,16 @@ type ScopeRequester interface { // NewGitServerHandler returns a new GitServerHandler, adding basic auth to // requests to hosts for which we have credentials -func NewGitServerHandler(creds config.Credentials, client ScopeRequester) *GitServerHandler { - return NewGitServerHandlerWithOptions(creds, client, GitServerHandlerOptions{ - ReadOnlyGitCredentials: true, - }) -} - -// NewGitServerHandlerWithOptions returns a configured Git server handler. -func NewGitServerHandlerWithOptions( +func NewGitServerHandler( creds config.Credentials, client ScopeRequester, - options GitServerHandlerOptions, + readOnlyGitCredentials bool, ) *GitServerHandler { handler := GitServerHandler{ credentials: newGitCredentialsMap(), jitAccessByHost: map[string]jitAccessConfig{}, client: client, - readOnlyGitCredentials: options.ReadOnlyGitCredentials, + readOnlyGitCredentials: readOnlyGitCredentials, reposAlreadyTried: threadsafe.NewMap[string, struct{}](), } diff --git a/internal/handlers/git_server_test.go b/internal/handlers/git_server_test.go index 61a0ea6..ee8b60d 100644 --- a/internal/handlers/git_server_test.go +++ b/internal/handlers/git_server_test.go @@ -29,7 +29,7 @@ func TestGitServerHandler_url(t *testing.T) { "password": "token", } - handler := NewGitServerHandler(config.Credentials{cred}, nil) + handler := NewGitServerHandler(config.Credentials{cred}, nil, true) // Valid github git request, prioritises non-installation token req := httptest.NewRequestWithContext(t.Context(), "GET", "https://github.com/account/repo", nil) @@ -62,7 +62,7 @@ func TestGitServerHandler(t *testing.T) { rubygemsCred, proximaCred, } - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) // Valid github git request, prioritises non-installation token req := httptest.NewRequestWithContext(t.Context(), "GET", "https://github.com/account/repo", nil) @@ -120,7 +120,7 @@ func TestGitServerHandler(t *testing.T) { bitBucketCred, rubygemsCred, } - handler = NewGitServerHandler(credentials, nil) + handler = NewGitServerHandler(credentials, nil, true) // Valid github git request, uses installation token req = httptest.NewRequestWithContext(t.Context(), "GET", "https://github.com/account/repo", nil) @@ -133,7 +133,7 @@ func TestGitServerHandler(t *testing.T) { func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "github_pat_fakefakefakesuperfake") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) tests := []struct { name string @@ -282,11 +282,7 @@ func TestGitServerHandler_OnlyAuthenticatesReadRequests(t *testing.T) { func TestGitServerHandler_AuthenticatesWriteRequestsWhenReadOnlyCredentialsAreDisabled(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "secret") - handler := NewGitServerHandlerWithOptions( - config.Credentials{credential}, - nil, - GitServerHandlerOptions{ReadOnlyGitCredentials: false}, - ) + handler := NewGitServerHandler(config.Credentials{credential}, nil, false) request := httptest.NewRequestWithContext( t.Context(), http.MethodPost, @@ -302,7 +298,7 @@ func TestGitServerHandler_AuthenticatesWriteRequestsWhenReadOnlyCredentialsAreDi func TestGitServerHandler_RejectsAmbiguousLFSOperations(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) tests := []struct { name string body string @@ -347,7 +343,7 @@ func TestGitServerHandler_RejectsAmbiguousLFSOperations(t *testing.T) { func TestGitServerHandler_DoesNotBlockIndependentlyAuthenticatedRequests(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://github.com/account/repo/git-receive-pack", nil) req.SetBasicAuth("caller", "caller-token") @@ -359,7 +355,7 @@ func TestGitServerHandler_DoesNotBlockIndependentlyAuthenticatedRequests(t *test func TestGitServerHandler_DoesNotRetryBlockedRequests(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://github.com/account/repo/info/refs?service=git-receive-pack", nil) roundTrips := 0 roundTripper := goproxy.RoundTripperFunc(func(*http.Request, *goproxy.ProxyCtx) (*http.Response, error) { @@ -382,7 +378,7 @@ func TestGitServerHandler_DoesNotRetryBlockedRequests(t *testing.T) { func TestGitServerHandler_DoesNotRetryNonReadOnlyRequestsWithCallerAuth(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://github.com/account/repo/info/refs?service=git-receive-pack", nil) req.SetBasicAuth("caller", "caller-token") roundTrips := 0 @@ -411,7 +407,7 @@ func TestGitServerHandler_DoesNotRetryNonReadOnlyRequestsWithCallerAuth(t *testi func TestGitServerHandler_DoesNotBlockRequestsWithoutMatchingCredentials(t *testing.T) { credential := testGitSourceCred("github.com", "x-access-token", "proxy-token") - handler := NewGitServerHandler(config.Credentials{credential}, nil) + handler := NewGitServerHandler(config.Credentials{credential}, nil, true) req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://gitlab.com/account/repo/git-receive-pack", nil) req, resp := handler.HandleRequest(req, nil) @@ -470,7 +466,7 @@ func TestGitServerHandler_AuthenticatedAccessToGitHubRepos(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handler := NewGitServerHandler(tt.credentials, nil) + handler := NewGitServerHandler(tt.credentials, nil, true) // Valid github git request, prioritises non-installation token req := httptest.NewRequestWithContext(t.Context(), "GET", fmt.Sprintf("https://github.com/%s", tt.repoNWO), nil) @@ -494,7 +490,7 @@ func TestGitServerHandler_AuthenticatedAccessToGitHubRepos(t *testing.T) { func TestGitServerHandler404Retry(t *testing.T) { installationCred := testGitSourceCred("github.com", "x-access-token", "v1.token") credentials := config.Credentials{installationCred} - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) rsp := &http.Response{StatusCode: 404, Body: io.NopCloser(strings.NewReader(""))} url, err := url.Parse("https://example.com") require.NoError(t, err) @@ -526,7 +522,7 @@ func TestGitServerHandler404Retry(t *testing.T) { func TestGitServerHandlerNoRetry(t *testing.T) { installationCred := testGitSourceCred("ghes.com", "x-access-token", "v1.token") credentials := config.Credentials{installationCred} - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) rsp := &http.Response{StatusCode: 404} url := "https://ghes.com/api/v3" @@ -617,7 +613,7 @@ func TestGitServerHandler_TokenFallback(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) var capturedTokens []string roundTripper := goproxy.RoundTripperFunc(func(r *http.Request, proxyCtx *goproxy.ProxyCtx) (*http.Response, error) { @@ -665,7 +661,7 @@ func TestGitServerHandler_TokenFallbackWithPost(t *testing.T) { testGitSourceCred("github.com", "x-access-token", installationToken), testGitSourceCred("github.com", "x-access-token", userToken), } - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) tests := []struct { name string @@ -748,7 +744,7 @@ func TestGitServerHandler_NoCloneWithSingleCredPost(t *testing.T) { credentials := config.Credentials{ testGitSourceCred("github.com", "x-access-token", installationToken), } - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) req, err := http.NewRequestWithContext(context.Background(), "POST", "https://github.com/github/dependabot-action/git-upload-pack", io.NopCloser(strings.NewReader("test body"))) require.NoError(t, err, "failed to create request") @@ -777,7 +773,7 @@ func TestGitServerHandler_RepositoryScopedCredentials(t *testing.T) { otherGitHubCred, bitBucketCred, } - handler := NewGitServerHandler(credentials, nil) + handler := NewGitServerHandler(credentials, nil, true) tests := map[string]string{ "valid github git request": "https://github.com/account1/repo1", @@ -844,7 +840,7 @@ func TestGitServerHandler_RequestJITAccess(t *testing.T) { credentials := config.Credentials{jitCred} testClient := &TestScopeRequester{} - handler := NewGitServerHandler(credentials, testClient) + handler := NewGitServerHandler(credentials, testClient, true) rsp := &http.Response{StatusCode: 404, Body: io.NopCloser(strings.NewReader(""))} url, err := url.Parse(test.url) require.NoError(t, err) @@ -946,7 +942,7 @@ func TestJITEndpointUsesExplicitAuthWhenProvided(t *testing.T) { }) apiClient := apiclient.New("", "job-token-is-wrong-token", "") // other token given for the API client - handler := NewGitServerHandler(creds, apiClient) + handler := NewGitServerHandler(creds, apiClient, true) // this is the actual network request req, err := http.NewRequestWithContext(context.Background(), "GET", "https://github.com/account/repo/info/refs?service=git-upload-pack", nil) diff --git a/proxy.go b/proxy.go index 0c10359..3d871b3 100644 --- a/proxy.go +++ b/proxy.go @@ -94,12 +94,10 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi azureDevOpsAPIHandler := handlers.NewAzureDevOpsAPIHandler(cfg.Credentials) proxy.OnRequest().DoFunc(azureDevOpsAPIHandler.HandleRequest) - gitServerHandler := handlers.NewGitServerHandlerWithOptions( + gitServerHandler := handlers.NewGitServerHandler( cfg.Credentials, apiClient, - handlers.GitServerHandlerOptions{ - ReadOnlyGitCredentials: cfg.Experiments.Enabled("proxy_read_only_git_credentials"), - }, + cfg.Experiments.Enabled("proxy_read_only_git_credentials"), ) proxy.OnRequest().DoFunc(gitServerHandler.HandleRequest) proxy.OnResponse().DoFunc(gitServerHandler.HandleResponse)