This is an automated email from the ASF dual-hosted git repository.

Startrekzky pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new ecb703c8e fix: Multiple credential-leak paths in gitextractor (#8872)
ecb703c8e is described below

commit ecb703c8e944f2b0a28cfa144d069484f85f1e6d
Author: Klesh Wong <[email protected]>
AuthorDate: Sun May 17 17:12:36 2026 +0800

    fix: Multiple credential-leak paths in gitextractor (#8872)
---
 backend/plugins/gitextractor/README.md             |  8 ++-
 .../plugins/gitextractor/parser/clone_gitcli.go    | 67 +++++++++++++++++-----
 2 files changed, 59 insertions(+), 16 deletions(-)

diff --git a/backend/plugins/gitextractor/README.md 
b/backend/plugins/gitextractor/README.md
index 623e55466..f14178fcc 100644
--- a/backend/plugins/gitextractor/README.md
+++ b/backend/plugins/gitextractor/README.md
@@ -14,4 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-Please see details in the [Apache DevLake 
website](https://devlake.apache.org/docs/Plugins/gitextractor)
\ No newline at end of file
+Please see details in the [Apache DevLake 
website](https://devlake.apache.org/docs/Plugins/gitextractor)
+
+
+Build with local compiled v1.3.2 libgit2
+```
+CGO_LDFLAGS="-L/usr/local/lib -lgit2" CGO_CFLAGS="-I/usr/local/include" go 
build ./plugins/gitextractor/... 2>&1 | tail -5
+```
\ No newline at end of file
diff --git a/backend/plugins/gitextractor/parser/clone_gitcli.go 
b/backend/plugins/gitextractor/parser/clone_gitcli.go
index adeceb9e3..61931b521 100644
--- a/backend/plugins/gitextractor/parser/clone_gitcli.go
+++ b/backend/plugins/gitextractor/parser/clone_gitcli.go
@@ -113,37 +113,40 @@ func (g *GitcliCloner) prepareSync() errors.Error {
                }
                // support private key
                if taskData.Options.PrivateKey != "" {
-                       pkFile, err := os.CreateTemp("", "gitext-pk")
+                       // Create a restricted temp directory so the key file 
is protected from the moment of creation (CWE-367, CWE-732)
+                       pkDir, err := os.MkdirTemp("", "gitext-pk-")
                        if err != nil {
-                               g.logger.Error(err, "create temp private key 
file error")
+                               g.logger.Error(err, "create temp private key 
dir error")
+                               return errors.Default.New("failed to handle the 
private key")
+                       }
+                       defer os.RemoveAll(pkDir)
+                       pkFilePath := path.Join(pkDir, "pk")
+                       pkFile, err := os.OpenFile(pkFilePath, 
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+                       if err != nil {
+                               g.logger.Error(err, "create private key file 
error")
                                return errors.Default.New("failed to handle the 
private key")
                        }
                        if _, e := 
pkFile.WriteString(taskData.Options.PrivateKey + "\n"); e != nil {
-                               g.logger.Error(err, "write private key file 
error")
-                               return errors.Default.New("failed to write the  
private key")
+                               g.logger.Error(e, "write private key file 
error")
+                               return errors.Default.New("failed to write the 
private key")
                        }
                        pkFile.Close()
-                       if e := os.Chmod(pkFile.Name(), 0600); e != nil {
-                               g.logger.Error(err, "chmod private key file 
error")
-                               return errors.Default.New("failed to modify the 
private key")
-                       }
 
                        if taskData.Options.Passphrase != "" {
+                               // Pass passphrase via stdin to avoid exposure 
in /proc/<pid>/cmdline (CWE-214)
                                pp := exec.CommandContext(
                                        g.ctx.GetContext(),
                                        "ssh-keygen", "-p",
-                                       "-P", taskData.Options.Passphrase,
                                        "-N", "",
-                                       "-f", pkFile.Name(),
+                                       "-f", pkFilePath,
                                )
-                               if ppout, pperr := pp.CombinedOutput(); pperr 
!= nil {
+                               pp.Stdin = 
strings.NewReader(taskData.Options.Passphrase + "\n")
+                               if _, pperr := pp.CombinedOutput(); pperr != 
nil {
                                        g.logger.Error(pperr, "change private 
key passphrase error")
-                                       g.logger.Info(string(ppout))
                                        return errors.Default.New("failed to 
decrypt the private key")
                                }
                        }
-                       defer os.Remove(pkFile.Name())
-                       sshCmdArgs = append(sshCmdArgs, fmt.Sprintf("-i %s -o 
StrictHostKeyChecking=no", pkFile.Name()))
+                       sshCmdArgs = append(sshCmdArgs, fmt.Sprintf("-i %s -o 
StrictHostKeyChecking=no", pkFilePath))
                }
                if len(sshCmdArgs) > 0 {
                        g.syncEnvs = append(g.syncEnvs, 
fmt.Sprintf("GIT_SSH_COMMAND=ssh %s", strings.Join(sshCmdArgs, " ")))
@@ -251,6 +254,7 @@ func (g *GitcliCloner) doubleClone() errors.Error {
        if e != nil {
                return errors.Convert(e)
        }
+       defer os.RemoveAll(intermediaryDir) // CWE-459: ensure cleanup on all 
exit paths
        // step 1: full clone into a intermediary dir
        backup := g.localDir
        g.localDir = intermediaryDir
@@ -303,7 +307,7 @@ func (g *GitcliCloner) gitCmd(gitcmd string, args 
...string) errors.Error {
 }
 
 func (g *GitcliCloner) git(env []string, dir string, gitcmd string, args 
...string) errors.Error {
-       g.logger.Debug("git %s %v", gitcmd, args)
+       g.logger.Debug("git %s %v", gitcmd, sanitizeArgs(args)) // CWE-532: 
sanitize before logging
        args = append([]string{gitcmd}, args...)
        cmd := exec.CommandContext(g.ctx.GetContext(), "git", args...)
        cmd.Env = env
@@ -335,14 +339,47 @@ func generateErrMsg(output []byte, err error) string {
        return errMsg
 }
 
+// sensitiveQueryParams lists URL query parameter names that may carry 
credentials.
+var sensitiveQueryParams = []string{"token", "access_token", "private_token", 
"api_key", "key", "apikey"}
+
 func sanitizeArgs(args []string) []string {
        var ret []string
        for _, arg := range args {
+               // Redact Authorization header values (e.g. 
--header=Authorization: Bearer TOKEN) (CWE-532)
+               lower := strings.ToLower(arg)
+               if authIdx := strings.Index(lower, "authorization:"); authIdx 
!= -1 {
+                       colonPos := authIdx + len("authorization:")
+                       rest := strings.TrimSpace(arg[colonPos:])
+                       parts := strings.SplitN(rest, " ", 2)
+                       if len(parts) == 2 {
+                               arg = arg[:colonPos] + " " + parts[0] + " " + 
strings.Repeat("*", len(parts[1]))
+                       } else if len(rest) > 0 {
+                               arg = arg[:colonPos] + " " + 
strings.Repeat("*", len(rest))
+                       }
+                       ret = append(ret, arg)
+                       continue
+               }
                u, err := url.Parse(arg)
                if err == nil && u != nil && u.User != nil {
                        password, ok := u.User.Password()
                        if ok {
+                               // Redact password in user:password@host URLs
                                arg = strings.Replace(arg, password, 
strings.Repeat("*", len(password)), -1)
+                       } else {
+                               // Redact username-only tokens (e.g. 
https://[email protected]/..., GitHub App pattern)
+                               username := u.User.Username()
+                               if len(username) >= 16 {
+                                       arg = strings.Replace(arg, username, 
strings.Repeat("*", len(username)), -1)
+                               }
+                       }
+               }
+               // Redact sensitive query parameters (e.g. ?private_token=..., 
?access_token=...)
+               if err == nil && u != nil && u.RawQuery != "" {
+                       q := u.Query()
+                       for _, param := range sensitiveQueryParams {
+                               if val := q.Get(param); val != "" {
+                                       arg = strings.Replace(arg, val, 
strings.Repeat("*", len(val)), -1)
+                               }
                        }
                }
                ret = append(ret, arg)

Reply via email to