github.com/google/osv-scalibr@v0.4.1/veles/secrets/gcpapikey/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 gcpapikey_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/gcpapikey" 26 ) 27 28 const ( 29 testKeyA = `AIzaSyAtestTESTt3s7te-_testtesttesttest` 30 testKeyB = `AIzaSyBtestTESTt3s7te-_testtesttesttest` 31 testKeyC = `AIzaSyCtestTESTt3s7te-_testtesttesttest` 32 testKeyD = `AIzaSyDtestTESTt3s7te-_testtesttesttest` 33 testKey = testKeyA 34 ) 35 36 // TestDetector_truePositives tests for cases where we know the Detector 37 // will find a GCP API key/s. 38 func TestDetector_truePositives(t *testing.T) { 39 engine, err := veles.NewDetectionEngine([]veles.Detector{gcpapikey.NewDetector()}) 40 if err != nil { 41 t.Fatal(err) 42 } 43 cases := []struct { 44 name string 45 input string 46 want []veles.Secret 47 }{ 48 { 49 name: "simple matching string with A prefix", 50 input: testKeyA, 51 want: []veles.Secret{ 52 gcpapikey.GCPAPIKey{Key: testKeyA}, 53 }, 54 }, 55 { 56 name: "simple matching string with B prefix", 57 input: testKeyB, 58 want: []veles.Secret{ 59 gcpapikey.GCPAPIKey{Key: testKeyB}, 60 }, 61 }, 62 { 63 name: "simple matching string with C prefix", 64 input: testKeyC, 65 want: []veles.Secret{ 66 gcpapikey.GCPAPIKey{Key: testKeyC}, 67 }, 68 }, 69 { 70 name: "simple matching string with D prefix", 71 input: testKeyD, 72 want: []veles.Secret{ 73 gcpapikey.GCPAPIKey{Key: testKeyD}, 74 }, 75 }, 76 { 77 name: "match at end of string", 78 input: `API_KEY=` + testKey, 79 want: []veles.Secret{ 80 gcpapikey.GCPAPIKey{Key: testKey}, 81 }, 82 }, 83 { 84 name: "match in middle of string", 85 input: `API_KEY="` + testKey + `"`, 86 want: []veles.Secret{ 87 gcpapikey.GCPAPIKey{Key: testKey}, 88 }, 89 }, 90 { 91 name: "multiple matches", 92 input: testKey + testKey + testKey, 93 want: []veles.Secret{ 94 gcpapikey.GCPAPIKey{Key: testKey}, 95 gcpapikey.GCPAPIKey{Key: testKey}, 96 gcpapikey.GCPAPIKey{Key: testKey}, 97 }, 98 }, 99 { 100 name: "multiple distinct matches", 101 input: testKey + "\n" + testKey[:len(testKey)-1] + "1\n", 102 want: []veles.Secret{ 103 gcpapikey.GCPAPIKey{Key: testKey}, 104 gcpapikey.GCPAPIKey{Key: testKey[:len(testKey)-1] + "1"}, 105 }, 106 }, 107 { 108 name: "larger_input_containing_key", 109 input: fmt.Sprintf(` 110 CONFIG_FILE=config.txt 111 API_KEY=%s 112 CLOUD_PROJECT=my-project 113 `, testKey), 114 want: []veles.Secret{ 115 gcpapikey.GCPAPIKey{Key: testKey}, 116 }, 117 }, 118 { 119 name: "potential match longer than max key length", 120 input: testKey + `test`, 121 want: []veles.Secret{ 122 gcpapikey.GCPAPIKey{Key: testKey}, 123 }, 124 }, 125 } 126 for _, tc := range cases { 127 t.Run(tc.name, func(t *testing.T) { 128 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 129 if err != nil { 130 t.Errorf("Detect() error: %v, want nil", err) 131 } 132 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 133 t.Errorf("Detect() diff (-want +got):\n%s", diff) 134 } 135 }) 136 } 137 } 138 139 // TestDetector_trueNegatives tests for cases where we know the Detector 140 // will not find a GCP API key. 141 func TestDetector_trueNegatives(t *testing.T) { 142 engine, err := veles.NewDetectionEngine([]veles.Detector{gcpapikey.NewDetector()}) 143 if err != nil { 144 t.Fatal(err) 145 } 146 cases := []struct { 147 name string 148 input string 149 want []veles.Secret 150 }{ 151 { 152 name: "empty input", 153 input: "", 154 }, 155 { 156 name: "wrong prefix", 157 input: "AIzaSyEtestTESTt3s7te-_testtesttesttest", 158 }, 159 { 160 name: "short key", 161 input: testKey[:len(testKey)-1], 162 }, 163 { 164 name: "incorrect casing of prefix", 165 input: `aizaSyAtestTESTt3s7te-_testtesttesttest`, 166 }, 167 { 168 name: "special character in key", 169 input: `AIzaSyAtest.TESTt3s7te-_testtesttesttes`, 170 }, 171 { 172 name: "special character in prefix", 173 input: `AI.zaSyAtestTESTt3s7te-_testtesttesttes`, 174 }, 175 { 176 name: "special character after prefix", 177 input: `AIzaSyA.testTESTt3s7te-_testtesttesttes`, 178 }, 179 { 180 // See https://pkg.go.dev/regexp and 181 // https://github.com/google/re2/wiki/syntax. 182 name: "overlapping matches not supported", 183 input: `AIzaSyA` + testKey, 184 want: []veles.Secret{ 185 gcpapikey.GCPAPIKey{Key: `AIzaSyA` + testKey[:len(testKey)-7]}, 186 }, 187 }, 188 } 189 for _, tc := range cases { 190 t.Run(tc.name, func(t *testing.T) { 191 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 192 if err != nil { 193 t.Errorf("Detect() error: %v, want nil", err) 194 } 195 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 196 t.Errorf("Detect() diff (-want +got):\n%s", diff) 197 } 198 }) 199 } 200 }