github.com/google/osv-scalibr@v0.4.1/veles/secrets/hashicorpvault/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 hashicorpvault 16 17 import ( 18 "strings" 19 "testing" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/osv-scalibr/veles" 23 ) 24 25 func TestNewTokenDetector_Detect(t *testing.T) { 26 tests := []struct { 27 name string 28 input string 29 expected []veles.Secret 30 }{ 31 { 32 name: "hvs token", 33 input: "VAULT_TOKEN=hvs.CAESIB8KI2QJk0ePUYdOQXaxl0", 34 expected: []veles.Secret{ 35 Token{Token: "hvs.CAESIB8KI2QJk0ePUYdOQXaxl0"}, 36 }, 37 }, 38 { 39 name: "hvb token", 40 input: "export VAULT_TOKEN=hvb.AAAAAQJz0zBvWUNOIKTkDhYX", 41 expected: []veles.Secret{ 42 Token{Token: "hvb.AAAAAQJz0zBvWUNOIKTkDhYX"}, 43 }, 44 }, 45 { 46 name: "multiple tokens", 47 input: "hvs.CAESIB8KI2QJk0ePUYdOQXaxl0 and hvb.AAAAAQJz0zBvWUNOIKTkDhYX", 48 expected: []veles.Secret{ 49 Token{Token: "hvs.CAESIB8KI2QJk0ePUYdOQXaxl0"}, 50 Token{Token: "hvb.AAAAAQJz0zBvWUNOIKTkDhYX"}, 51 }, 52 }, 53 { 54 name: "long token", 55 input: "hvs.CAESIDOKRphWTHBGVCFxpRF0iKHJsQUF6aMSCxdGH6_7n8MbGicKImh2cy5raWJjQjdrcmpYOEoxanUza2ljZGJhYwkEGPDt4a4", 56 expected: []veles.Secret{ 57 Token{Token: "hvs.CAESIDOKRphWTHBGVCFxpRF0iKHJsQUF6aMSCxdGH6_7n8MbGicKImh2cy5raWJjQjdrcmpYOEoxanUza2ljZGJhYwkEGPDt4a4"}, 58 }, 59 }, 60 { 61 name: "no token", 62 input: "some random text without vault tokens", 63 expected: nil, 64 }, 65 { 66 name: "invalid prefix", 67 input: "hvx.CAESIB8KI2QJk0ePUYdOQXaxl0", 68 expected: nil, 69 }, 70 { 71 name: "too short", 72 input: "hvs.short", 73 expected: nil, 74 }, 75 } 76 77 engine, err := veles.NewDetectionEngine([]veles.Detector{NewTokenDetector()}) 78 if err != nil { 79 t.Fatalf("Failed to create detection engine: %v", err) 80 } 81 82 for _, test := range tests { 83 t.Run(test.name, func(t *testing.T) { 84 reader := strings.NewReader(test.input) 85 secrets, err := engine.Detect(t.Context(), reader) 86 if err != nil { 87 t.Fatalf("Detect() returned error: %v", err) 88 } 89 if diff := cmp.Diff(test.expected, secrets); diff != "" { 90 t.Errorf("Detect() returned unexpected result (-expected +got):\n%s", diff) 91 } 92 }) 93 } 94 } 95 96 func TestNewAppRoleDetector_Detect(t *testing.T) { 97 tests := []struct { 98 name string 99 input string 100 expected []veles.Secret 101 }{ 102 { 103 name: "single UUID with ROLE_ID context", 104 input: "ROLE_ID=12345678-1234-1234-1234-123456789012", 105 expected: []veles.Secret{ 106 AppRoleCredentials{RoleID: "12345678-1234-1234-1234-123456789012"}, 107 }, 108 }, 109 { 110 name: "context-aware credential pair", 111 input: "role_id: 87654321-4321-4321-4321-210987654321\nsecret_id: 11111111-2222-3333-4444-555555555555", 112 expected: []veles.Secret{ 113 AppRoleCredentials{ 114 RoleID: "87654321-4321-4321-4321-210987654321", 115 SecretID: "11111111-2222-3333-4444-555555555555", 116 }, 117 }, 118 }, 119 { 120 name: "standalone role_id with context", 121 input: "role_id: 87654321-4321-4321-4321-210987654321", 122 expected: []veles.Secret{ 123 AppRoleCredentials{RoleID: "87654321-4321-4321-4321-210987654321"}, 124 }, 125 }, 126 { 127 name: "standalone secret_id with context", 128 input: "secret_id: 11111111-2222-3333-4444-555555555555", 129 expected: []veles.Secret{ 130 AppRoleCredentials{SecretID: "11111111-2222-3333-4444-555555555555"}, 131 }, 132 }, 133 { 134 name: "UUID without context", 135 input: "some random UUID: 12345678-1234-1234-1234-123456789012 in text", 136 expected: []veles.Secret{ 137 AppRoleCredentials{ID: "12345678-1234-1234-1234-123456789012"}, 138 }, 139 }, 140 { 141 name: "mixed case UUID with invalid hex chars", 142 input: "ROLE_ID=12345678-ABCD-1234-EFGH-123456789012", 143 expected: nil, // G and H are not valid hex characters 144 }, 145 { 146 name: "no UUID", 147 input: "some random text without UUIDs", 148 expected: nil, 149 }, 150 { 151 name: "invalid UUID format", 152 input: "12345678-1234-1234-1234-12345678901", // too short 153 expected: nil, 154 }, 155 { 156 name: "invalid UUID format with extra chars", 157 input: "12345678-1234-1234-1234-1234567890123", // too long 158 expected: []veles.Secret{ 159 AppRoleCredentials{ID: "12345678-1234-1234-1234-123456789012"}, 160 }, // The regex will match the valid portion 161 }, 162 { 163 name: "invalid UUID format missing hyphens", 164 input: "1234567812341234123412345678901", 165 expected: nil, 166 }, 167 } 168 169 engine, err := veles.NewDetectionEngine([]veles.Detector{NewAppRoleDetector()}) 170 if err != nil { 171 t.Fatalf("Failed to create detection engine: %v", err) 172 } 173 174 for _, test := range tests { 175 t.Run(test.name, func(t *testing.T) { 176 reader := strings.NewReader(test.input) 177 secrets, err := engine.Detect(t.Context(), reader) 178 if err != nil { 179 t.Fatalf("Detect() returned error: %v", err) 180 } 181 if diff := cmp.Diff(test.expected, secrets); diff != "" { 182 t.Errorf("Detect() returned unexpected result (-expected +got):\n%s", diff) 183 } 184 }) 185 } 186 } 187 188 func TestDetector_LargeInput(t *testing.T) { 189 // Test that detector can handle large inputs without issues 190 largeInput := strings.Repeat("some random text ", 10000) + "hvs.CAESIB8KI2QJk0ePUYdOQXaxl0" 191 192 engine, err := veles.NewDetectionEngine([]veles.Detector{NewTokenDetector()}) 193 if err != nil { 194 t.Fatalf("Failed to create detection engine: %v", err) 195 } 196 197 reader := strings.NewReader(largeInput) 198 secrets, err := engine.Detect(t.Context(), reader) 199 if err != nil { 200 t.Fatalf("Detect() returned error: %v", err) 201 } 202 203 expected := []veles.Secret{ 204 Token{Token: "hvs.CAESIB8KI2QJk0ePUYdOQXaxl0"}, 205 } 206 207 if diff := cmp.Diff(expected, secrets); diff != "" { 208 t.Errorf("Detect() returned unexpected result (-expected +got):\n%s", diff) 209 } 210 } 211 212 func TestDetector_EmptyInput(t *testing.T) { 213 engine, err := veles.NewDetectionEngine([]veles.Detector{NewTokenDetector()}) 214 if err != nil { 215 t.Fatalf("Failed to create detection engine: %v", err) 216 } 217 218 reader := strings.NewReader("") 219 secrets, err := engine.Detect(t.Context(), reader) 220 if err != nil { 221 t.Fatalf("Detect() returned error: %v", err) 222 } 223 224 if len(secrets) != 0 { 225 t.Errorf("Expected no secrets from empty input, got %d secrets", len(secrets)) 226 } 227 } 228 229 // Note: TestDetector_ErrorReading was removed because the DetectionEngine 230 // handles reader errors at a different level than individual detectors. 231 232 func TestDetect_IncorrectFormat(t *testing.T) { 233 tests := []struct { 234 name string 235 input string 236 }{ 237 { 238 name: "invalid token prefix", 239 input: "hvx.CAESIB8KI2QJk0ePUYdOQXaxl0", 240 }, 241 { 242 name: "token too short", 243 input: "hvs.ABC", 244 }, 245 { 246 name: "wrong separator", 247 input: "hvs-CAESIB8KI2QJk0ePUYdOQXaxl0", 248 }, 249 { 250 name: "invalid UUID format - missing hyphens", 251 input: "role_id: 12345678123412341234123456789012", 252 }, 253 { 254 name: "invalid UUID format - wrong length", 255 input: "secret_id: 12345678-1234-1234-1234-12345678901", 256 }, 257 { 258 name: "invalid UUID format - invalid hex characters", 259 input: "role_id: 12345678-1234-1234-1234-12345678901G", 260 }, 261 { 262 name: "malformed token with valid prefix", 263 input: "hvs.", 264 }, 265 { 266 name: "legacy token too short", 267 input: "s.ABC", 268 }, 269 { 270 name: "mixed valid and invalid", 271 input: "hvx.invalid and role_id: invalid-uuid-format", 272 }, 273 } 274 275 // Test both token and AppRole detectors 276 detectors := []struct { 277 name string 278 detector veles.Detector 279 }{ 280 {"TokenDetector", NewTokenDetector()}, 281 {"AppRoleDetector", NewAppRoleDetector()}, 282 } 283 284 for _, detectorTest := range detectors { 285 t.Run(detectorTest.name, func(t *testing.T) { 286 engine, err := veles.NewDetectionEngine([]veles.Detector{detectorTest.detector}) 287 if err != nil { 288 t.Fatalf("Failed to create detection engine: %v", err) 289 } 290 291 for _, test := range tests { 292 t.Run(test.name, func(t *testing.T) { 293 reader := strings.NewReader(test.input) 294 secrets, err := engine.Detect(t.Context(), reader) 295 if err != nil { 296 t.Fatalf("Detect() returned error: %v", err) 297 } 298 299 // All these inputs should produce no valid secrets 300 if len(secrets) != 0 { 301 t.Errorf("Expected no secrets for invalid format '%s', but got %d secrets: %v", 302 test.input, len(secrets), secrets) 303 } 304 }) 305 } 306 }) 307 } 308 }