Repository: gitleaks/gitleaks (go)

Issue:
Gitleaks precommit staged still scans the whole git history
**Describe the bug**
Gitleaks precommit staged still scans the whole git history.
When running gitleaks in a pre-commit hook, the new options
`gitleaks git --pre-commit --staged ` scans and reports leaks on the whole git history.

**To Reproduce**
Run `gitleaks git --pre-commit --staged` in a project this, will always scan the git history and ignore the staged files.

**Expected behavior**
Running `gitleaks git --pre-commit --staged` in a project should only scan staged files, not the whole history

**Basic Info (please complete the following information):**
 - OS: Ubuntu 22.04.5 LTS
 - Gitleaks Version: 8.19.2

cc @zricethezav


Pull request:
Fix behaviour with --pre-commit and --staged
### Description:
This should fix #1522 and #1532, pending some testing + feedback.

(I never used `protect` so I don't know what the expectations are.)

### Checklist:

* [ ] Does your PR pass tests?
* [ ] Have you written new tests for your changes?
* [ ] Have you lint your code locally prior to submission?


Patch:
diff --git a/cmd/git.go b/cmd/git.go
index af0623b..a18585a 100644
--- a/cmd/git.go
+++ b/cmd/git.go
@@ -57,17 +57,36 @@ func runGit(cmd *cobra.Command, args []string) {
 	}
 
 	var (
-		gitCmd  *sources.GitCmd
-		logOpts string
+		gitCmd    *sources.GitCmd
+		logOpts   string
+		preCommit bool
+		staged    bool
 	)
 	logOpts, err = cmd.Flags().GetString("log-opts")
 	if err != nil {
 		log.Fatal().Err(err).Msg("could not call GetString() for log-opts")
 	}
-	gitCmd, err = sources.NewGitLogCmd(source, logOpts)
+	staged, err = cmd.Flags().GetBool("staged")
 	if err != nil {
-		log.Fatal().Err(err).Msg("could not create Git cmd")
+		log.Fatal().Err(err).Msg("could not call GetBool() for staged")
 	}
+	preCommit, err = cmd.Flags().GetBool("pre-commit")
+	if err != nil {
+		log.Fatal().Err(err).Msg("could not call GetBool() for pre-commit")
+	}
+
+	if preCommit || staged {
+		gitCmd, err = sources.NewGitDiffCmd(source, staged)
+		if err != nil {
+			log.Fatal().Err(err).Msg("could not create Git diff cmd")
+		}
+	} else {
+		gitCmd, err = sources.NewGitLogCmd(source, logOpts)
+		if err != nil {
+			log.Fatal().Err(err).Msg("could not create Git log cmd")
+		}
+	}
+
 	findings, err = detector.DetectGit(gitCmd)
 	if err != nil {
 		// don't exit on error, just log it
diff --git a/detect/detect.go b/detect/detect.go
index 15cc870..110ebc0 100644
--- a/detect/detect.go
+++ b/detect/detect.go
@@ -265,6 +265,10 @@ func (d *Detector) detectRule(fragment Fragment, rule config.Rule) []report.Find
 		// extract secret from match
 		secret := strings.Trim(fragment.Raw[matchIndex[0]:matchIndex[1]], "\n")
 
+		// Fixes: https://github.com/gitleaks/gitleaks/issues/1352
+		// removes the incorrectly following line that was detected by regex expression '\n'
+		matchIndex[1] = matchIndex[0] + len(secret)
+
 		// determine location of match. Note that the location
 		// in the finding will be the line/column numbers of the _match_
 		// not the _secret_, which will be different if the secretGroup
diff --git a/detect/location.go b/detect/location.go
index 63b9538..ac19173 100644
--- a/detect/location.go
+++ b/detect/location.go
@@ -50,6 +50,7 @@ func location(fragment Fragment, matchIndex []int) Location {
 			location.endColumn = (end - prevNewLine)
 			location.endLineIndex = newLineByteIndex
 		}
+
 		prevNewLine = pair[0]
 	}
 


Test patch:
diff --git a/detect/detect_test.go b/detect/detect_test.go
index c787931..3aa75cf 100644
--- a/detect/detect_test.go
+++ b/detect/detect_test.go
@@ -603,11 +603,32 @@ func TestFromFiles(t *testing.T) {
 			cfgName:          "simple",
 			expectedFindings: []report.Finding{},
 		},
+		{
+			source:  filepath.Join(repoBasePath, "nogit", ".env.prod"),
+			cfgName: "generic",
+			expectedFindings: []report.Finding{
+				{
+					Description: "Generic API Key",
+					StartLine:   4,
+					EndLine:     4,
+					StartColumn: 5,
+					EndColumn:   35,
+					Match:       "PASSWORD=8ae31cacf141669ddfb5da",
+					Secret:      "8ae31cacf141669ddfb5da",
+					Line:        "\nDB_PASSWORD=8ae31cacf141669ddfb5da",
+					File:        "../testdata/repos/nogit/.env.prod",
+					RuleID:      "generic-api-key",
+					Tags:        []string{},
+					Entropy:     3.5383105,
+					Fingerprint: "../testdata/repos/nogit/.env.prod:generic-api-key:4",
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {
 		viper.AddConfigPath(configPath)
-		viper.SetConfigName("simple")
+		viper.SetConfigName(tt.cfgName)
 		viper.SetConfigType("toml")
 		err := viper.ReadInConfig()
 		require.NoError(t, err)
diff --git a/testdata/config/generic.toml b/testdata/config/generic.toml
index 625e44e..f4d1766 100644
--- a/testdata/config/generic.toml
+++ b/testdata/config/generic.toml
@@ -3,6 +3,8 @@ title = "gitleaks config"
 [[rules]]
 description = "Generic API Key"
 id = "generic-api-key"
-regex = '''(?i)((key|api|token|secret|password)[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9a-zA-Z\-_=]{8,64})['\"]'''
-entropy = 3.7
-secretGroup = 4
+regex = '''(?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-z\-_.=]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$)'''
+entropy = 3.5
+keywords = [
+    "key","api","token","secret","client","passwd","password","auth","access",
+]
\ No newline at end of file
diff --git a/testdata/repos/nogit/.env.prod b/testdata/repos/nogit/.env.prod
new file mode 100644
index 0000000..472c30e
--- /dev/null
+++ b/testdata/repos/nogit/.env.prod
@@ -0,0 +1,6 @@
+DB_HOST=example.com
+DB_PORT=443
+DB_USERNAME=postgres
+DB_PASSWORD=8ae31cacf141669ddfb5da
+DB_NAME=best_db
+DB_SSL=true
\ No newline at end of file