github.com/google/osv-scalibr@v0.4.1/veles/secrets/postmanapikey/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 postmanapikey_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 postmanapikey "github.com/google/osv-scalibr/veles/secrets/postmanapikey" 26 ) 27 28 const ( 29 // Example valid Postman API and Collection tokens. 30 detectorPMAK = "PMAK-68b96bd4ae8d2b0001db8a86-" + 31 "192b1cb49020c70a4d0c814ab71de822d7" 32 detectorPMAT = "PMAT-01K4A58P2HS2Q43TXHSXFRDBZX" 33 ) 34 35 // TestAPIKeyDetector_truePositives tests PMAK detection. 36 func TestAPIKeyDetector_truePositives(t *testing.T) { 37 engine, err := veles.NewDetectionEngine( 38 []veles.Detector{postmanapikey.NewAPIKeyDetector()}, 39 ) 40 if err != nil { 41 t.Fatal(err) 42 } 43 44 cases := []struct { 45 name string 46 input string 47 want []veles.Secret 48 }{{ 49 name: "simple matching string", 50 input: detectorPMAK, 51 want: []veles.Secret{ 52 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 53 }, 54 }, { 55 name: "match at end of string", 56 input: `POSTMAN_KEY=` + detectorPMAK, 57 want: []veles.Secret{ 58 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 59 }, 60 }, { 61 name: "match in quotes", 62 input: `key="` + detectorPMAK + `"`, 63 want: []veles.Secret{ 64 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 65 }, 66 }, { 67 name: "multiple matches", 68 input: detectorPMAK + "\n" + detectorPMAK, 69 want: []veles.Secret{ 70 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 71 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 72 }, 73 }, { 74 name: "larger_input_containing_key", 75 input: fmt.Sprintf("config:\n api_key: %s\n", 76 detectorPMAK), 77 want: []veles.Secret{ 78 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 79 }, 80 }, { 81 name: "potential match longer than max key length", 82 input: detectorPMAK + "EXTRA", 83 want: []veles.Secret{ 84 postmanapikey.PostmanAPIKey{Key: detectorPMAK}, 85 }, 86 }} 87 88 for _, tc := range cases { 89 t.Run(tc.name, func(t *testing.T) { 90 got, err := engine.Detect(t.Context(), 91 strings.NewReader(tc.input)) 92 if err != nil { 93 t.Errorf("Detect() error: %v, want nil", err) 94 } 95 if diff := cmp.Diff(tc.want, got, 96 cmpopts.EquateEmpty()); diff != "" { 97 t.Errorf("Detect() diff (-want +got):\n%s", 98 diff) 99 } 100 }) 101 } 102 } 103 104 // TestAPIKeyDetector_trueNegatives tests PMAK false negatives. 105 func TestAPIKeyDetector_trueNegatives(t *testing.T) { 106 engine, err := veles.NewDetectionEngine( 107 []veles.Detector{postmanapikey.NewAPIKeyDetector()}, 108 ) 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 cases := []struct { 114 name string 115 input string 116 want []veles.Secret 117 }{{ 118 name: "empty input", 119 input: "", 120 }, { 121 name: "short key should not match", 122 input: detectorPMAK[:len(detectorPMAK)-5], 123 }, { 124 name: "invalid_character_in_key_should_not_match", 125 input: "PMAK-" + strings.ReplaceAll( 126 detectorPMAK[5:], "a", "!", 127 ), 128 }, { 129 name: "incorrect prefix should not match", 130 input: "XMAK-" + detectorPMAK[5:], 131 }, { 132 name: "prefix missing dash should not match", 133 input: "PMAK" + detectorPMAK[5:], // removes the dash 134 }} 135 136 for _, tc := range cases { 137 t.Run(tc.name, func(t *testing.T) { 138 got, err := engine.Detect(t.Context(), 139 strings.NewReader(tc.input)) 140 if err != nil { 141 t.Errorf("Detect() error: %v, want nil", err) 142 } 143 if diff := cmp.Diff(tc.want, got, 144 cmpopts.EquateEmpty()); diff != "" { 145 t.Errorf("Detect() diff (-want +got):\n%s", 146 diff) 147 } 148 }) 149 } 150 } 151 152 // TestCollectionTokenDetector_truePositives tests PMAT detection. 153 func TestCollectionTokenDetector_truePositives(t *testing.T) { 154 engine, err := veles.NewDetectionEngine( 155 []veles.Detector{postmanapikey.NewCollectionTokenDetector()}, 156 ) 157 if err != nil { 158 t.Fatal(err) 159 } 160 161 cases := []struct { 162 name string 163 input string 164 want []veles.Secret 165 }{{ 166 name: "simple matching string", 167 input: detectorPMAT, 168 want: []veles.Secret{ 169 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 170 }, 171 }, { 172 name: "match at end of string", 173 input: `PMAT_KEY=` + detectorPMAT, 174 want: []veles.Secret{ 175 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 176 }, 177 }, { 178 name: "match in quotes", 179 input: `secret="` + detectorPMAT + `"`, 180 want: []veles.Secret{ 181 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 182 }, 183 }, { 184 name: "multiple matches", 185 input: detectorPMAT + " " + detectorPMAT, 186 want: []veles.Secret{ 187 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 188 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 189 }, 190 }, { 191 name: "larger_input_containing_key", 192 input: fmt.Sprintf("token:\n value: %s\n", 193 detectorPMAT), 194 want: []veles.Secret{ 195 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 196 }, 197 }, { 198 name: "potential match longer than max key length", 199 input: detectorPMAT + "EXTRA", 200 want: []veles.Secret{ 201 postmanapikey.PostmanCollectionToken{Key: detectorPMAT}, 202 }, 203 }} 204 205 for _, tc := range cases { 206 t.Run(tc.name, func(t *testing.T) { 207 got, err := engine.Detect(t.Context(), 208 strings.NewReader(tc.input)) 209 if err != nil { 210 t.Errorf("Detect() error: %v, want nil", err) 211 } 212 if diff := cmp.Diff(tc.want, got, 213 cmpopts.EquateEmpty()); diff != "" { 214 t.Errorf("Detect() diff (-want +got):\n%s", 215 diff) 216 } 217 }) 218 } 219 } 220 221 // TestCollectionTokenDetector_trueNegatives tests PMAT false negatives. 222 func TestCollectionTokenDetector_trueNegatives(t *testing.T) { 223 engine, err := veles.NewDetectionEngine( 224 []veles.Detector{postmanapikey.NewCollectionTokenDetector()}, 225 ) 226 if err != nil { 227 t.Fatal(err) 228 } 229 230 cases := []struct { 231 name string 232 input string 233 want []veles.Secret 234 }{{ 235 name: "empty input", 236 input: "", 237 }, { 238 name: "short key should not match", 239 input: detectorPMAT[:len(detectorPMAT)-2], 240 }, { 241 name: "invalid_character_in_key_should_not_match", 242 input: "PMAT-" + strings.ReplaceAll( 243 detectorPMAT[5:], "A", "#", 244 ), 245 }, { 246 name: "incorrect prefix should not match", 247 input: "PMAX-" + detectorPMAT[5:], 248 }, { 249 name: "prefix missing dash should not match", 250 input: "PMAT" + detectorPMAT[5:], // removes the dash 251 }} 252 253 for _, tc := range cases { 254 t.Run(tc.name, func(t *testing.T) { 255 got, err := engine.Detect(t.Context(), 256 strings.NewReader(tc.input)) 257 if err != nil { 258 t.Errorf("Detect() error: %v, want nil", err) 259 } 260 if diff := cmp.Diff(tc.want, got, 261 cmpopts.EquateEmpty()); diff != "" { 262 t.Errorf("Detect() diff (-want +got):\n%s", 263 diff) 264 } 265 }) 266 } 267 }