// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package report import ( "slices" "strconv" "strings" "sync" "sync/atomic" "gitea.com/gitea/runner/act/runner" ) // A job's plan runs before its job logger exists and logs through the process-wide one, which // several tasks share, so that one holds the union of every live task's starting secrets. var ( globalMu sync.Mutex globalMasks = map[*Reporter][]string{} globalReplacer atomic.Pointer[strings.Replacer] // nil while nothing is registered ) func registerGlobalMasks(r *Reporter) { globalMu.Lock() defer globalMu.Unlock() masks := slices.Clip(r.oldnew) for i := 0; i < len(r.oldnew); i += 2 { // the console handler Go-quotes values with special characters if quoted := strconv.Quote(r.oldnew[i]); quoted[1:len(quoted)-1] != r.oldnew[i] { masks = append(masks, quoted[1:len(quoted)-1], r.oldnew[i+1]) } } globalMasks[r] = masks rebuildGlobalReplacer() } func deregisterGlobalMasks(r *Reporter) { globalMu.Lock() defer globalMu.Unlock() delete(globalMasks, r) rebuildGlobalReplacer() } func rebuildGlobalReplacer() { // caller holds globalMu var oldnew []string for _, masks := range globalMasks { oldnew = append(oldnew, masks...) } if len(oldnew) == 0 { globalReplacer.Store(nil) return } globalReplacer.Store(runner.NewSecretReplacer(oldnew)) } // MaskGlobal hides every registered value in a formatted line of the process-wide log, fields // included. It is meant for logging.TextOptions.Filter. func MaskGlobal(line []byte) []byte { replacer := globalReplacer.Load() if replacer == nil { // nothing to hide, so an idle daemon pays no copy return line } return []byte(replacer.Replace(string(line))) }