utils.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package detect
  2. import (
  3. // "encoding/json"
  4. "fmt"
  5. "math"
  6. "strings"
  7. "time"
  8. "github.com/charmbracelet/lipgloss"
  9. "github.com/zricethezav/gitleaks/v8/report"
  10. "github.com/gitleaks/go-gitdiff/gitdiff"
  11. "github.com/rs/zerolog/log"
  12. )
  13. // augmentGitFinding updates the start and end line numbers of a finding to include the
  14. // delta from the git diff
  15. func augmentGitFinding(finding report.Finding, textFragment *gitdiff.TextFragment, f *gitdiff.File) report.Finding {
  16. if !strings.HasPrefix(finding.Match, "file detected") {
  17. finding.StartLine += int(textFragment.NewPosition)
  18. finding.EndLine += int(textFragment.NewPosition)
  19. }
  20. if f.PatchHeader != nil {
  21. finding.Commit = f.PatchHeader.SHA
  22. finding.Message = f.PatchHeader.Message()
  23. if f.PatchHeader.Author != nil {
  24. finding.Author = f.PatchHeader.Author.Name
  25. finding.Email = f.PatchHeader.Author.Email
  26. }
  27. finding.Date = f.PatchHeader.AuthorDate.UTC().Format(time.RFC3339)
  28. }
  29. return finding
  30. }
  31. // shannonEntropy calculates the entropy of data using the formula defined here:
  32. // https://en.wiktionary.org/wiki/Shannon_entropy
  33. // Another way to think about what this is doing is calculating the number of bits
  34. // needed to on average encode the data. So, the higher the entropy, the more random the data, the
  35. // more bits needed to encode that data.
  36. func shannonEntropy(data string) (entropy float64) {
  37. if data == "" {
  38. return 0
  39. }
  40. charCounts := make(map[rune]int)
  41. for _, char := range data {
  42. charCounts[char]++
  43. }
  44. invLength := 1.0 / float64(len(data))
  45. for _, count := range charCounts {
  46. freq := float64(count) * invLength
  47. entropy -= freq * math.Log2(freq)
  48. }
  49. return entropy
  50. }
  51. // filter will dedupe and redact findings
  52. func filter(findings []report.Finding, redact bool) []report.Finding {
  53. var retFindings []report.Finding
  54. for _, f := range findings {
  55. include := true
  56. if strings.Contains(strings.ToLower(f.RuleID), "generic") {
  57. for _, fPrime := range findings {
  58. if f.StartLine == fPrime.StartLine &&
  59. f.Commit == fPrime.Commit &&
  60. f.RuleID != fPrime.RuleID &&
  61. strings.Contains(fPrime.Secret, f.Secret) &&
  62. !strings.Contains(strings.ToLower(fPrime.RuleID), "generic") {
  63. genericMatch := strings.Replace(f.Match, f.Secret, "REDACTED", -1)
  64. betterMatch := strings.Replace(fPrime.Match, fPrime.Secret, "REDACTED", -1)
  65. log.Trace().Msgf("skipping %s finding (%s), %s rule takes precendence (%s)", f.RuleID, genericMatch, fPrime.RuleID, betterMatch)
  66. include = false
  67. break
  68. }
  69. }
  70. }
  71. if redact {
  72. f.Redact()
  73. }
  74. if include {
  75. retFindings = append(retFindings, f)
  76. }
  77. }
  78. return retFindings
  79. }
  80. func printFinding(f report.Finding) {
  81. // trim all whitespace and tabs from the line
  82. f.Line = strings.TrimSpace(f.Line)
  83. // trim all whitespace and tabs from the secret
  84. f.Secret = strings.TrimSpace(f.Secret)
  85. // trim all whitespace and tabs from the match
  86. f.Match = strings.TrimSpace(f.Match)
  87. matchInLineIDX := strings.Index(f.Line, f.Match)
  88. secretInMatchIdx := strings.Index(f.Match, f.Secret)
  89. start := f.Line[0:matchInLineIDX]
  90. startMatchIdx := 0
  91. if matchInLineIDX > 20 {
  92. startMatchIdx = matchInLineIDX - 20
  93. start = "..." + f.Line[startMatchIdx:matchInLineIDX]
  94. }
  95. matchBeginning := lipgloss.NewStyle().SetString(f.Match[0:secretInMatchIdx]).Foreground(lipgloss.Color("#f5d445"))
  96. secret := lipgloss.NewStyle().SetString(f.Secret).
  97. Bold(true).
  98. Italic(true).
  99. Foreground(lipgloss.Color("#f05c07"))
  100. matchEnd := lipgloss.NewStyle().SetString(f.Match[secretInMatchIdx+len(f.Secret):]).Foreground(lipgloss.Color("#f5d445"))
  101. lineEnd := f.Line[matchInLineIDX+len(f.Match):]
  102. if len(f.Secret) > 100 {
  103. secret = lipgloss.NewStyle().SetString(f.Secret[0:100] + "...").
  104. Bold(true).
  105. Italic(true).
  106. Foreground(lipgloss.Color("#f05c07"))
  107. }
  108. if len(lineEnd) > 20 {
  109. lineEnd = lineEnd[0:20] + "..."
  110. }
  111. finding := fmt.Sprintf("%s%s%s%s%s\n", strings.TrimPrefix(strings.TrimLeft(start, " "), "\n"), matchBeginning, secret, matchEnd, lineEnd)
  112. fmt.Printf("%-12s %s", "Finding:", finding)
  113. fmt.Printf("%-12s %s\n", "Secret:", secret)
  114. fmt.Printf("%-12s %s\n", "RuleID:", f.RuleID)
  115. fmt.Printf("%-12s %f\n", "Entropy:", f.Entropy)
  116. if f.File == "" {
  117. fmt.Println("")
  118. return
  119. }
  120. fmt.Printf("%-12s %s\n", "File:", f.File)
  121. fmt.Printf("%-12s %d\n", "Line:", f.StartLine)
  122. if f.Commit == "" {
  123. fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
  124. fmt.Println("")
  125. return
  126. }
  127. fmt.Printf("%-12s %s\n", "Commit:", f.Commit)
  128. fmt.Printf("%-12s %s\n", "Author:", f.Author)
  129. fmt.Printf("%-12s %s\n", "Email:", f.Email)
  130. fmt.Printf("%-12s %s\n", "Date:", f.Date)
  131. fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
  132. fmt.Println("")
  133. }
  134. func containsDigit(s string) bool {
  135. for _, c := range s {
  136. switch c {
  137. case '1', '2', '3', '4', '5', '6', '7', '8', '9':
  138. return true
  139. }
  140. }
  141. return false
  142. }