directory.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package sources
  2. import (
  3. "io/fs"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "github.com/fatih/semgroup"
  8. "github.com/zricethezav/gitleaks/v8/config"
  9. "github.com/zricethezav/gitleaks/v8/logging"
  10. )
  11. type ScanTarget struct {
  12. Path string
  13. Symlink string
  14. VirtualPath string
  15. Source string
  16. // The following items are only used for git archive scans
  17. // (since we have to treat them like regular dir scans)
  18. GitInfo GitInfo
  19. // TODO one day use a single Metadata map
  20. // instead of git fields
  21. // Metadata map[string]string
  22. }
  23. var isWindows = runtime.GOOS == "windows"
  24. func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool, allowlists []*config.Allowlist) (<-chan ScanTarget, error) {
  25. paths := make(chan ScanTarget)
  26. s.Go(func() error {
  27. defer close(paths)
  28. return filepath.Walk(source,
  29. func(path string, fInfo os.FileInfo, err error) error {
  30. logger := logging.With().Str("path", path).Logger()
  31. if err != nil {
  32. if os.IsPermission(err) {
  33. // This seems to only fail on directories at this stage.
  34. logger.Warn().Msg("Skipping directory: permission denied")
  35. return filepath.SkipDir
  36. }
  37. return err
  38. }
  39. // Empty; nothing to do here.
  40. if fInfo.Size() == 0 {
  41. return nil
  42. }
  43. // Unwrap symlinks, if |followSymlinks| is set.
  44. scanTarget := ScanTarget{
  45. Path: path,
  46. }
  47. if fInfo.Mode().Type() == fs.ModeSymlink {
  48. if !followSymlinks {
  49. logger.Debug().Msg("Skipping symlink")
  50. return nil
  51. }
  52. realPath, err := filepath.EvalSymlinks(path)
  53. if err != nil {
  54. return err
  55. }
  56. realPathFileInfo, _ := os.Stat(realPath)
  57. if realPathFileInfo.IsDir() {
  58. logger.Warn().Str("target", realPath).Msg("Skipping symlinked directory")
  59. return nil
  60. }
  61. scanTarget.Path = realPath
  62. scanTarget.Symlink = path
  63. }
  64. // TODO: Also run this check against the resolved symlink?
  65. var skip bool
  66. for _, a := range allowlists {
  67. skip = a.PathAllowed(path) ||
  68. // TODO: Remove this in v9.
  69. // This is an awkward hack to mitigate https://github.com/gitleaks/gitleaks/issues/1641.
  70. (isWindows && a.PathAllowed(filepath.ToSlash(path)))
  71. if skip {
  72. break
  73. }
  74. }
  75. if fInfo.IsDir() {
  76. // Directory
  77. if skip {
  78. logger.Debug().Msg("Skipping directory due to global allowlist")
  79. return filepath.SkipDir
  80. }
  81. if fInfo.Name() == ".git" {
  82. // Don't scan .git directories.
  83. // TODO: Add this to the config allowlist, instead of hard-coding it.
  84. return filepath.SkipDir
  85. }
  86. } else {
  87. // File
  88. if skip {
  89. logger.Debug().Msg("Skipping file due to global allowlist")
  90. return nil
  91. }
  92. paths <- scanTarget
  93. }
  94. return nil
  95. })
  96. })
  97. return paths, nil
  98. }