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  }