github.com/google/osv-scalibr@v0.4.1/veles/secrets/openai/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 openai
    16  
    17  import (
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/veles"
    24  )
    25  
    26  const (
    27  	validAPIKey = "sk-proj-12345678901234567890T3BlbkFJ" +
    28  		"12345678901234567890123456"
    29  )
    30  
    31  func TestDetector(t *testing.T) {
    32  	engine, err := veles.NewDetectionEngine([]veles.Detector{NewDetector()})
    33  	if err != nil {
    34  		t.Fatal(err)
    35  	}
    36  
    37  	cases := []struct {
    38  		name  string
    39  		input string
    40  		want  []veles.Secret
    41  	}{{
    42  		name:  "project_key",
    43  		input: validAPIKey,
    44  		want: []veles.Secret{
    45  			APIKey{Key: validAPIKey},
    46  		},
    47  	}, {
    48  		name:  "project_key_in_config",
    49  		input: "OPENAI_API_KEY=" + validAPIKey,
    50  		want: []veles.Secret{
    51  			APIKey{Key: validAPIKey},
    52  		},
    53  	}, {
    54  		name:  "project_key_in_env",
    55  		input: "export OPENAI_KEY=\"" + validAPIKey + "\"",
    56  		want: []veles.Secret{
    57  			APIKey{Key: validAPIKey},
    58  		},
    59  	}, {
    60  		name: "multiple_project_keys",
    61  		input: validAPIKey + "\n" +
    62  			"sk-proj-abcdefghij1234567890T3BlbkFJklmnopqrstuvwxyz098765432109876",
    63  		want: []veles.Secret{
    64  			APIKey{Key: validAPIKey},
    65  			APIKey{Key: "sk-proj-abcdefghij1234567890T3BlbkFJ" +
    66  				"klmnopqrstuvwxyz098765432109876"},
    67  		},
    68  	}, {
    69  		name: "openai_project_key_with_special_chars",
    70  		input: "sk-proj-AbC_DeF-GhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGh" +
    71  			"T3BlbkFJXyZ-123_456-789_012-345_678-901_234-567_890-AbCdEfGhIjKlMnOpQrStUvWxYzZzZz",
    72  		want: []veles.Secret{
    73  			APIKey{Key: "sk-proj-AbC_DeF-GhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGh" +
    74  				"T3BlbkFJXyZ-123_456-789_012-345_678-901_234-567_890-AbCdEfGhIjKlMnOpQrStUvWxYzZzZz"},
    75  		},
    76  	}, {
    77  		name:  "legacy_openai_key_format",
    78  		input: "sk-FakeTest123456789T3BlbkFJAbCdEfGhIjKlMnOpQrStUvWxYz",
    79  		want: []veles.Secret{
    80  			APIKey{Key: "sk-FakeTest123456789T3BlbkFJAbCdEfGhIjKlMnOpQrStUvWxYz"},
    81  		},
    82  	}, {
    83  		name: "multiple_legacy_keys",
    84  		input: "sk-TestKey1234567890T3BlbkFJXyZaBcDeFgHiJkLmNoPqRsTuVw\n" +
    85  			"sk-AnotherFakeKey12T3BlbkFJMnOpQrStUvWxYzAbCdEfGhIjKl",
    86  		want: []veles.Secret{
    87  			APIKey{Key: "sk-TestKey1234567890T3BlbkFJXyZaBcDeFgHiJkLmNoPqRsTuVw"},
    88  			APIKey{Key: "sk-AnotherFakeKey12T3BlbkFJMnOpQrStUvWxYzAbCdEfGhIjKl"},
    89  		},
    90  	}}
    91  
    92  	for _, tc := range cases {
    93  		t.Run(tc.name, func(t *testing.T) {
    94  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
    95  			if err != nil {
    96  				t.Errorf("Detect() error: %v, want nil", err)
    97  			}
    98  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
    99  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestDetector_NoMatches(t *testing.T) {
   106  	engine, err := veles.NewDetectionEngine([]veles.Detector{NewDetector()})
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	cases := []struct {
   112  		name  string
   113  		input string
   114  	}{{
   115  		name:  "too_short",
   116  		input: "sk-tooshort",
   117  	}, {
   118  		name:  "wrong_prefix",
   119  		input: "ak-proj-abcdefghijklmnopqrstT3BlbkFJuvwxyzABCDEF123456",
   120  	}, {
   121  		name:  "malformed_project_key_missing_marker",
   122  		input: "sk-proj-abcdefghijklmnopqrstuvwxyzABCDEF123456",
   123  	}, {
   124  		name:  "malformed_project_key_wrong_marker",
   125  		input: "sk-proj-abcdefghijklmnopqrT3BlbkFZuvwxyzABCDEF123456",
   126  	}, {
   127  		name:  "legacy_key_not_supported",
   128  		input: "sk-abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJK",
   129  	}, {
   130  		name:  "no_secrets",
   131  		input: "This is just regular text with no secrets",
   132  	}, {
   133  		name:  "sk_prefix_but_not_key",
   134  		input: "skeleton key is not an API key",
   135  	}}
   136  
   137  	for _, tc := range cases {
   138  		t.Run(tc.name, func(t *testing.T) {
   139  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   140  			if err != nil {
   141  				t.Errorf("Detect() error: %v, want nil", err)
   142  			}
   143  			if len(got) != 0 {
   144  				t.Errorf("Detect() got %v secrets, want 0", len(got))
   145  			}
   146  		})
   147  	}
   148  }
   149  
   150  func TestProjectKeyValidation(t *testing.T) {
   151  	testCases := []struct {
   152  		name    string
   153  		key     string
   154  		isValid bool
   155  	}{{
   156  		name:    "valid_project_key",
   157  		key:     validAPIKey,
   158  		isValid: true,
   159  	}, {
   160  		name:    "not_a_key",
   161  		key:     "not-a-key",
   162  		isValid: false,
   163  	}, {
   164  		name:    "empty_string",
   165  		key:     "",
   166  		isValid: false,
   167  	}, {
   168  		name:    "legacy_key_format",
   169  		key:     "sk-123456789012345678901234567890123456789012345678",
   170  		isValid: false,
   171  	}}
   172  
   173  	for _, tc := range testCases {
   174  		t.Run(tc.name, func(t *testing.T) {
   175  			// Test by trying to detect the key
   176  			engine, err := veles.NewDetectionEngine([]veles.Detector{NewDetector()})
   177  			if err != nil {
   178  				t.Fatal(err)
   179  			}
   180  
   181  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.key))
   182  			if err != nil {
   183  				t.Errorf("Detect() error: %v, want nil", err)
   184  			}
   185  
   186  			isDetected := len(got) > 0
   187  			if isDetected != tc.isValid {
   188  				t.Errorf("Key %q detected=%v, want valid=%v",
   189  					tc.key, isDetected, tc.isValid)
   190  			}
   191  		})
   192  	}
   193  }