From 8639660dd97c12ad4b640a3f238f77691a44dbaa Mon Sep 17 00:00:00 2001 From: chrisbelford01 <216903392+chrisbelford01@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:32:40 +0000 Subject: [PATCH 1/2] fix(gitea): honor shared HTTP settings in enum and secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credit chrisbelford01 for the HTTP-client wiring fixes published in chrisbelford01/pipeleek. The source commits recorded a placeholder author identity (standard ); this port uses the repository owner’s GitHub identity with maintainer approval. Adapt the fixes to current code, add regression and CLI coverage, and document shared settings. Preserve emojis, existing features, and defaults. Original source commits: https://github.com/chrisbelford01/pipeleek/commit/2ea62630c321e9362088a8d20fd76c33df36eb65 https://github.com/chrisbelford01/pipeleek/commit/41b72b0010c8f054de89aabb9954c026f931dbe9 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/introduction/proxying.md | 2 + pkg/gitea/enum/enum.go | 4 +- pkg/gitea/http_settings_test.go | 117 ++++++++++++++++++++++++ pkg/gitea/secrets/secrets.go | 4 +- tests/e2e/gitea/enum/enum_test.go | 31 ++++--- tests/e2e/gitea/secrets/secrets_test.go | 44 ++++----- 6 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 pkg/gitea/http_settings_test.go diff --git a/docs/introduction/proxying.md b/docs/introduction/proxying.md index 9dba8032..30348254 100644 --- a/docs/introduction/proxying.md +++ b/docs/introduction/proxying.md @@ -36,6 +36,8 @@ HTTP_PROXY=socks5://127.0.0.1:1080 pipeleek gl scan -u https://gitlab.internal.c Alternatively, use the `--proxy` flag to set any proxy from the command line without relying on `HTTP_PROXY`. It accepts both HTTP and SOCKS5 URLs and takes precedence over the environment variable: +Gitea `enum` and `secrets` commands use these shared HTTP settings too, including `--ignore-proxy`, `--tls-verification`, and `--http-timeout`. + ```bash # HTTP proxy pipeleek --proxy http://127.0.0.1:8080 gl scan -u https://gitlab.com -t glpat-xxxxx diff --git a/pkg/gitea/enum/enum.go b/pkg/gitea/enum/enum.go index 363e50dc..ae34b22e 100644 --- a/pkg/gitea/enum/enum.go +++ b/pkg/gitea/enum/enum.go @@ -4,12 +4,14 @@ import ( "fmt" "code.gitea.io/sdk/gitea" + "github.com/CompassSecurity/pipeleek/pkg/httpclient" "github.com/rs/zerolog/log" ) // RunEnum performs the enumeration of Gitea access rights. func RunEnum(giteaURL, apiToken string) error { - client, err := gitea.NewClient(giteaURL, gitea.SetToken(apiToken)) + client, err := gitea.NewClient(giteaURL, gitea.SetToken(apiToken), + gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient())) if err != nil { return err } diff --git a/pkg/gitea/http_settings_test.go b/pkg/gitea/http_settings_test.go new file mode 100644 index 00000000..008af280 --- /dev/null +++ b/pkg/gitea/http_settings_test.go @@ -0,0 +1,117 @@ +package gitea_test + +import ( + "errors" + "net" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + "time" + + giteaenum "github.com/CompassSecurity/pipeleek/pkg/gitea/enum" + "github.com/CompassSecurity/pipeleek/pkg/gitea/secrets" + "github.com/CompassSecurity/pipeleek/pkg/httpclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func serveGitea(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/v1/version": + _, _ = w.Write([]byte(`{"version":"1.20.0"}`)) + case "/api/v1/user": + _, _ = w.Write([]byte(`{"id":1,"login":"testuser"}`)) + case "/api/v1/user/repos", "/api/v1/user/orgs": + _, _ = w.Write([]byte(`[]`)) + default: + w.WriteHeader(http.StatusNotFound) + } +} + +func TestCommandsHTTPSettings(t *testing.T) { + commands := []struct { + name string + run func(string) error + }{ + { + name: "enum", + run: func(url string) error { + return giteaenum.RunEnum(url, "test-token") + }, + }, + { + name: "secrets", + run: func(url string) error { + return secrets.ListAllSecrets(secrets.Config{URL: url, Token: "test-token"}) + }, + }, + } + + for _, command := range commands { + t.Run(command.name, func(t *testing.T) { + httpclient.SetProxy("") + httpclient.SetIgnoreProxy(true) + httpclient.SetInsecureSkipVerify(true) + httpclient.SetHTTPTimeout(0) + t.Cleanup(func() { + httpclient.SetProxy("") + httpclient.SetIgnoreProxy(false) + httpclient.SetInsecureSkipVerify(true) + httpclient.SetHTTPTimeout(0) + }) + + t.Run("explicit proxy takes precedence over ignore-proxy", func(t *testing.T) { + var directRequests, proxyRequests atomic.Int32 + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + directRequests.Add(1) + serveGitea(w, r) + })) + defer target.Close() + proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxyRequests.Add(1) + assert.True(t, r.URL.IsAbs(), "proxy must receive an absolute request URL") + assert.Equal(t, "token test-token", r.Header.Get("Authorization")) + serveGitea(w, r) + })) + defer proxy.Close() + + httpclient.SetProxy(proxy.URL) + defer httpclient.SetProxy("") + + require.NoError(t, command.run(target.URL)) + assert.GreaterOrEqual(t, proxyRequests.Load(), int32(3), "all SDK requests must use the proxy") + assert.Zero(t, directRequests.Load(), "requests must not bypass the explicit proxy") + }) + + t.Run("TLS verification settings", func(t *testing.T) { + target := httptest.NewTLSServer(http.HandlerFunc(serveGitea)) + defer target.Close() + + httpclient.SetInsecureSkipVerify(true) + require.NoError(t, command.run(target.URL), "default settings must support self-signed targets") + + httpclient.SetInsecureSkipVerify(false) + defer httpclient.SetInsecureSkipVerify(true) + require.Error(t, command.run(target.URL), "enforced verification must reject an untrusted certificate") + }) + + t.Run("HTTP timeout", func(t *testing.T) { + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + serveGitea(w, r) + })) + defer target.Close() + + httpclient.SetHTTPTimeout(10 * time.Millisecond) + defer httpclient.SetHTTPTimeout(0) + err := command.run(target.URL) + require.Error(t, err) + var netErr net.Error + require.True(t, errors.As(err, &netErr), "expected a network timeout, got %v", err) + assert.True(t, netErr.Timeout()) + }) + }) + } +} diff --git a/pkg/gitea/secrets/secrets.go b/pkg/gitea/secrets/secrets.go index dd93fd3e..8f97a66c 100644 --- a/pkg/gitea/secrets/secrets.go +++ b/pkg/gitea/secrets/secrets.go @@ -4,6 +4,7 @@ import ( "fmt" "code.gitea.io/sdk/gitea" + "github.com/CompassSecurity/pipeleek/pkg/httpclient" "github.com/rs/zerolog/log" ) @@ -58,7 +59,8 @@ func ListAllSecrets(cfg Config) error { } func createClientContext(cfg Config) (*clientContext, error) { - client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token)) + client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token), + gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient())) if err != nil { return nil, err } diff --git a/tests/e2e/gitea/enum/enum_test.go b/tests/e2e/gitea/enum/enum_test.go index 777700c6..07a82adb 100644 --- a/tests/e2e/gitea/enum/enum_test.go +++ b/tests/e2e/gitea/enum/enum_test.go @@ -58,19 +58,26 @@ func TestGiteaEnum(t *testing.T) { }) defer cleanup() - stdout, stderr, exitErr := testutil.RunCLI(t, []string{ - "gitea", "enum", - "--url", server.URL, - "--token", "gitea-token", - }, nil, 10*time.Second) + for _, mode := range []string{"direct", "explicit proxy"} { + t.Run(mode, func(t *testing.T) { + args := []string{"gitea", "enum", "--token", "gitea-token"} + if mode == "explicit proxy" { + args = append(args, "--url", "http://gitea.invalid", "--proxy", server.URL, "--ignore-proxy") + } else { + args = append(args, "--url", server.URL) + } + before := len(getRequests()) + stdout, stderr, exitErr := testutil.RunCLI(t, args, nil, 10*time.Second) - assert.Nil(t, exitErr, "Enum command should succeed") - - requests := getRequests() - assert.True(t, len(requests) >= 1, "Should make API requests") - - t.Logf("STDOUT:\n%s", stdout) - t.Logf("STDERR:\n%s", stderr) + assert.Nil(t, exitErr, "Enum command should succeed") + assert.Greater(t, len(getRequests()), before, "Should make API requests") + output := stdout + stderr + assert.Contains(t, output, "testuser") + assert.Contains(t, output, "repo1") + assert.Contains(t, output, "repo2") + assert.Contains(t, output, "my-org") + }) + } } // TestGitea_APIErrors tests various API error responses diff --git a/tests/e2e/gitea/secrets/secrets_test.go b/tests/e2e/gitea/secrets/secrets_test.go index a6c3d5de..0e700561 100644 --- a/tests/e2e/gitea/secrets/secrets_test.go +++ b/tests/e2e/gitea/secrets/secrets_test.go @@ -72,28 +72,28 @@ func TestGiteaSecrets_Success(t *testing.T) { }) defer cleanup() - stdout, stderr, exitErr := testutil.RunCLI(t, []string{ - "gitea", "secrets", - "--url", server.URL, - "--token", "test-token", - }, nil, 10*time.Second) - - assert.Nil(t, exitErr, "Secrets command should succeed") - - output := stdout + stderr - t.Logf("Output:\n%s", output) - - // Verify expected API calls were made - requests := getRequests() - assert.True(t, len(requests) >= 4, "Should make multiple API requests") - - // Verify output contains secrets - assert.Contains(t, output, "ORG_SECRET_1", "Should output org secret 1") - assert.Contains(t, output, "ORG_SECRET_2", "Should output org secret 2") - assert.Contains(t, output, "REPO_SECRET_1", "Should output repo secret 1") - assert.Contains(t, output, "REPO_SECRET_2", "Should output repo secret 2") - assert.Contains(t, output, "test-org", "Should output organization name") - assert.Contains(t, output, "test-repo", "Should output repository name") + for _, mode := range []string{"direct", "explicit proxy"} { + t.Run(mode, func(t *testing.T) { + args := []string{"gitea", "secrets", "--token", "test-token"} + if mode == "explicit proxy" { + args = append(args, "--url", "http://gitea.invalid", "--proxy", server.URL, "--ignore-proxy") + } else { + args = append(args, "--url", server.URL) + } + before := len(getRequests()) + stdout, stderr, exitErr := testutil.RunCLI(t, args, nil, 10*time.Second) + + assert.Nil(t, exitErr, "Secrets command should succeed") + assert.GreaterOrEqual(t, len(getRequests())-before, 4, "Should make multiple API requests") + output := stdout + stderr + assert.Contains(t, output, "ORG_SECRET_1", "Should output org secret 1") + assert.Contains(t, output, "ORG_SECRET_2", "Should output org secret 2") + assert.Contains(t, output, "REPO_SECRET_1", "Should output repo secret 1") + assert.Contains(t, output, "REPO_SECRET_2", "Should output repo secret 2") + assert.Contains(t, output, "test-org", "Should output organization name") + assert.Contains(t, output, "test-repo", "Should output repository name") + }) + } } func TestGiteaSecrets_OrgPagination(t *testing.T) { From f3f9cef234d459676f7174e9fcabece601957d65 Mon Sep 17 00:00:00 2001 From: frjcomp <107982661+frjcomp@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:48:14 +0000 Subject: [PATCH 2/2] fix(gitea): honor HTTP settings in variables and vuln commands Inject the shared HTTP client into the remaining Gitea SDK clients. Add proxy, TLS, timeout, and CLI regression coverage and update proxy documentation. Preserve existing features, defaults, and output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/introduction/proxying.md | 2 +- pkg/gitea/http_settings_test.go | 90 +++++++++++++++++++++ pkg/gitea/variables/variables.go | 3 +- pkg/gitea/vuln/vuln.go | 3 +- tests/e2e/gitea/variables/variables_test.go | 17 ++++ tests/e2e/gitea/vuln/vuln_test.go | 41 ++++++++++ 6 files changed, 153 insertions(+), 3 deletions(-) diff --git a/docs/introduction/proxying.md b/docs/introduction/proxying.md index 30348254..ac0f4244 100644 --- a/docs/introduction/proxying.md +++ b/docs/introduction/proxying.md @@ -36,7 +36,7 @@ HTTP_PROXY=socks5://127.0.0.1:1080 pipeleek gl scan -u https://gitlab.internal.c Alternatively, use the `--proxy` flag to set any proxy from the command line without relying on `HTTP_PROXY`. It accepts both HTTP and SOCKS5 URLs and takes precedence over the environment variable: -Gitea `enum` and `secrets` commands use these shared HTTP settings too, including `--ignore-proxy`, `--tls-verification`, and `--http-timeout`. +All Gitea commands (`scan`, `enum`, `secrets`, `variables`, and `vuln`) use these shared HTTP settings, including `--ignore-proxy`, `--tls-verification`, and `--http-timeout`. For `vuln`, the settings apply to both the Gitea version lookup and NIST requests. ```bash # HTTP proxy diff --git a/pkg/gitea/http_settings_test.go b/pkg/gitea/http_settings_test.go index 008af280..889cae3f 100644 --- a/pkg/gitea/http_settings_test.go +++ b/pkg/gitea/http_settings_test.go @@ -1,6 +1,7 @@ package gitea_test import ( + "bytes" "errors" "net" "net/http" @@ -11,7 +12,11 @@ import ( giteaenum "github.com/CompassSecurity/pipeleek/pkg/gitea/enum" "github.com/CompassSecurity/pipeleek/pkg/gitea/secrets" + "github.com/CompassSecurity/pipeleek/pkg/gitea/variables" + "github.com/CompassSecurity/pipeleek/pkg/gitea/vuln" "github.com/CompassSecurity/pipeleek/pkg/httpclient" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -47,8 +52,93 @@ func TestCommandsHTTPSettings(t *testing.T) { return secrets.ListAllSecrets(secrets.Config{URL: url, Token: "test-token"}) }, }, + { + name: "variables", + run: func(url string) error { + return variables.ListAllVariables(variables.Config{URL: url, Token: "test-token"}) + }, + }, } + runCommandsHTTPSettings(t, commands) +} + +func TestVulnHTTPSettings(t *testing.T) { + httpclient.SetIgnoreProxy(true) + t.Cleanup(func() { + httpclient.SetProxy("") + httpclient.SetIgnoreProxy(false) + httpclient.SetInsecureSkipVerify(true) + httpclient.SetHTTPTimeout(0) + }) + nist := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"totalResults":0,"vulnerabilities":[]}`)) + })) + defer nist.Close() + t.Setenv("PIPELEEK_NIST_BASE_URL", nist.URL) + + for _, mode := range []string{"explicit proxy", "self-signed TLS", "enforced TLS", "timeout"} { + t.Run(mode, func(t *testing.T) { + httpclient.SetProxy("") + httpclient.SetInsecureSkipVerify(true) + httpclient.SetHTTPTimeout(0) + var output bytes.Buffer + savedLogger := log.Logger + log.Logger = zerolog.New(&output) + defer func() { log.Logger = savedLogger }() + + var versionRequests atomic.Int32 + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/nist" { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"totalResults":0,"vulnerabilities":[]}`)) + return + } + versionRequests.Add(1) + if mode == "timeout" { + time.Sleep(100 * time.Millisecond) + } + serveGitea(w, r) + }) + var target *httptest.Server + if mode == "self-signed TLS" || mode == "enforced TLS" { + target = httptest.NewTLSServer(handler) + } else { + target = httptest.NewServer(handler) + } + defer target.Close() + url := target.URL + switch mode { + case "explicit proxy": + httpclient.SetProxy(target.URL) + t.Setenv("PIPELEEK_NIST_BASE_URL", "http://nist.invalid/nist") + url = "http://gitea.invalid" + case "enforced TLS": + httpclient.SetInsecureSkipVerify(false) + case "timeout": + httpclient.SetHTTPTimeout(20 * time.Millisecond) + } + + vuln.RunCheckVulns(url, "test-token") + assert.Contains(t, output.String(), "Finished vuln scan") + if mode == "enforced TLS" || mode == "timeout" { + assert.Contains(t, output.String(), "Failed creating Gitea client") + assert.Contains(t, output.String(), `"version":"none"`) + } else { + assert.Contains(t, output.String(), `"version":"1.20.0"`) + assert.NotContains(t, output.String(), "Failed creating Gitea client") + assert.Positive(t, versionRequests.Load()) + } + }) + } +} + +func runCommandsHTTPSettings(t *testing.T, commands []struct { + name string + run func(string) error +}) { + t.Helper() for _, command := range commands { t.Run(command.name, func(t *testing.T) { httpclient.SetProxy("") diff --git a/pkg/gitea/variables/variables.go b/pkg/gitea/variables/variables.go index 1de76b2d..c144eeef 100644 --- a/pkg/gitea/variables/variables.go +++ b/pkg/gitea/variables/variables.go @@ -60,7 +60,8 @@ func ListAllVariables(cfg Config) error { } func createClientContext(cfg Config) (*clientContext, error) { - client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token)) + client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token), + gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient())) if err != nil { return nil, err } diff --git a/pkg/gitea/vuln/vuln.go b/pkg/gitea/vuln/vuln.go index fda8de31..58bd7596 100644 --- a/pkg/gitea/vuln/vuln.go +++ b/pkg/gitea/vuln/vuln.go @@ -14,7 +14,8 @@ import ( // RunCheckVulns checks the Gitea instance for vulnerabilities func RunCheckVulns(giteaUrl, giteaApiToken string) { version := "none" - giteaClient, err := gitea.NewClient(giteaUrl, gitea.SetToken(giteaApiToken)) + giteaClient, err := gitea.NewClient(giteaUrl, gitea.SetToken(giteaApiToken), + gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient())) if err != nil { log.Warn().Err(err).Msg("Failed creating Gitea client") } else { diff --git a/tests/e2e/gitea/variables/variables_test.go b/tests/e2e/gitea/variables/variables_test.go index adcf5ebd..8c26731a 100644 --- a/tests/e2e/gitea/variables/variables_test.go +++ b/tests/e2e/gitea/variables/variables_test.go @@ -100,6 +100,23 @@ func TestGiteaVariables_Success(t *testing.T) { assert.Contains(t, output, "REPO_VAR_2", "Should output repo variable 2") assert.Contains(t, output, "test-org", "Should output organization name") assert.Contains(t, output, "test-repo", "Should output repository name") + + t.Run("explicit proxy", func(t *testing.T) { + before := len(getRequests()) + stdout, stderr, exitErr := testutil.RunCLI(t, []string{ + "gitea", "variables", + "--url", "http://gitea.invalid", + "--token", "test-token", + "--proxy", server.URL, + "--ignore-proxy", + }, nil, 10*time.Second) + assert.Nil(t, exitErr, "Variables command should succeed through the proxy") + assert.GreaterOrEqual(t, len(getRequests())-before, 4) + output := stdout + stderr + for _, value := range []string{"ORG_VAR_1", "ORG_VAR_2", "REPO_VAR_1", "REPO_VAR_2", "org_value_1", "repo_value_1"} { + assert.Contains(t, output, value) + } + }) } func TestGiteaVariables_Pagination(t *testing.T) { diff --git a/tests/e2e/gitea/vuln/vuln_test.go b/tests/e2e/gitea/vuln/vuln_test.go index 52e5c01a..f9a76af9 100644 --- a/tests/e2e/gitea/vuln/vuln_test.go +++ b/tests/e2e/gitea/vuln/vuln_test.go @@ -109,6 +109,47 @@ func TestGiteaVuln_MissingToken(t *testing.T) { assert.Contains(t, stdout, "required configuration missing", "Should mention missing required configuration") } +func TestGiteaVuln_Proxy(t *testing.T) { + server, getRequests, cleanup := testutil.StartMockServerWithRecording(t, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/v1/version": + _, _ = w.Write([]byte(`{"version":"1.20.0"}`)) + case "/nist": + assert.Contains(t, r.URL.Query().Get("cpeName"), ":gitea:1.20.0:") + _, _ = w.Write([]byte(`{"totalResults":1,"vulnerabilities":[{"cve":{"id":"CVE-2023-1234","descriptions":[{"value":"Test vulnerability"}]}}]}`)) + default: + w.WriteHeader(http.StatusNotFound) + } + }) + defer cleanup() + + stdout, stderr, exitErr := testutil.RunCLI(t, []string{ + "gitea", "vuln", + "--url", "http://gitea.invalid", + "--token", "test-token", + "--proxy", server.URL, + "--ignore-proxy", + }, []string{"PIPELEEK_NIST_BASE_URL=http://nist.invalid/nist"}, 15*time.Second) + + assert.Nil(t, exitErr) + output := stdout + stderr + assert.Contains(t, output, "1.20.0") + assert.Contains(t, output, "CVE-2023-1234") + assert.NotContains(t, output, "Failed creating Gitea client") + var versionSeen, nistSeen bool + for _, request := range getRequests() { + switch request.Path { + case "/api/v1/version": + versionSeen = true + case "/nist": + nistSeen = true + } + } + assert.True(t, versionSeen, "Gitea version lookup must use the proxy") + assert.True(t, nistSeen, "NIST lookup must use the proxy") +} + func TestGiteaVuln_MissingGitea(t *testing.T) { stdout, _, exitErr := testutil.RunCLI(t, []string{ "gitea", "vuln",