Repository: gitleaks/gitleaks (go)

Issue:
ExternalSecret secretStoreRef SecretStore wrongly detected as Kubernetes Secret in v8.21.2
**Describe the bug**
Finding: **kind: SecretStore** where it should not because it is not a **kind: Secret** with v8.21.2 toml rules config.

**To Reproduce**
Steps to reproduce the behavior:
create a gitleaks-secretstore.yaml file with the following content and commit into a git repo :

```
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: example
  namespace: example-ns
spec:
  provider:
    vault:
      server: "https://vault.acme.org"
      # Path is the mount path of the Vault KV backend endpoint
      # Used as a path prefix for the external secret key
      path: "secret"
      # Version is the Vault KV secret engine version.
      # This can be either "v1" or "v2", defaults to "v2"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "demo"
          # Optional service account reference
          serviceAccountRef:
            name: "my-sa"
          # Optional secret field containing a Kubernetes ServiceAccount JWT
          # used for authenticating with Vault
          secretRef:
            name: "my-secret"
            key: "vault"

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: example
  namespace: example-ns
spec:
  refreshInterval: 15s
  secretStoreRef:
    name: example
    kind: SecretStore
  target:
    name: mysecret
    creationPolicy: Owner
  data:
    - remoteRef:
        conversionStrategy: Default
        decodingStrategy: None
        key: secret/mysecret
        property: foo
      secretKey: foo
```

and run gitleaks :

```
gitleaks git --verbose -c ~/.gitleaks/gitleaks-v8.21.2.toml .

    ○
    │╲
    │ ○
    ○ ░
    ░    gitleaks

Finding:     kind: SecretStore
  target:
    name: mysecret
    creationPolicy: Owner
  data:
    - remoteRef:
        conversionStrategy: Default
        decodingStrategy: None
                                   key: secret/mysecret
Secret:      key: secret/mysecret
RuleID:      kubernetes-secret-yaml
Entropy:     3.241446
File:        gitleaks-secretstore.yaml
Line:        40
Commit:      6547d83e81d243deab224c3c2378f56a997c61d7
Author:      kwxd8412
Email:       daniel.viard@orange.com
Date:        2024-11-18T14:10:39Z
Fingerprint: 6547d83e81d243deab224c3c2378f56a997c61d7:gitleaks-secretstore.yaml:kubernetes-secret-yaml:40
```

**Expected behavior**
No leaks detected

**Screenshots**
not applicable

**Basic Info (please complete the following information):**
 - OS:
 - Gitleaks Version: v8.21.2

**Additional context**
Related to https://github.com/gitleaks/gitleaks/pull/1586

cc @zricethezav


Pull request description:
Fix Kubernetes rule matching `kind: SecretStore`
### Description:
This fixes #1644.

### Checklist:

* [x] Does your PR pass tests?
* [x] Have you written new tests for your changes?
* [x] Have you lint your code locally prior to submission?


Patch:
diff --git a/cmd/detect.go b/cmd/detect.go
index 3287981..db9c18b 100644
--- a/cmd/detect.go
+++ b/cmd/detect.go
@@ -62,6 +62,10 @@ func runDetect(cmd *cobra.Command, args []string) {
 
 	detector := Detector(cmd, cfg, source)
 
+	// set follow symlinks flag
+	if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
 	// set exit code
 	exitCode, err := cmd.Flags().GetInt("exit-code")
 	if err != nil {
@@ -83,7 +87,12 @@ func runDetect(cmd *cobra.Command, args []string) {
 	// start the detector scan
 	if noGit {
 		var paths <-chan sources.ScanTarget
-		paths, err = sources.DirectoryTargets(source, detector.Sema, detector.FollowSymlinks)
+		paths, err = sources.DirectoryTargets(
+			source,
+			detector.Sema,
+			detector.FollowSymlinks,
+			detector.Config.Allowlist.PathAllowed,
+		)
 		if err != nil {
 			log.Fatal().Err(err)
 		}
@@ -120,10 +129,5 @@ func runDetect(cmd *cobra.Command, args []string) {
 		}
 	}
 
-	// set follow symlinks flag
-	if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-
 	findingSummaryAndExit(findings, cmd, cfg, exitCode, start, err)
 }
diff --git a/cmd/directory.go b/cmd/directory.go
index 656f867..a4289bd 100644
--- a/cmd/directory.go
+++ b/cmd/directory.go
@@ -45,6 +45,10 @@ func runDirectory(cmd *cobra.Command, args []string) {
 
 	detector := Detector(cmd, cfg, source)
 
+	// set follow symlinks flag
+	if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
 	// set exit code
 	exitCode, err := cmd.Flags().GetInt("exit-code")
 	if err != nil {
@@ -52,7 +56,12 @@ func runDirectory(cmd *cobra.Command, args []string) {
 	}
 
 	var paths <-chan sources.ScanTarget
-	paths, err = sources.DirectoryTargets(source, detector.Sema, detector.FollowSymlinks)
+	paths, err = sources.DirectoryTargets(
+		source,
+		detector.Sema,
+		detector.FollowSymlinks,
+		detector.Config.Allowlist.PathAllowed,
+	)
 	if err != nil {
 		log.Fatal().Err(err)
 	}
diff --git a/cmd/generate/config/base/config.go b/cmd/generate/config/base/config.go
index 6ca5dae..60d3e6c 100644
--- a/cmd/generate/config/base/config.go
+++ b/cmd/generate/config/base/config.go
@@ -64,7 +64,7 @@ func CreateGlobalConfig() config.Config {
 				// ----------- Golang files -----------
 				regexp.MustCompile(`go\.(mod|sum|work(\.sum)?)$`),
 				regexp.MustCompile(`(^|/)vendor/modules\.txt$`),
-				regexp.MustCompile(`(^|/)vendor/(github\.com|golang\.org/x|google\.golang\.org|gopkg\.in|istio\.io|k8s\.io|sigs\.k8s\.io)/.*$`),
+				regexp.MustCompile(`(^|/)vendor/(github\.com|golang\.org/x|google\.golang\.org|gopkg\.in|istio\.io|k8s\.io|sigs\.k8s\.io)(/.*)?$`),
 
 				// ----------- Java files -----------
 				// Gradle
@@ -75,12 +75,9 @@ func CreateGlobalConfig() config.Config {
 
 				// ----------- JavaScript files -----------
 				// Dependencies and lock files.
-				regexp.MustCompile(`(^|/)node_modules/.*?$`),
-				regexp.MustCompile(`(^|/)package-lock\.json$`),
-				regexp.MustCompile(`(^|/)yarn\.lock$`),
-				regexp.MustCompile(`(^|/)pnpm-lock\.yaml$`),
-				regexp.MustCompile(`(^|/)npm-shrinkwrap\.json$`),
-				regexp.MustCompile(`(^|/)bower_components/.*?$`),
+				regexp.MustCompile(`(^|/)node_modules(/.*)?$`),
+				regexp.MustCompile(`(^|/)(npm-shrinkwrap\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$`),
+				regexp.MustCompile(`(^|/)bower_components(/.*)?$`),
 				// TODO: Add more common static assets, such as swagger-ui.
 				regexp.MustCompile(`(^|/)(angular|jquery(-?ui)?|plotly|swagger-?ui)[a-zA-Z0-9.-]*(\.min)?\.js(\.map)?$`),
 
@@ -88,17 +85,17 @@ func CreateGlobalConfig() config.Config {
 				// Dependencies and lock files.
 				regexp.MustCompile(`(^|/)(Pipfile|poetry)\.lock$`),
 				// Virtual environments
-				regexp.MustCompile(`(?i)/?(v?env|virtualenv)/lib(64)?/.+$`),
-				regexp.MustCompile(`(?i)(^|/)(lib(64)?/python[23](\.\d{1,2})+/|python/[23](\.\d{1,2})+/lib(64)?/).+$`),
+				regexp.MustCompile(`(?i)/?(v?env|virtualenv)/lib(64)?(/.*)?$`),
+				regexp.MustCompile(`(?i)(^|/)(lib(64)?/python[23](\.\d{1,2})+|python/[23](\.\d{1,2})+/lib(64)?)(/.*)?$`),
 				// dist-info directory (https://py-pkgs.org/04-package-structure.html#building-sdists-and-wheels)
-				regexp.MustCompile(`(?i)(^|/)[a-z0-9_.]+-[0-9.]+\.dist-info/.+$`),
+				regexp.MustCompile(`(?i)(^|/)[a-z0-9_.]+-[0-9.]+\.dist-info(/.+)?$`),
 
 				// ----------- Ruby files -----------
-				regexp.MustCompile(`(^|/)vendor/(bundle|ruby)/.*?$`),
+				regexp.MustCompile(`(^|/)vendor/(bundle|ruby)(/.*?)?$`),
 				regexp.MustCompile(`\.gem$`), // tar archive
 
 				// Misc
-				regexp.MustCompile(`verification-metadata.xml`),
+				regexp.MustCompile(`verification-metadata\.xml`),
 				regexp.MustCompile(`Database.refactorlog`),
 				//regexp.MustCompile(`vendor`),
 			},
diff --git a/cmd/generate/config/rules/kubernetes.go b/cmd/generate/config/rules/kubernetes.go
index ab90e2a..a1f3eab 100644
--- a/cmd/generate/config/rules/kubernetes.go
+++ b/cmd/generate/config/rules/kubernetes.go
@@ -12,7 +12,7 @@ import (
 func KubernetesSecret() *config.Rule {
 	// Only match basic variations of `kind: secret`, we don't want things like `kind: ExternalSecret`.
 	//language=regexp
-	kindPat := `\bkind:[ \t]*["']?secret["']?`
+	kindPat := `\bkind:[ \t]*["']?\bsecret\b["']?`
 	// Only matches values (`key: value`) under `data:` that are:
 	// - valid base64 characters
 	// - longer than 10 characters (no "YmFyCg==")
@@ -39,6 +39,9 @@ func KubernetesSecret() *config.Rule {
 					// variable: ""
 					// variable: ''
 					regexp.MustCompile(`[\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:\{\{[ \t\w"|$:=,.-]+}}|""|'')`),
+					// TODO: Requires multiple allowlists to target match.
+					// Avoid overreach between directives.
+					//regexp.MustCompile(`(kind:(.|\s)+\n---\n(.|\s)+\bdata:|data:(.|\s)+\n---\n(.|\s)+\bkind:)`),
 				},
 			},
 		},
@@ -239,6 +242,43 @@ metadata:
   name: kubernetes-dashboard-key-holder
   namespace: kubernetes-dashboard
 type: Opaque
+`,
+		// TODO: Requires multiple allowlists.
+		//		"overly-permissive3.yaml": ` kind: Secret
+		//  target:
+		//    name: mysecret
+		//    creationPolicy: Owner
+		//
+		//---
+		//
+		//kind: ConfigMap
+		//  data:
+		//        conversionStrategy: Default
+		//        decodingStrategy: None
+		//        key: secret/mysecret
+		//        property: foo
+		//      secretKey: foo`,
+		// https://github.com/gitleaks/gitleaks/issues/1644
+		"wrong-kind.yaml": `apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: example
+  namespace: example-ns
+spec:
+  refreshInterval: 15s
+  secretStoreRef:
+    name: example
+    kind: SecretStore
+  target:
+    name: mysecret
+    creationPolicy: Owner
+  data:
+    - remoteRef:
+        conversionStrategy: Default
+        decodingStrategy: None
+        key: secret/mysecret
+        property: foo
+      secretKey: foo
 `,
 		"sopssecret.yaml": `apiVersion: isindir.github.com/v1alpha3
 kind: SopsSecret
diff --git a/cmd/root.go b/cmd/root.go
index 27ed2a0..47066d7 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -283,8 +283,17 @@ func findingSummaryAndExit(findings []report.Finding, cmd *cobra.Command, cfg co
 	// write report if desired
 	reportPath, _ := cmd.Flags().GetString("report-path")
 	ext, _ := cmd.Flags().GetString("report-format")
+
 	if reportPath != "" {
-		if err := report.Write(findings, cfg, ext, reportPath); err != nil {
+		reportWriter := os.Stdout
+		if reportPath != "-" {
+			reportWriter, err = os.Create(reportPath)
+			if err != nil {
+				log.Fatal().Err(err).Msg("could not create report file")
+			}
+		}
+
+		if err = report.Write(findings, cfg, ext, reportWriter); err != nil {
 			log.Fatal().Err(err).Msg("could not write")
 		}
 	}
diff --git a/config/gitleaks.toml b/config/gitleaks.toml
index 09d6c8f..4466cdf 100644
--- a/config/gitleaks.toml
+++ b/config/gitleaks.toml
@@ -32,25 +32,22 @@ paths = [
     '''(.*?)(doc|docx|zip|xls|pdf|bin|socket|vsidx|v2|suo|wsuo|.dll|pdb|exe|gltf)$''',
     '''go\.(mod|sum|work(\.sum)?)$''',
     '''(^|/)vendor/modules\.txt$''',
-    '''(^|/)vendor/(github\.com|golang\.org/x|google\.golang\.org|gopkg\.in|istio\.io|k8s\.io|sigs\.k8s\.io)/.*$''',
+    '''(^|/)vendor/(github\.com|golang\.org/x|google\.golang\.org|gopkg\.in|istio\.io|k8s\.io|sigs\.k8s\.io)(/.*)?$''',
     '''(^|/)gradlew(\.bat)?$''',
     '''(^|/)gradle\.lockfile$''',
     '''(^|/)mvnw(\.cmd)?$''',
     '''(^|/)\.mvn/wrapper/MavenWrapperDownloader\.java$''',
-    '''(^|/)node_modules/.*?$''',
-    '''(^|/)package-lock\.json$''',
-    '''(^|/)yarn\.lock$''',
-    '''(^|/)pnpm-lock\.yaml$''',
-    '''(^|/)npm-shrinkwrap\.json$''',
-    '''(^|/)bower_components/.*?$''',
+    '''(^|/)node_modules(/.*)?$''',
+    '''(^|/)(npm-shrinkwrap\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$''',
+    '''(^|/)bower_components(/.*)?$''',
     '''(^|/)(angular|jquery(-?ui)?|plotly|swagger-?ui)[a-zA-Z0-9.-]*(\.min)?\.js(\.map)?$''',
     '''(^|/)(Pipfile|poetry)\.lock$''',
-    '''(?i)/?(v?env|virtualenv)/lib(64)?/.+$''',
-    '''(?i)(^|/)(lib(64)?/python[23](\.\d{1,2})+/|python/[23](\.\d{1,2})+/lib(64)?/).+$''',
-    '''(?i)(^|/)[a-z0-9_.]+-[0-9.]+\.dist-info/.+$''',
-    '''(^|/)vendor/(bundle|ruby)/.*?$''',
+    '''(?i)/?(v?env|virtualenv)/lib(64)?(/.*)?$''',
+    '''(?i)(^|/)(lib(64)?/python[23](\.\d{1,2})+|python/[23](\.\d{1,2})+/lib(64)?)(/.*)?$''',
+    '''(?i)(^|/)[a-z0-9_.]+-[0-9.]+\.dist-info(/.+)?$''',
+    '''(^|/)vendor/(bundle|ruby)(/.*?)?$''',
     '''\.gem$''',
-    '''verification-metadata.xml''',
+    '''verification-metadata\.xml''',
     '''Database.refactorlog''',
 ]
 stopwords = [
@@ -2330,7 +2327,7 @@ keywords = ["kraken"]
 [[rules]]
 id = "kubernetes-secret-yaml"
 description = "Possible Kubernetes Secret detected, posing a risk of leaking credentials/tokens from your deployments"
-regex = '''(?i)(?:\bkind:[ \t]*["']?secret["']?(?:.|\s){0,200}?\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:["']?[a-z0-9+/]{10,}={0,3}["']?|\{\{[ \t\w"|$:=,.-]+}}|""|''))|\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:["']?[a-z0-9+/]{10,}={0,3}["']?|\{\{[ \t\w"|$:=,.-]+}}|""|''))(?:.|\s){0,200}?\bkind:[ \t]*["']?secret["']?)'''
+regex = '''(?i)(?:\bkind:[ \t]*["']?\bsecret\b["']?(?:.|\s){0,200}?\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:["']?[a-z0-9+/]{10,}={0,3}["']?|\{\{[ \t\w"|$:=,.-]+}}|""|''))|\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:["']?[a-z0-9+/]{10,}={0,3}["']?|\{\{[ \t\w"|$:=,.-]+}}|""|''))(?:.|\s){0,200}?\bkind:[ \t]*["']?\bsecret\b["']?)'''
 path = '''(?i)\.ya?ml$'''
 keywords = ["secret"]
 # NOTE: Gitleaks >= v8.21.0 should use [[rules.allowlists] instead.
diff --git a/detect/detect.go b/detect/detect.go
index 0128f84..8a410d2 100644
--- a/detect/detect.go
+++ b/detect/detect.go
@@ -22,7 +22,7 @@ import (
 
 const (
 	gitleaksAllowSignature = "gitleaks:allow"
-	chunkSize              = 10 * 1_000 // 10kb
+	chunkSize              = 100 * 1_000 // 100kb
 )
 
 var newLineRegexp = regexp.MustCompile("\n")
diff --git a/detect/directory.go b/detect/directory.go
index be9410d..7a832fc 100644
--- a/detect/directory.go
+++ b/detect/directory.go
@@ -16,6 +16,7 @@ func (d *Detector) DetectFiles(paths <-chan sources.ScanTarget) ([]report.Findin
 		d.Sema.Go(func() error {
 			logger := log.With().Str("path", pa.Path).Logger()
 			logger.Trace().Msg("Scanning path")
+
 			f, err := os.Open(pa.Path)
 			if err != nil {
 				if os.IsPermission(err) {
@@ -24,7 +25,9 @@ func (d *Detector) DetectFiles(paths <-chan sources.ScanTarget) ([]report.Findin
 				}
 				return err
 			}
-			defer f.Close()
+			defer func() {
+				_ = f.Close()
+			}()
 
 			// Get file size
 			fileInfo, err := f.Stat()
@@ -37,7 +40,7 @@ func (d *Detector) DetectFiles(paths <-chan sources.ScanTarget) ([]report.Findin
 				if rawLength > int64(d.MaxTargetMegaBytes) {
 					logger.Debug().
 						Int64("size", rawLength).
-						Msgf("Skipping file: exceeds --max-target-megabytes")
+						Msg("Skipping file: exceeds --max-target-megabytes")
 					return nil
 				}
 			}
diff --git a/report/report.go b/report/report.go
index 81b6ceb..77b34d6 100644
--- a/report/report.go
+++ b/report/report.go
@@ -1,7 +1,7 @@
 package report
 
 import (
-	"os"
+	"io"
 	"strings"
 
 	"github.com/zricethezav/gitleaks/v8/config"
@@ -13,23 +13,20 @@ const (
 	CWE_DESCRIPTION = "Use of Hard-coded Credentials"
 )
 
-func Write(findings []Finding, cfg config.Config, ext string, reportPath string) error {
-	file, err := os.Create(reportPath)
-	if err != nil {
-		return err
-	}
+func Write(findings []Finding, cfg config.Config, ext string, report io.WriteCloser) error {
+	var err error
 	ext = strings.ToLower(ext)
 	switch ext {
 	case ".json", "json":
-		err = writeJson(findings, file)
+		err = writeJson(findings, report)
 	case ".jsonextra", "jsonextra":
-		err = writeJsonExtra(findings, file)
+		err = writeJsonExtra(findings, report)
 	case ".csv", "csv":
-		err = writeCsv(findings, file)
+		err = writeCsv(findings, report)
 	case ".xml", "junit":
-		err = writeJunit(findings, file)
+		err = writeJunit(findings, report)
 	case ".sarif", "sarif":
-		err = writeSarif(cfg, findings, file)
+		err = writeSarif(cfg, findings, report)
 	}
 
 	return err
diff --git a/sources/directory.go b/sources/directory.go
index 1d1a52d..a90e3d3 100644
--- a/sources/directory.go
+++ b/sources/directory.go
@@ -1,12 +1,12 @@
 package sources
 
 import (
-	"github.com/rs/zerolog/log"
 	"io/fs"
 	"os"
 	"path/filepath"
 
 	"github.com/fatih/semgroup"
+	"github.com/rs/zerolog/log"
 )
 
 type ScanTarget struct {
@@ -14,13 +14,14 @@ type ScanTarget struct {
 	Symlink string
 }
 
-func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool) (<-chan ScanTarget, error) {
+func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool, shouldSkip func(string) bool) (<-chan ScanTarget, error) {
 	paths := make(chan ScanTarget)
 	s.Go(func() error {
 		defer close(paths)
 		return filepath.Walk(source,
 			func(path string, fInfo os.FileInfo, err error) error {
 				logger := log.With().Str("path", path).Logger()
+
 				if err != nil {
 					if os.IsPermission(err) {
 						// This seems to only fail on directories at this stage.
@@ -30,21 +31,18 @@ func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool) (<-
 					return err
 				}
 
-				if fInfo.Name() == ".git" && fInfo.IsDir() {
-					return filepath.SkipDir
-				}
+				// Empty; nothing to do here.
 				if fInfo.Size() == 0 {
 					return nil
 				}
-				if fInfo.Mode().IsRegular() {
-					paths <- ScanTarget{
-						Path:    path,
-						Symlink: "",
-					}
+
+				// Unwrap symlinks, if |followSymlinks| is set.
+				scanTarget := ScanTarget{
+					Path: path,
 				}
 				if fInfo.Mode().Type() == fs.ModeSymlink {
 					if !followSymlinks {
-						log.Debug().Str("path", path).Msg("Skipping symlink")
+						logger.Debug().Msg("Skipping symlink")
 						return nil
 					}
 
@@ -52,15 +50,39 @@ func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool) (<-
 					if err != nil {
 						return err
 					}
+
 					realPathFileInfo, _ := os.Stat(realPath)
 					if realPathFileInfo.IsDir() {
-						log.Debug().Msgf("found symlinked directory: %s -> %s [skipping]", path, realPath)
+						logger.Warn().Str("target", realPath).Msg("Skipping symlinked directory")
 						return nil
 					}
-					paths <- ScanTarget{
-						Path:    realPath,
-						Symlink: path,
+
+					scanTarget.Path = realPath
+					scanTarget.Symlink = path
+				}
+
+				// TODO: Also run this check against the resolved symlink?
+				skip := shouldSkip(path)
+				if fInfo.IsDir() {
+					// Directory
+					if skip {
+						logger.Debug().Msg("Skipping directory due to global allowlist")
+						return filepath.SkipDir
+					}
+
+					if fInfo.Name() == ".git" {
+						// Don't scan .git directories.
+						// TODO: Add this to the config allowlist, instead of hard-coding it.
+						return filepath.SkipDir
+					}
+				} else {
+					// File
+					if skip {
+						logger.Debug().Msg("Skipping file due to global allowlist")
+						return nil
 					}
+
+					paths <- scanTarget
 				}
 				return nil
 			})


Test patch:
diff --git a/detect/detect_test.go b/detect/detect_test.go
index 33b2ef9..e2d7611 100644
--- a/detect/detect_test.go
+++ b/detect/detect_test.go
@@ -789,7 +789,7 @@ func TestFromFiles(t *testing.T) {
 		err = detector.AddGitleaksIgnore(ignorePath)
 		require.NoError(t, err)
 		detector.FollowSymlinks = true
-		paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true)
+		paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true, cfg.Allowlist.PathAllowed)
 		require.NoError(t, err)
 		findings, err := detector.DetectFiles(paths)
 		require.NoError(t, err)
@@ -840,7 +840,7 @@ func TestDetectWithSymlinks(t *testing.T) {
 		cfg, _ := vc.Translate()
 		detector := NewDetector(cfg)
 		detector.FollowSymlinks = true
-		paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true)
+		paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true, cfg.Allowlist.PathAllowed)
 		require.NoError(t, err)
 		findings, err := detector.DetectFiles(paths)
 		require.NoError(t, err)
diff --git a/report/report_test.go b/report/report_test.go
index 516fa72..78de056 100644
--- a/report/report_test.go
+++ b/report/report_test.go
@@ -1,9 +1,7 @@
 package report
 
 import (
-	"os"
-	"path/filepath"
-	"strconv"
+	"bytes"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -97,15 +95,14 @@ func TestReport(t *testing.T) {
 		// },
 	}
 
-	for i, test := range tests {
+	for _, test := range tests {
 		t.Run(test.ext, func(t *testing.T) {
-			tmpfile, err := os.Create(filepath.Join(t.TempDir(), strconv.Itoa(i)+test.ext))
-			require.NoError(t, err)
-			err = Write(test.findings, config.Config{}, test.ext, tmpfile.Name())
-			require.NoError(t, err)
-			got, err := os.ReadFile(tmpfile.Name())
+			buf := testWriter{
+				bytes.NewBuffer(nil),
+			}
+			err := Write(test.findings, config.Config{}, test.ext, buf)
 			require.NoError(t, err)
-			assert.FileExists(t, tmpfile.Name())
+			got := buf.Bytes()
 			if test.wantEmpty {
 				assert.Empty(t, got)
 				return
@@ -114,3 +111,11 @@ func TestReport(t *testing.T) {
 		})
 	}
 }
+
+type testWriter struct {
+	*bytes.Buffer
+}
+
+func (t testWriter) Close() error {
+	return nil
+}