directory.go 2.5 KB

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