commit.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package scan
  2. import (
  3. "fmt"
  4. "path/filepath"
  5. "strings"
  6. "github.com/go-git/go-git/v5"
  7. fdiff "github.com/go-git/go-git/v5/plumbing/format/diff"
  8. "github.com/go-git/go-git/v5/plumbing/object"
  9. )
  10. // CommitScanner is a commit scanner
  11. type CommitScanner struct {
  12. BaseScanner
  13. repo *git.Repository
  14. repoName string
  15. commit *object.Commit
  16. patch *object.Patch
  17. }
  18. // NewCommitScanner creates and returns a commit scanner
  19. func NewCommitScanner(base BaseScanner, repo *git.Repository, commit *object.Commit) *CommitScanner {
  20. cs := &CommitScanner{
  21. BaseScanner: base,
  22. repo: repo,
  23. commit: commit,
  24. repoName: getRepoName(base.opts),
  25. }
  26. cs.scannerType = typeCommitScanner
  27. return cs
  28. }
  29. // SetRepoName sets the repo name of the scanner.
  30. func (cs *CommitScanner) SetRepoName(repoName string) {
  31. cs.repoName = repoName
  32. }
  33. // SetPatch sets the patch to be inspected by the commit scanner. This is used to avoid
  34. // a race condition when running a threaded repo scan
  35. func (cs *CommitScanner) SetPatch(patch *object.Patch) {
  36. cs.patch = patch
  37. }
  38. // Scan kicks off a CommitScanner Scan
  39. func (cs *CommitScanner) Scan() (Report, error) {
  40. var scannerReport Report
  41. if len(cs.commit.ParentHashes) == 0 {
  42. facScanner := NewFilesAtCommitScanner(cs.BaseScanner, cs.repo, cs.commit)
  43. return facScanner.Scan()
  44. }
  45. if cs.patch == nil {
  46. parent, err := cs.commit.Parent(0)
  47. if err != nil {
  48. return scannerReport, err
  49. }
  50. if parent == nil {
  51. return scannerReport, nil
  52. }
  53. cs.patch, err = parent.Patch(cs.commit)
  54. if err != nil {
  55. return scannerReport, fmt.Errorf("could not generate Patch")
  56. }
  57. }
  58. patchContent := cs.patch.String()
  59. for _, f := range cs.patch.FilePatches() {
  60. if f.IsBinary() {
  61. continue
  62. }
  63. for _, chunk := range f.Chunks() {
  64. if chunk.Type() == fdiff.Add {
  65. _, to := f.Files()
  66. if cs.cfg.Allowlist.FileAllowed(filepath.Base(to.Path())) ||
  67. cs.cfg.Allowlist.PathAllowed(to.Path()) {
  68. continue
  69. }
  70. // Check individual file path ONLY rules
  71. for _, rule := range cs.cfg.Rules {
  72. if rule.CommitAllowed(cs.commit.Hash.String()) {
  73. continue
  74. }
  75. if rule.HasFileOrPathLeakOnly(to.Path()) {
  76. leak := NewLeak("", "Filename or path offender: "+to.Path(), defaultLineNumber).WithCommit(cs.commit)
  77. leak.Repo = cs.repoName
  78. leak.File = to.Path()
  79. leak.RepoURL = cs.opts.RepoURL
  80. leak.LeakURL = leak.URL()
  81. leak.Rule = rule.Description
  82. leak.Tags = strings.Join(rule.Tags, ", ")
  83. if cs.opts.Verbose {
  84. leak.Log(cs.opts.Redact)
  85. }
  86. scannerReport.Leaks = append(scannerReport.Leaks, leak)
  87. continue
  88. }
  89. }
  90. lineLookup := make(map[string]bool)
  91. // Check the actual content
  92. for _, line := range strings.Split(chunk.Content(), "\n") {
  93. for _, rule := range cs.cfg.Rules {
  94. offender := rule.Inspect(line)
  95. if offender == "" {
  96. continue
  97. }
  98. if cs.cfg.Allowlist.RegexAllowed(line) ||
  99. rule.AllowList.FileAllowed(filepath.Base(to.Path())) ||
  100. rule.AllowList.PathAllowed(to.Path()) ||
  101. rule.AllowList.CommitAllowed(cs.commit.Hash.String()) {
  102. continue
  103. }
  104. if rule.File.String() != "" && !rule.HasFileLeak(filepath.Base(to.Path())) {
  105. continue
  106. }
  107. if rule.Path.String() != "" && !rule.HasFilePathLeak(to.Path()) {
  108. continue
  109. }
  110. leak := NewLeak(line, offender, defaultLineNumber).WithCommit(cs.commit)
  111. leak.File = to.Path()
  112. leak.LineNumber = extractLine(patchContent, leak, lineLookup)
  113. leak.RepoURL = cs.opts.RepoURL
  114. leak.Repo = cs.repoName
  115. leak.LeakURL = leak.URL()
  116. leak.Rule = rule.Description
  117. leak.Tags = strings.Join(rule.Tags, ", ")
  118. if cs.opts.Verbose {
  119. leak.Log(cs.opts.Redact)
  120. }
  121. scannerReport.Leaks = append(scannerReport.Leaks, leak)
  122. }
  123. }
  124. }
  125. }
  126. }
  127. scannerReport.Commits = 1
  128. return scannerReport, nil
  129. }