repo_description: Release engineering, simplified
repo_topics: announcements github-actions hacktoberfest package release-automation release-engineering
code: // Package redact provides functions to redact secrets from strings.
  package redact
  
  import (
  	"cmp"
  	"io"
  	"slices"
  	"strings"
  )
  
  // Writer wraps the writer and redacts secret-looking environment
  // variable values in the written data with their "$NAME" counterparts.
  //
  // Each entry in env should be in "KEY=VALUE" format.
  func Writer(w io.Writer, env []string) io.Writer {
  	return &redactWriter{
  		re: redact(env),
  		w:  w,
  	}
  }
  
  type redactWriter struct {
  	re *strings.Replacer
  	w  io.Writer
  }
  
  // Write implements [io.Writer].
  func (w *redactWriter) Write(p []byte) (int, error) {
  	_, err := io.WriteString(w.w, w.re.Replace(string(p)))
  	if err != nil {
  		return 0, err
  	}
  	return len(p), nil
  }
  
  // redact returns a strings.Replacer that replaces all occurrences of
  // secret-looking environment variable values in s with their "$NAME"
  // counterparts.
  //
  // Each entry in env should be in "KEY=VALUE" format.
  func redact(env []string) *strings.Replacer {
  	type kv struct{ k, v string }
  	var secrets []kv
  	for _, e := range env {
  		k, v, ok := strings.Cut(e, "=")
  		if !ok || v == "" {
  			continue
  		}
  		if looksSecret(k, v) {
  			secrets = append(secrets, kv{k, v})
  		}
  	}
  	slices.SortFunc(secrets, func(a, b kv) int {
  		if c := cmp.Compare(len(b.v), len(a.v)); c != 0 {
  			return c
  		}
  		return cmp.Compare(a.k, b.k)
  	})
  	oldnew := make([]string, 0, len(secrets)*2)
  	for _, e := range secrets {
  		oldnew = append(oldnew, e.v, "$"+e.k)
  	}
  	return strings.NewReplacer(oldnew...)
  }
  
  var keySuffixes = []string{
  	"_KEY",
  	"_SECRET",
  	"_PASSWORD",
  	"_TOKEN",
  }
  
  var valuePrefixes = []string{
  	"sk-",
  	"ghp_",
  	"ghs_",
  	"gho_",
  	"ghu_",
  	"dckr_pat_",
  	"glpat-",
  	"AIZA",
  	"xox",
  }
  
  // String redacts secret-looking environment variable values in s
  // with their "$NAME" counterparts.
  //
  // Each entry in env should be in "KEY=VALUE" format.
  func String(s string, env []string) string {
  	return redact(env).Replace(s)
  }
  
  func looksSecret(k, v string) bool {
  	for _, s := range keySuffixes {
  		if strings.HasSuffix(k, s) {
  			return true
  		}
  	}
  	for _, p := range valuePrefixes {
  		if strings.HasPrefix(v, p) {
  			return true
  		}
  	}
  	return false
  }
  