64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// Package argmask renders a command line for messages with the values of
|
|
// secret-named long flags masked; bonfire's wrong-directory hint and
|
|
// summer's delegate errors share it.
|
|
package argmask
|
|
|
|
import "strings"
|
|
|
|
// secretWords mark a long flag whose value is masked.
|
|
var secretWords = []string{"password", "passwd", "secret", "token", "key"}
|
|
|
|
// secretFlag reports whether the long flag name (without "--") looks like it
|
|
// carries a secret.
|
|
func secretFlag(name string) bool {
|
|
name = strings.ToLower(name)
|
|
for _, w := range secretWords {
|
|
if strings.Contains(name, w) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Join renders args as one line, separated by single spaces and each
|
|
// quoted for a shell (Quote). The value of a long flag named like a secret
|
|
// becomes ***, in the "--flag=value" form and in the "--flag value" form,
|
|
// so a password typed on the command line does not reach stderr and CI
|
|
// logs. In the "--flag value" form the next argument is masked whatever its
|
|
// first character (pflag takes "-Xk9" as the value too); only "--" is left
|
|
// alone. Join cannot see flag types, so a secret-named boolean flag
|
|
// over-masks the argument after it, which is the safe side.
|
|
func Join(args []string) string {
|
|
var b strings.Builder
|
|
for i := 0; i < len(args); i++ {
|
|
if i > 0 {
|
|
b.WriteByte(' ')
|
|
}
|
|
a := args[i]
|
|
name, long := strings.CutPrefix(a, "--")
|
|
flag, _, inline := strings.Cut(name, "=")
|
|
if !long || flag == "" || !secretFlag(flag) {
|
|
b.WriteString(Quote(a))
|
|
continue
|
|
}
|
|
if inline {
|
|
b.WriteString(Quote("--"+flag) + "=***")
|
|
continue
|
|
}
|
|
b.WriteString(Quote(a))
|
|
if i+1 < len(args) && args[i+1] != "--" {
|
|
b.WriteString(" ***")
|
|
i++
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// Quote single-quotes s when a shell would split or expand it.
|
|
func Quote(s string) string {
|
|
if s != "" && !strings.ContainsAny(s, " \t\n'\"\\$`*?[]{}()<>|&;#~!") {
|
|
return s
|
|
}
|
|
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
|
}
|