directory.go 2.6 KB

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