package cli import ( "regexp" "strings" ) // Privacy mode redacts sensitive tokens (IPs, long secret-like strings, // hostnames, MAC addresses, ssh key material) from rendered output. The // replacement uses a shifted block pattern (▚▞) which preserves token // length so the layout doesn't shift but makes the content obviously // unreadable — think "frosted glass" rather than the usual `****`. var ( privacyIPv4 = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`) privacyIPv6 = regexp.MustCompile(`\b(?:[0-9a-fA-F]{1,4}:){2,}[0-9a-fA-F:]{0,}\b`) privacyMAC = regexp.MustCompile(`\b(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\b`) privacyHost = regexp.MustCompile(`\b[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}(?:\.[a-zA-Z0-9\-]{1,63}){1,}\b`) privacyHex = regexp.MustCompile(`\b[A-Fa-f0-9]{24,}\b`) privacyB64 = regexp.MustCompile(`\b[A-Za-z0-9+/]{28,}={0,2}\b`) privacyKey = regexp.MustCompile(`(?i)(password|passwd|token|secret|apikey|api[_-]?key|bearer|authorization)\s*[:=]\s*\S+`) privacyUsrAt = regexp.MustCompile(`[A-Za-z0-9._-]+@[A-Za-z0-9.-]+`) ) // privacyGlyphs is a small set of dense unicode shade characters. By // cycling through them the redacted range looks like diffused noise // rather than a flat mask. var privacyGlyphs = []rune{'▚', '▞', '▓', '▒'} // privacyMask returns a redaction string the same visual length as src. func privacyMask(src string) string { rs := []rune(src) out := make([]rune, len(rs)) for i, r := range rs { if r == ' ' || r == '\t' || r == '\n' { out[i] = r continue } out[i] = privacyGlyphs[i%len(privacyGlyphs)] } return string(out) } // privacyRedact scrubs every sensitive pattern from the given line. The // function is intentionally line-scoped — callers apply it row by row so // that multi-line ANSI layouts survive the substitution. func privacyRedact(line string) string { if line == "" { return line } replace := func(re *regexp.Regexp, s string) string { return re.ReplaceAllStringFunc(s, privacyMask) } // Order matters: scrub the longest/most specific patterns first so // later passes don't hit already-masked text. line = privacyKey.ReplaceAllStringFunc(line, func(m string) string { // Keep the label (password/token/etc) but mask the value. idx := strings.IndexAny(m, "=:") if idx < 0 { return privacyMask(m) } return m[:idx+1] + privacyMask(strings.TrimLeft(m[idx+1:], " ")) }) line = replace(privacyB64, line) line = replace(privacyHex, line) line = replace(privacyMAC, line) line = replace(privacyIPv4, line) line = replace(privacyIPv6, line) line = replace(privacyUsrAt, line) line = replace(privacyHost, line) return line } // applyPrivacyMultiline redacts every line independently. Safe to call // on ANSI-styled output — masks only the literal runs a regex matches. func applyPrivacyMultiline(s string) string { if s == "" { return s } lines := strings.Split(s, "\n") for i, ln := range lines { lines[i] = privacyRedact(ln) } return strings.Join(lines, "\n") }