@@ -113,37 +113,40 @@ func (g *GitcliCloner) prepareSync() errors.Error {
113113 }
114114 // support private key
115115 if taskData .Options .PrivateKey != "" {
116- pkFile , err := os .CreateTemp ("" , "gitext-pk" )
116+ // Create a restricted temp directory so the key file is protected from the moment of creation (CWE-367, CWE-732)
117+ pkDir , err := os .MkdirTemp ("" , "gitext-pk-" )
117118 if err != nil {
118- g .logger .Error (err , "create temp private key file error" )
119+ g .logger .Error (err , "create temp private key dir error" )
120+ return errors .Default .New ("failed to handle the private key" )
121+ }
122+ defer os .RemoveAll (pkDir )
123+ pkFilePath := path .Join (pkDir , "pk" )
124+ pkFile , err := os .OpenFile (pkFilePath , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0600 )
125+ if err != nil {
126+ g .logger .Error (err , "create private key file error" )
119127 return errors .Default .New ("failed to handle the private key" )
120128 }
121129 if _ , e := pkFile .WriteString (taskData .Options .PrivateKey + "\n " ); e != nil {
122- g .logger .Error (err , "write private key file error" )
123- return errors .Default .New ("failed to write the private key" )
130+ g .logger .Error (e , "write private key file error" )
131+ return errors .Default .New ("failed to write the private key" )
124132 }
125133 pkFile .Close ()
126- if e := os .Chmod (pkFile .Name (), 0600 ); e != nil {
127- g .logger .Error (err , "chmod private key file error" )
128- return errors .Default .New ("failed to modify the private key" )
129- }
130134
131135 if taskData .Options .Passphrase != "" {
136+ // Pass passphrase via stdin to avoid exposure in /proc/<pid>/cmdline (CWE-214)
132137 pp := exec .CommandContext (
133138 g .ctx .GetContext (),
134139 "ssh-keygen" , "-p" ,
135- "-P" , taskData .Options .Passphrase ,
136140 "-N" , "" ,
137- "-f" , pkFile . Name () ,
141+ "-f" , pkFilePath ,
138142 )
139- if ppout , pperr := pp .CombinedOutput (); pperr != nil {
143+ pp .Stdin = strings .NewReader (taskData .Options .Passphrase + "\n " )
144+ if _ , pperr := pp .CombinedOutput (); pperr != nil {
140145 g .logger .Error (pperr , "change private key passphrase error" )
141- g .logger .Info (string (ppout ))
142146 return errors .Default .New ("failed to decrypt the private key" )
143147 }
144148 }
145- defer os .Remove (pkFile .Name ())
146- sshCmdArgs = append (sshCmdArgs , fmt .Sprintf ("-i %s -o StrictHostKeyChecking=no" , pkFile .Name ()))
149+ sshCmdArgs = append (sshCmdArgs , fmt .Sprintf ("-i %s -o StrictHostKeyChecking=no" , pkFilePath ))
147150 }
148151 if len (sshCmdArgs ) > 0 {
149152 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 {
251254 if e != nil {
252255 return errors .Convert (e )
253256 }
257+ defer os .RemoveAll (intermediaryDir ) // CWE-459: ensure cleanup on all exit paths
254258 // step 1: full clone into a intermediary dir
255259 backup := g .localDir
256260 g .localDir = intermediaryDir
@@ -303,7 +307,7 @@ func (g *GitcliCloner) gitCmd(gitcmd string, args ...string) errors.Error {
303307}
304308
305309func (g * GitcliCloner ) git (env []string , dir string , gitcmd string , args ... string ) errors.Error {
306- g .logger .Debug ("git %s %v" , gitcmd , args )
310+ g .logger .Debug ("git %s %v" , gitcmd , sanitizeArgs ( args )) // CWE-532: sanitize before logging
307311 args = append ([]string {gitcmd }, args ... )
308312 cmd := exec .CommandContext (g .ctx .GetContext (), "git" , args ... )
309313 cmd .Env = env
@@ -335,14 +339,47 @@ func generateErrMsg(output []byte, err error) string {
335339 return errMsg
336340}
337341
342+ // sensitiveQueryParams lists URL query parameter names that may carry credentials.
343+ var sensitiveQueryParams = []string {"token" , "access_token" , "private_token" , "api_key" , "key" , "apikey" }
344+
338345func sanitizeArgs (args []string ) []string {
339346 var ret []string
340347 for _ , arg := range args {
348+ // Redact Authorization header values (e.g. --header=Authorization: Bearer TOKEN) (CWE-532)
349+ lower := strings .ToLower (arg )
350+ if authIdx := strings .Index (lower , "authorization:" ); authIdx != - 1 {
351+ colonPos := authIdx + len ("authorization:" )
352+ rest := strings .TrimSpace (arg [colonPos :])
353+ parts := strings .SplitN (rest , " " , 2 )
354+ if len (parts ) == 2 {
355+ arg = arg [:colonPos ] + " " + parts [0 ] + " " + strings .Repeat ("*" , len (parts [1 ]))
356+ } else if len (rest ) > 0 {
357+ arg = arg [:colonPos ] + " " + strings .Repeat ("*" , len (rest ))
358+ }
359+ ret = append (ret , arg )
360+ continue
361+ }
341362 u , err := url .Parse (arg )
342363 if err == nil && u != nil && u .User != nil {
343364 password , ok := u .User .Password ()
344365 if ok {
366+ // Redact password in user:password@host URLs
345367 arg = strings .Replace (arg , password , strings .Repeat ("*" , len (password )), - 1 )
368+ } else {
369+ // Redact username-only tokens (e.g. https://TOKEN@github.com/..., GitHub App pattern)
370+ username := u .User .Username ()
371+ if len (username ) >= 16 {
372+ arg = strings .Replace (arg , username , strings .Repeat ("*" , len (username )), - 1 )
373+ }
374+ }
375+ }
376+ // Redact sensitive query parameters (e.g. ?private_token=..., ?access_token=...)
377+ if err == nil && u != nil && u .RawQuery != "" {
378+ q := u .Query ()
379+ for _ , param := range sensitiveQueryParams {
380+ if val := q .Get (param ); val != "" {
381+ arg = strings .Replace (arg , val , strings .Repeat ("*" , len (val )), - 1 )
382+ }
346383 }
347384 }
348385 ret = append (ret , arg )
0 commit comments