github.com/google/osv-scalibr@v0.4.1/veles/secrets/grokxaiapikey/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 grokxaiapikey_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 grokxaiapikey "github.com/google/osv-scalibr/veles/secrets/grokxaiapikey" 26 ) 27 28 const ( 29 // Example valid API and Management keys. 30 detectorAPIKey = "xai-lY6JXMlP8jvE3CAgqkn2EiRlMZ444mzFQS0JLKIv4p6ZcoGGxW2Mk6EIMs72dLXylw0Kg4MLyOHGDj6c" 31 detectorMgmtKey = "xai-token-jS4Ke7pHhyiPVH0gWNcFmpnBLAMRgZchGWroIOWqLK5TB2obw8zbgVudrULg5DkZNdOoKsQ6rema3LGz" 32 ) 33 34 // TestAPIKeyDetector_truePositives tests that the API key detector finds xai-... keys. 35 func TestAPIKeyDetector_truePositives(t *testing.T) { 36 engine, err := veles.NewDetectionEngine([]veles.Detector{grokxaiapikey.NewAPIKeyDetector()}) 37 if err != nil { 38 t.Fatal(err) 39 } 40 41 cases := []struct { 42 name string 43 input string 44 want []veles.Secret 45 }{{ 46 name: "simple matching string", 47 input: detectorAPIKey, 48 want: []veles.Secret{ 49 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 50 }, 51 }, { 52 name: "match at end of string", 53 input: `XAI_KEY=` + detectorAPIKey, 54 want: []veles.Secret{ 55 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 56 }, 57 }, { 58 name: "match in quotes", 59 input: `env "XAI"="` + detectorAPIKey + `"`, 60 want: []veles.Secret{ 61 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 62 }, 63 }, { 64 name: "multiple matches", 65 input: detectorAPIKey + detectorAPIKey, 66 want: []veles.Secret{ 67 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 68 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 69 }, 70 }, { 71 name: "larger input containing key", 72 input: fmt.Sprintf("some: yaml\napi_key: %s\nother: value\n", detectorAPIKey), 73 want: []veles.Secret{ 74 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 75 }, 76 }, { 77 name: "potential match longer than max key length", 78 input: detectorAPIKey + "EXTRA", 79 want: []veles.Secret{ 80 grokxaiapikey.GrokXAIAPIKey{Key: detectorAPIKey}, 81 }, 82 }} 83 84 for _, tc := range cases { 85 t.Run(tc.name, func(t *testing.T) { 86 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 87 if err != nil { 88 t.Errorf("Detect() error: %v, want nil", err) 89 } 90 fmt.Printf("got = %+v\n", got) 91 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 92 t.Errorf("Detect() diff (-want +got):\n%s", diff) 93 } 94 }) 95 } 96 } 97 98 // TestAPIKeyDetector_trueNegatives tests that non-matching inputs do not produce false positives. 99 func TestAPIKeyDetector_trueNegatives(t *testing.T) { 100 engine, err := veles.NewDetectionEngine([]veles.Detector{grokxaiapikey.NewAPIKeyDetector()}) 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 cases := []struct { 106 name string 107 input string 108 want []veles.Secret 109 }{{ 110 name: "empty input", 111 input: "", 112 }, { 113 name: "short key should not match", 114 input: detectorAPIKey[:len(detectorAPIKey)-1], 115 }, { 116 name: "invalid character in key should not match", 117 input: "xai-" + strings.ReplaceAll(detectorAPIKey[4:], "A", "-"), 118 }, { 119 name: "incorrect prefix should not match", 120 input: "XAi-" + detectorAPIKey[4:], 121 }, { 122 name: "prefix missing dash should not match", 123 input: "xaix" + detectorAPIKey[3:], 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 // TestManagementKeyDetector_truePositives tests that the management key detector finds xai-token-... keys. 140 func TestManagementKeyDetector_truePositives(t *testing.T) { 141 engine, err := veles.NewDetectionEngine([]veles.Detector{grokxaiapikey.NewManagementKeyDetector()}) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 cases := []struct { 147 name string 148 input string 149 want []veles.Secret 150 }{{ 151 name: "simple matching string", 152 input: detectorMgmtKey, 153 want: []veles.Secret{ 154 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey}, 155 }, 156 }, { 157 name: "match at end of string", 158 input: `GROK_MGMT=` + detectorMgmtKey, 159 want: []veles.Secret{ 160 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey}, 161 }, 162 }, { 163 name: "match in quotes", 164 input: `secret="` + detectorMgmtKey + `"`, 165 want: []veles.Secret{ 166 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey}, 167 }, 168 }, { 169 name: "multiple distinct matches", 170 input: detectorMgmtKey + "\n" + detectorMgmtKey[:len(detectorMgmtKey)-1] + "1\n", 171 want: []veles.Secret{ 172 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey}, 173 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey[:len(detectorMgmtKey)-1] + "1"}, 174 }, 175 }, { 176 name: "larger input containing key", 177 input: fmt.Sprintf("config:\n management_key: %s\n", detectorMgmtKey), 178 want: []veles.Secret{ 179 grokxaiapikey.GrokXAIManagementKey{Key: detectorMgmtKey}, 180 }, 181 }} 182 183 for _, tc := range cases { 184 t.Run(tc.name, func(t *testing.T) { 185 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 186 if err != nil { 187 t.Errorf("Detect() error: %v, want nil", err) 188 } 189 fmt.Printf("got = %+v\n", got) 190 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 191 t.Errorf("Detect() diff (-want +got):\n%s", diff) 192 } 193 }) 194 } 195 } 196 197 // TestManagementKeyDetector_trueNegatives tests negative cases for the management key detector. 198 func TestManagementKeyDetector_trueNegatives(t *testing.T) { 199 engine, err := veles.NewDetectionEngine([]veles.Detector{grokxaiapikey.NewManagementKeyDetector()}) 200 if err != nil { 201 t.Fatal(err) 202 } 203 204 cases := []struct { 205 name string 206 input string 207 want []veles.Secret 208 }{{ 209 name: "empty input", 210 input: "", 211 }, { 212 name: "short key should not match", 213 input: detectorMgmtKey[:len(detectorMgmtKey)-1], 214 }, { 215 name: "invalid character in key should not match", 216 input: "xai-token-" + strings.ReplaceAll(detectorMgmtKey[len("xai-token-"):], "o", "-"), 217 }, { 218 name: "incorrect prefix should not match", 219 input: "xaitoken-" + detectorMgmtKey[len("xai-token-"):], 220 }, { 221 name: "prefix missing dash should not match", 222 input: "xaitoken" + detectorMgmtKey[len("xai-token-")-1:], 223 }} 224 225 for _, tc := range cases { 226 t.Run(tc.name, func(t *testing.T) { 227 got, err := engine.Detect(t.Context(), strings.NewReader(tc.input)) 228 if err != nil { 229 t.Errorf("Detect() error: %v, want nil", err) 230 } 231 if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { 232 t.Errorf("Detect() diff (-want +got):\n%s", diff) 233 } 234 }) 235 } 236 }