4
0

utils.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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/logging"
  10. "github.com/zricethezav/gitleaks/v8/report"
  11. "github.com/gitleaks/go-gitdiff/gitdiff"
  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 uint) []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. logging.Trace().Msgf("skipping %s finding (%s), %s rule takes precedence (%s)", f.RuleID, genericMatch, fPrime.RuleID, betterMatch)
  66. include = false
  67. break
  68. }
  69. }
  70. }
  71. if redact > 0 {
  72. f.Redact(redact)
  73. }
  74. if include {
  75. retFindings = append(retFindings, f)
  76. }
  77. }
  78. return retFindings
  79. }
  80. func printFinding(f report.Finding, noColor bool) {
  81. // trim all whitespace and tabs
  82. f.Line = strings.TrimSpace(f.Line)
  83. f.Secret = strings.TrimSpace(f.Secret)
  84. f.Match = strings.TrimSpace(f.Match)
  85. isFileMatch := strings.HasPrefix(f.Match, "file detected:")
  86. skipColor := noColor
  87. finding := ""
  88. var secret lipgloss.Style
  89. // Matches from filenames do not have a |line| or |secret|
  90. if !isFileMatch {
  91. matchInLineIDX := strings.Index(f.Line, f.Match)
  92. secretInMatchIdx := strings.Index(f.Match, f.Secret)
  93. skipColor = false
  94. if matchInLineIDX == -1 || noColor {
  95. skipColor = true
  96. matchInLineIDX = 0
  97. }
  98. start := f.Line[0:matchInLineIDX]
  99. startMatchIdx := 0
  100. if matchInLineIDX > 20 {
  101. startMatchIdx = matchInLineIDX - 20
  102. start = "..." + f.Line[startMatchIdx:matchInLineIDX]
  103. }
  104. matchBeginning := lipgloss.NewStyle().SetString(f.Match[0:secretInMatchIdx]).Foreground(lipgloss.Color("#f5d445"))
  105. secret = lipgloss.NewStyle().SetString(f.Secret).
  106. Bold(true).
  107. Italic(true).
  108. Foreground(lipgloss.Color("#f05c07"))
  109. matchEnd := lipgloss.NewStyle().SetString(f.Match[secretInMatchIdx+len(f.Secret):]).Foreground(lipgloss.Color("#f5d445"))
  110. lineEndIdx := matchInLineIDX + len(f.Match)
  111. if len(f.Line)-1 <= lineEndIdx {
  112. lineEndIdx = len(f.Line)
  113. }
  114. lineEnd := f.Line[lineEndIdx:]
  115. if len(f.Secret) > 100 {
  116. secret = lipgloss.NewStyle().SetString(f.Secret[0:100] + "...").
  117. Bold(true).
  118. Italic(true).
  119. Foreground(lipgloss.Color("#f05c07"))
  120. }
  121. if len(lineEnd) > 20 {
  122. lineEnd = lineEnd[0:20] + "..."
  123. }
  124. finding = fmt.Sprintf("%s%s%s%s%s\n", strings.TrimPrefix(strings.TrimLeft(start, " "), "\n"), matchBeginning, secret, matchEnd, lineEnd)
  125. }
  126. if skipColor || isFileMatch {
  127. fmt.Printf("%-12s %s\n", "Finding:", f.Match)
  128. fmt.Printf("%-12s %s\n", "Secret:", f.Secret)
  129. } else {
  130. fmt.Printf("%-12s %s", "Finding:", finding)
  131. fmt.Printf("%-12s %s\n", "Secret:", secret)
  132. }
  133. fmt.Printf("%-12s %s\n", "RuleID:", f.RuleID)
  134. fmt.Printf("%-12s %f\n", "Entropy:", f.Entropy)
  135. if f.File == "" {
  136. fmt.Println("")
  137. return
  138. }
  139. if len(f.Tags) > 0 {
  140. fmt.Printf("%-12s %s\n", "Tags:", f.Tags)
  141. }
  142. fmt.Printf("%-12s %s\n", "File:", f.File)
  143. fmt.Printf("%-12s %d\n", "Line:", f.StartLine)
  144. if f.Commit == "" {
  145. fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
  146. fmt.Println("")
  147. return
  148. }
  149. fmt.Printf("%-12s %s\n", "Commit:", f.Commit)
  150. fmt.Printf("%-12s %s\n", "Author:", f.Author)
  151. fmt.Printf("%-12s %s\n", "Email:", f.Email)
  152. fmt.Printf("%-12s %s\n", "Date:", f.Date)
  153. fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint)
  154. fmt.Println("")
  155. }
  156. func containsDigit(s string) bool {
  157. for _, c := range s {
  158. switch c {
  159. case '1', '2', '3', '4', '5', '6', '7', '8', '9':
  160. return true
  161. }
  162. }
  163. return false
  164. }
  165. func isWhitespace(ch byte) bool {
  166. return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
  167. }