Skip to content

Commit 1ac9b1f

Browse files
authored
fix(repo): sanitize git repo URL before inserting into report metadata (#9391)
1 parent 6fa3849 commit 1ac9b1f

File tree

3 files changed

+81
-6
lines changed

3 files changed

+81
-6
lines changed

pkg/fanal/artifact/local/fs.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"crypto/sha256"
77
"errors"
88
"io/fs"
9+
"net/url"
910
"os"
1011
"path"
1112
"path/filepath"
@@ -154,7 +155,7 @@ func extractGitInfo(dir string) (bool, artifact.RepoMetadata, error) {
154155
remoteConfig, err = repo.Remote("origin")
155156
}
156157
if err == nil && len(remoteConfig.Config().URLs) > 0 {
157-
metadata.RepoURL = remoteConfig.Config().URLs[0]
158+
metadata.RepoURL = sanitizeRemoteURL(remoteConfig.Config().URLs[0])
158159
}
159160

160161
// Check if repository is clean for caching purposes
@@ -361,3 +362,20 @@ func (a Artifact) calcCacheKey() (string, error) {
361362
d := digest.NewDigest(digest.SHA256, h)
362363
return d.String(), nil
363364
}
365+
366+
// sanitizeRemoteURL removes credentials (userinfo) from URLs.
367+
func sanitizeRemoteURL(gitUrl string) string {
368+
// Only attempt sanitization for URLs with an explicit scheme.
369+
if !strings.Contains(gitUrl, "://") {
370+
return gitUrl
371+
}
372+
373+
// Try URL parsing first.
374+
if u, err := url.Parse(gitUrl); err == nil {
375+
// Clear userinfo (username:password)
376+
u.User = nil
377+
gitUrl = u.String()
378+
}
379+
380+
return gitUrl
381+
}

pkg/fanal/artifact/local/fs_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,3 +2531,60 @@ func TestArtifact_AnalysisStrategy(t *testing.T) {
25312531
})
25322532
}
25332533
}
2534+
2535+
func Test_sanitizeRemoteURL(t *testing.T) {
2536+
tests := []struct {
2537+
name string
2538+
in string
2539+
want string
2540+
}{
2541+
{
2542+
name: "https with user:pass",
2543+
in: "https://user:[email protected]/org/repo.git",
2544+
want: "https://github.com/org/repo.git",
2545+
},
2546+
{
2547+
name: "port in authority with userinfo",
2548+
in: "https://user:pass@host:8443/repo.git",
2549+
want: "https://host:8443/repo.git",
2550+
},
2551+
{
2552+
name: "http with username only",
2553+
in: "http://[email protected]/org/repo",
2554+
want: "http://github.com/org/repo",
2555+
},
2556+
{
2557+
name: "double scheme after userinfo",
2558+
in: "https://gitlab-ci-token:[email protected]/gitrepo.git",
2559+
want: "https://example.com/gitrepo.git",
2560+
},
2561+
{
2562+
name: "ssh scheme with username",
2563+
in: "ssh://[email protected]/org/repo.git",
2564+
want: "ssh://github.com/org/repo.git",
2565+
},
2566+
{
2567+
name: "scp-like ssh unchanged",
2568+
in: "[email protected]:org/repo.git",
2569+
want: "[email protected]:org/repo.git",
2570+
},
2571+
{
2572+
name: "already clean https",
2573+
in: "https://github.com/org/repo.git",
2574+
want: "https://github.com/org/repo.git",
2575+
},
2576+
{
2577+
name: "no scheme left as-is",
2578+
in: "github.com/org/repo.git",
2579+
want: "github.com/org/repo.git",
2580+
},
2581+
}
2582+
2583+
for _, tt := range tests {
2584+
t.Run(tt.name, func(t *testing.T) {
2585+
t.Parallel()
2586+
got := sanitizeRemoteURL(tt.in)
2587+
assert.Equal(t, tt.want, got)
2588+
})
2589+
}
2590+
}

pkg/fanal/artifact/repo/git_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
458458
{
459459
name: "success with embedded credentials",
460460
target: makeTarget(testUsername, testPassword),
461-
wantRepoURL: makeTarget(testUsername, testPassword), // TODO: username/password should be stripped
461+
wantRepoURL: tsURL.String(),
462462
},
463463
{
464464
name: "failure with wrong password",
@@ -486,8 +486,9 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
486486
tsURL := setupAuthTestServer(t, testUsername, testPassword)
487487

488488
// Add credentials to URL
489-
tsURL.User = url.UserPassword(testUsername, testPassword)
490-
targetWithCreds := tsURL.String()
489+
u := *tsURL // Copy the URL
490+
u.User = url.UserPassword(testUsername, testPassword)
491+
targetWithCreds := u.String()
491492

492493
// Clone the repository with URL-embedded credentials
493494
cloneDir := filepath.Join(t.TempDir(), "cloned-repo")
@@ -499,8 +500,7 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
499500
require.NoError(t, err)
500501

501502
// Scan and verify the local cloned directory
502-
// TODO: The credentials in the URL should be stripped in the RepoURL
503-
testInspectArtifact(t, cloneDir, targetWithCreds, "")
503+
testInspectArtifact(t, cloneDir, tsURL.String(), "")
504504
})
505505
}
506506

0 commit comments

Comments
 (0)