Skip to content
33 changes: 19 additions & 14 deletions pkg/detectors/gitlab/v1/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result

matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
resMatch := strings.TrimSpace(match[1])

// ignore v2 detectors which have a prefix of `glpat-`
if strings.Contains(match[0], "glpat-") {
continue
}
resMatch := strings.TrimSpace(match[1])

// to avoid false positives
if detectors.StringShannonEntropy(resMatch) < 3.6 {
continue
}

s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
ExtraData: map[string]string{},
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
"version": fmt.Sprintf("%d", s.Version()),
}
for _, endpoint := range s.Endpoints() {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),

@amanfcp amanfcp Aug 7, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a single data string, if we have 2 set of credentials and only 2nd endpoint is correct, it would not be possible to identify why one result is verified and the other is not.

Suggested change
Raw: []byte(resMatch),
Raw: []byte(resMatch),
RawV2: []byte(resMatch + endpoint),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered this change but hesitated, concerned it might duplicate existing results in the Enterprise.
I think @rosecodym can comment better on this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, you're right. Raw/RawV2 is like a primary key, and updating it even slightly can have impacts on the enterprise data, so let's not touch that. Instead, we can store the endpoint in the extra data.

@amanfcp amanfcp Aug 7, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could cause problems in diagnosing issues later on in enterprise too. This just might require migration.
Also, I'm positive that "adding" a RawV2 won't require migration
@rosecodym awaiting your response.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a RawV2 that wasn't there before is fine and won't affect the deduplication fingerprinting.
Changing Raw or RawV2 will cause issues though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dustin-decker for the clarification - I updated the code.

RawV2: []byte(resMatch + endpoint),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
"version": fmt.Sprintf("%d", s.Version()),
},
}

if verify {
for _, endpoint := range s.Endpoints() {
if verify {
isVerified, extraData, verificationErr := VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)
s1.Verified = isVerified
maps.Copy(s1.ExtraData, extraData)
Expand All @@ -102,11 +103,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
"key": resMatch,
"host": endpoint,
}

// if secret is verified with one endpoint, break the loop to continue to next secret
results = append(results, s1)
break
}
}
}

results = append(results, s1)
results = append(results, s1)
}
}

return results, nil
Expand Down
6 changes: 4 additions & 2 deletions pkg/detectors/gitlab/v1/gitlab_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ func TestGitlab_FromChunk(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.s.SetCloudEndpoint("https://gitlab.com")
tt.s.UseCloudEndpoint(true)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Gitlab.FromData() error = %v, wantErr %v", err, tt.wantErr)
Expand All @@ -189,7 +191,7 @@ func TestGitlab_FromChunk(t *testing.T) {
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
Expand Down Expand Up @@ -287,7 +289,7 @@ func TestGitlab_FromChunk_WithV2Secrets(t *testing.T) {
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
Expand Down
47 changes: 21 additions & 26 deletions pkg/detectors/gitlab/v1/gitlab_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,10 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)

var (
validPattern = `[{
"_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
"name": "Gitlab",
"type": "Detector",
"api": true,
"authentication_type": "",
"verification_url": "https://api.example.com/example",
"test_secrets": {
"gitlab_secret": "oXCt4JT2wf1_WlZl2OVG"
},
"docs":"https://docs.gitlab.com/test/api/example.json#get-drone-test-example-settings", // this matches the pattern but fail in entropy check
"expected_response": "200",
"method": "GET",
"deprecated": false
}]`
secret = "oXCt4JT2wf1_WlZl2OVG"
validPattern2 = "GITLAB_TOKEN=ABc123456789dEFghIJK"
secret2 = "ABc123456789dEFghIJK"
)

func TestGitLab_Pattern(t *testing.T) {
d := Scanner{}
d.SetCloudEndpoint("https://gitlab.com")
d.UseCloudEndpoint(true)
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})

tests := []struct {
Expand All @@ -41,14 +22,28 @@ func TestGitLab_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{secret},
name: "valid pattern",
input: `[{
"_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
"name": "Gitlab",
"type": "Detector",
"api": true,
"authentication_type": "",
"verification_url": "https://api.example.com/example",
"test_secrets": {
"gitlab_secret": "oXCt4JT2wf1_WlZl2OVG"
},
"docs":"https://docs.gitlab.com/test/api/example.json#get-drone-test-example-settings", // this matches the pattern but fail in entropy check
"expected_response": "200",
"method": "GET",
"deprecated": false
}]`,
want: []string{"oXCt4JT2wf1_WlZl2OVGhttps://gitlab.com"},
},
{
name: "valid pattern (with = before secret)",
input: validPattern2,
want: []string{secret2},
input: "GITLAB_TOKEN=ABc123456789dEFghIJK",
want: []string{"ABc123456789dEFghIJKhttps://gitlab.com"},
},
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/detectors/gitlab/v2/gitlab_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func TestGitlabV2_FromChunk_WithV1Secrets(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.s.SetCloudEndpoint("https://gitlab.com")
tt.s.UseCloudEndpoint(true)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Gitlab.FromData() error = %v, wantErr %v", err, tt.wantErr)
Expand All @@ -101,7 +103,7 @@ func TestGitlabV2_FromChunk_WithV1Secrets(t *testing.T) {
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
Expand Down Expand Up @@ -280,7 +282,7 @@ func TestGitlabV2_FromChunk(t *testing.T) {
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
Expand Down
33 changes: 19 additions & 14 deletions pkg/detectors/gitlab/v2/gitlab_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,21 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result

matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {

resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
ExtraData: map[string]string{},
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
"version": fmt.Sprintf("%d", s.Version()),
}

if verify {
for _, endpoint := range s.Endpoints() {
for _, endpoint := range s.Endpoints() {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
RawV2: []byte(resMatch + endpoint),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
"version": fmt.Sprintf("%d", s.Version()),
},
}

if verify {

isVerified, extraData, verificationErr := v1.VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)
s1.Verified = isVerified
maps.Copy(s1.ExtraData, extraData)
Expand All @@ -85,11 +86,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
"key": resMatch,
"host": endpoint,
}

// if secret is verified with one endpoint, break the loop to continue to next secret
results = append(results, s1)
break
}
}
}

results = append(results, s1)
results = append(results, s1)
}
}

return results, nil
Expand Down
39 changes: 18 additions & 21 deletions pkg/detectors/gitlab/v2/gitlab_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,10 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)

var (
validPattern = `[{
"_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
"name": "Gitlab",
"type": "Detector",
"api": true,
"authentication_type": "",
"verification_url": "https://api.example.com/example",
"test_secrets": {
"gitlab_secret": "glpat-W6fYSu70dPEo5w_SwbHWgQ"
},
"expected_response": "200",
"method": "GET",
"deprecated": false
}]`
secret = "glpat-W6fYSu70dPEo5w_SwbHWgQ"
)

func TestGitLab_Pattern(t *testing.T) {
d := Scanner{}
d.SetCloudEndpoint("https://gitlab.com")
d.UseCloudEndpoint(true)
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})

tests := []struct {
Expand All @@ -38,9 +22,22 @@ func TestGitLab_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{secret},
name: "valid pattern",
input: `[{
"_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
"name": "Gitlab",
"type": "Detector",
"api": true,
"authentication_type": "",
"verification_url": "https://api.example.com/example",
"test_secrets": {
"gitlab_secret": "glpat-W6fYSu70dPEo5w_SwbHWgQ"
},
"expected_response": "200",
"method": "GET",
"deprecated": false
}]`,
want: []string{"glpat-W6fYSu70dPEo5w_SwbHWgQhttps://gitlab.com"},
},
}

Expand Down