github.com/google/osv-scalibr@v0.4.1/veles/secrets/gitlabpat/detector_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gitlabpat_test 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 "github.com/google/osv-scalibr/veles" 25 "github.com/google/osv-scalibr/veles/secrets/gitlabpat" 26 ) 27 28 const ( 29 testKeyVersioned = "glpat-bzox79Of-KE9FD2LjoXXF4CvyxA.01.0r0l8l6ir" 30 testKeyVersionedInvalidCrc32 = "glpat-bzox79Of-KE9FD2LjoXXF4CvyxA.01.0r03gxo7a" 31 testKeyRoutable = "glpat-bzox79Of-KE9FD2LjoXXF4CvyxA.0r03gxo7s" 32 testKeyLegacy = "glpat-vzDNJu3Lvh4YCCekKsnx" 33 ) 34 35 // TestDetector_truePositives tests cases where the Detector should find a GitLab PAT. 36 func TestDetector_truePositives(t *testing.T) { 37 engine, err := veles.NewDetectionEngine([]veles.Detector{gitlabpat.NewDetector()}) 38 if err != nil { 39 t.Fatal(err) 40 } 41 cases := []struct { 42 name string 43 input string 44 want []veles.Secret 45 }{ 46 { 47 name: "routable versioned simple", 48 input: testKeyVersioned, 49 want: []veles.Secret{ 50 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 51 }, 52 }, 53 { 54 name: "routable simple", 55 input: testKeyRoutable, 56 want: []veles.Secret{ 57 gitlabpat.GitlabPAT{Pat: testKeyRoutable}, 58 }, 59 }, 60 { 61 name: "Legacy simple", 62 input: testKeyLegacy, 63 want: []veles.Secret{ 64 gitlabpat.GitlabPAT{Pat: testKeyLegacy}, 65 }, 66 }, 67 { 68 name: "match in middle of string (versioned)", 69 input: `GITLAB_PAT="` + testKeyVersioned + `"`, 70 want: []veles.Secret{ 71 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 72 }, 73 }, 74 { 75 name: "multiple matches (same token repeated)", 76 input: testKeyVersioned + " " + testKeyVersioned + " " + testKeyVersioned, 77 want: []veles.Secret{ 78 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 79 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 80 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 81 }, 82 }, 83 { 84 name: "multiple distinct matches", 85 input: testKeyVersioned + "\n" + testKeyRoutable + "\n" + testKeyLegacy, 86 want: []veles.Secret{ 87 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 88 gitlabpat.GitlabPAT{Pat: testKeyRoutable}, 89 gitlabpat.GitlabPAT{Pat: testKeyLegacy}, 90 }, 91 }, 92 { 93 name: "multiple distinct matches with extra dot", 94 input: testKeyVersioned + ".11aa" + "\n" + testKeyRoutable + ".11aa" + "\n" + testKeyLegacy + ".11aa", 95 want: []veles.Secret{ 96 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 97 gitlabpat.GitlabPAT{Pat: testKeyRoutable}, 98 gitlabpat.GitlabPAT{Pat: testKeyLegacy}, 99 }, 100 }, 101 { 102 name: "larger_input_containing_versioned_key", 103 input: fmt.Sprintf(` 104 :test_api_key: pat-test 105 :gitlab_pat: %s 106 `, testKeyVersioned), 107 want: []veles.Secret{ 108 gitlabpat.GitlabPAT{Pat: testKeyVersioned}, 109 }, 110 }, 111 } 112 for _, tc := range cases { 113 t.Run(tc.name, func(t *testing.T) { 114 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 115 if err != nil { 116 t.Errorf("Detect() error: %v, want nil", err) 117 } 118 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 119 t.Errorf("Detect() diff (-want +got):\n%s", diff) 120 } 121 }) 122 } 123 } 124 125 // TestDetector_trueNegatives tests cases where the Detector should NOT find a GitLab PAT. 126 func TestDetector_trueNegatives(t *testing.T) { 127 engine, err := veles.NewDetectionEngine([]veles.Detector{gitlabpat.NewDetector()}) 128 if err != nil { 129 t.Fatal(err) 130 } 131 cases := []struct { 132 name string 133 input string 134 want []veles.Secret 135 }{ 136 { 137 name: "empty input", 138 input: "", 139 }, 140 { 141 name: "short versioned key should not match", 142 input: testKeyVersioned[:len(testKeyLegacy)-10], 143 }, 144 { 145 name: "invalid character in key should not match", 146 input: `glpat-` + strings.Repeat("a", 10) + "!" + "aaaa", 147 }, 148 { 149 name: "incorrect prefix should not match", 150 input: `glpaa-` + strings.Repeat("a", 51), 151 }, 152 { 153 name: "prefix missing dash should not match", 154 input: `glpat` + strings.Repeat("a", 51), 155 }, 156 { 157 name: "invalid CRC32 should not match", 158 input: testKeyVersionedInvalidCrc32, 159 }, 160 } 161 for _, tc := range cases { 162 t.Run(tc.name, func(t *testing.T) { 163 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 164 if err != nil { 165 t.Errorf("Detect() error: %v, want nil", err) 166 } 167 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 168 t.Errorf("Detect() diff (-want +got):\n%s", diff) 169 } 170 }) 171 } 172 }