Skip to content

Commit 9499324

Browse files
committed
fix(git): detect secrets in renamed/copied files
When a file is renamed using `git mv` or when git detects a 100% copy, the `git log --patch` output shows only: ``` similarity index 100% rename from fileA.txt rename to fileB.txt ``` Without any actual content diff, causing the scanner to miss secrets in the renamed file. This fix adds `--no-renames` to the git log and git diff commands, which disables git's rename detection. This causes git to treat renames as a delete + add operation, ensuring the full file content is shown for newly created files. Fixes #4672 ## Changes - Add `--no-renames` flag to `RepoPath()` in gitparse.go - Add `--no-renames` flag to `Staged()` in gitparse.go - Add regression test `TestRenamedFileContainsSecret` ## Testing Created a test repository with: 1. Initial file with AWS credentials 2. Renamed file using `git mv` Before fix: Secret only reported in original file (now deleted) After fix: Secret correctly reported in renamed file
1 parent 94fdf01 commit 9499324

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

pkg/gitparse/gitparse.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,12 @@ func (c *Parser) RepoPath(
234234
args := []string{
235235
"-C", source,
236236
"log",
237-
"--patch", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---patch
237+
"--patch", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---patch
238238
"--full-history",
239239
"--date=iso-strict",
240240
"--pretty=fuller", // https://git-scm.com/docs/git-log#_pretty_formats
241241
"--notes", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---notesltrefgt
242+
"--no-renames", // Disable rename detection to ensure renamed/copied files show full content
242243
}
243244
if abbreviatedLog {
244245
args = append(args, "--diff-filter=AM")
@@ -279,7 +280,8 @@ func (c *Parser) RepoPath(
279280
// Staged parses the output of the `git diff` command for the `source` path.
280281
func (c *Parser) Staged(ctx context.Context, source string) (chan *Diff, error) {
281282
// Provide the --cached flag to diff to get the diff of the staged changes.
282-
args := []string{"-C", source, "diff", "-p", "--cached", "--full-history", "--diff-filter=AM", "--date=iso-strict"}
283+
// --no-renames ensures renamed/copied files show full content instead of similarity index.
284+
args := []string{"-C", source, "diff", "-p", "--cached", "--full-history", "--diff-filter=AM", "--no-renames", "--date=iso-strict"}
283285

284286
cmd := exec.Command("git", args...)
285287

pkg/sources/git/git_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,3 +1163,56 @@ func TestPrepareRepoWithNormalizationBare(t *testing.T) {
11631163
})
11641164
}
11651165
}
1166+
1167+
// TestRenamedFileContainsSecret ensures that when a file is renamed (git mv),
1168+
// the scanner still detects secrets in the renamed file.
1169+
// This is a regression test for: https://github.com/trufflesecurity/trufflehog/issues/4672
1170+
func TestRenamedFileContainsSecret(t *testing.T) {
1171+
t.Parallel()
1172+
1173+
// Create a test repo with a secret
1174+
repoPath := setupTestRepo(t, "rename-test-repo")
1175+
1176+
// Create initial file with a secret
1177+
secret := "AKIA1234567890ABCDEF"
1178+
initialContent := fmt.Sprintf("aws_access_key_id = %s\naws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", secret)
1179+
addTestFileAndCommit(t, repoPath, "credentials.txt", initialContent)
1180+
1181+
// Rename the file using git mv
1182+
assert.NoError(t, exec.Command("git", "-C", repoPath, "mv", "credentials.txt", "credentials_backup.txt").Run())
1183+
assert.NoError(t, exec.Command("git", "-C", repoPath, "commit", "-m", "Rename credentials file").Run())
1184+
1185+
// Scan the repo
1186+
ctx := context.Background()
1187+
s := Source{}
1188+
conn, err := anypb.New(&sourcespb.Git{
1189+
Directories: []string{repoPath},
1190+
Credential: &sourcespb.Git_Unauthenticated{Unauthenticated: &credentialspb.Unauthenticated{}},
1191+
})
1192+
assert.NoError(t, err)
1193+
1194+
err = s.Init(ctx, "test", 0, 0, false, conn, 1)
1195+
assert.NoError(t, err)
1196+
1197+
chunksChan := make(chan *sources.Chunk, 100)
1198+
go func() {
1199+
defer close(chunksChan)
1200+
err := s.Chunks(ctx, chunksChan)
1201+
assert.NoError(t, err)
1202+
}()
1203+
1204+
// Collect all chunks and verify we find the secret in the renamed file
1205+
foundInRenamedFile := false
1206+
for chunk := range chunksChan {
1207+
if chunk.SourceMetadata != nil {
1208+
metadata := chunk.SourceMetadata.GetGit()
1209+
if metadata != nil && strings.Contains(metadata.File, "credentials_backup.txt") {
1210+
if bytes.Contains(chunk.Data, []byte(secret)) {
1211+
foundInRenamedFile = true
1212+
}
1213+
}
1214+
}
1215+
}
1216+
1217+
assert.True(t, foundInRenamedFile, "Secret should be detected in the renamed file (credentials_backup.txt)")
1218+
}

0 commit comments

Comments
 (0)