finding.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package report
  2. import (
  3. "fmt"
  4. "math"
  5. "strings"
  6. "github.com/charmbracelet/lipgloss"
  7. "github.com/zricethezav/gitleaks/v8/sources"
  8. )
  9. // Finding contains a whole bunch of information about a secret finding.
  10. // Plenty of real estate in this bad boy so fillerup as needed.
  11. type Finding struct {
  12. // Rule is the name of the rule that was matched
  13. RuleID string
  14. Description string
  15. StartLine int
  16. EndLine int
  17. StartColumn int
  18. EndColumn int
  19. Line string `json:"-"`
  20. Match string
  21. // Captured secret
  22. Secret string
  23. // File is the name of the file containing the finding
  24. File string
  25. SymlinkFile string
  26. Commit string
  27. Link string `json:",omitempty"`
  28. // Entropy is the shannon entropy of Value
  29. Entropy float32
  30. Author string
  31. Email string
  32. Date string
  33. Message string
  34. Tags []string
  35. // unique identifier
  36. Fingerprint string
  37. // Fragment used for multi-part rule checking, CEL filtering,
  38. // and eventually ML validation
  39. Fragment *sources.Fragment `json:",omitempty"`
  40. // TODO keeping private for now to during experimental phase
  41. requiredFindings []*RequiredFinding
  42. }
  43. type RequiredFinding struct {
  44. // contains a subset of the Finding fields
  45. // only used for reporting
  46. RuleID string
  47. StartLine int
  48. EndLine int
  49. StartColumn int
  50. EndColumn int
  51. Line string `json:"-"`
  52. Match string
  53. Secret string
  54. }
  55. func (f *Finding) AddRequiredFindings(afs []*RequiredFinding) {
  56. if f.requiredFindings == nil {
  57. f.requiredFindings = make([]*RequiredFinding, 0)
  58. }
  59. f.requiredFindings = append(f.requiredFindings, afs...)
  60. }
  61. // Redact removes sensitive information from a finding.
  62. func (f *Finding) Redact(percent uint) {
  63. secret := maskSecret(f.Secret, percent)
  64. if percent >= 100 {
  65. secret = "REDACTED"
  66. }
  67. f.Line = strings.ReplaceAll(f.Line, f.Secret, secret)
  68. f.Match = strings.ReplaceAll(f.Match, f.Secret, secret)
  69. f.Secret = secret
  70. }
  71. func maskSecret(secret string, percent uint) string {
  72. if percent > 100 {
  73. percent = 100
  74. }
  75. len := float64(len(secret))
  76. if len <= 0 {
  77. return secret
  78. }
  79. prc := float64(100 - percent)
  80. lth := int64(math.RoundToEven(len * prc / float64(100)))
  81. return secret[:lth] + "..."
  82. }
  83. func (f *Finding) PrintRequiredFindings() {
  84. if len(f.requiredFindings) == 0 {
  85. return
  86. }
  87. fmt.Printf("%-12s ", "Required:")
  88. // Create orange style for secrets
  89. orangeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#bf9478"))
  90. for i, aux := range f.requiredFindings {
  91. auxSecret := strings.TrimSpace(aux.Secret)
  92. // Truncate long secrets for readability
  93. if len(auxSecret) > 40 {
  94. auxSecret = auxSecret[:37] + "..."
  95. }
  96. // Format: rule-id:line:secret
  97. if i == 0 {
  98. fmt.Printf("%s:%d:%s\n", aux.RuleID, aux.StartLine, orangeStyle.Render(auxSecret))
  99. } else {
  100. fmt.Printf("%-12s %s:%d:%s\n", "", aux.RuleID, aux.StartLine, orangeStyle.Render(auxSecret))
  101. }
  102. }
  103. }