github.com/google/osv-scalibr@v0.4.1/veles/secrets/gcpapikey/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 gcpapikey_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  	"github.com/google/osv-scalibr/veles/secrets/gcpapikey"
    26  )
    27  
    28  const (
    29  	testKeyA = `AIzaSyAtestTESTt3s7te-_testtesttesttest`
    30  	testKeyB = `AIzaSyBtestTESTt3s7te-_testtesttesttest`
    31  	testKeyC = `AIzaSyCtestTESTt3s7te-_testtesttesttest`
    32  	testKeyD = `AIzaSyDtestTESTt3s7te-_testtesttesttest`
    33  	testKey  = testKeyA
    34  )
    35  
    36  // TestDetector_truePositives tests for cases where we know the Detector
    37  // will find a GCP API key/s.
    38  func TestDetector_truePositives(t *testing.T) {
    39  	engine, err := veles.NewDetectionEngine([]veles.Detector{gcpapikey.NewDetector()})
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  	cases := []struct {
    44  		name  string
    45  		input string
    46  		want  []veles.Secret
    47  	}{
    48  		{
    49  			name:  "simple matching string with A prefix",
    50  			input: testKeyA,
    51  			want: []veles.Secret{
    52  				gcpapikey.GCPAPIKey{Key: testKeyA},
    53  			},
    54  		},
    55  		{
    56  			name:  "simple matching string with B prefix",
    57  			input: testKeyB,
    58  			want: []veles.Secret{
    59  				gcpapikey.GCPAPIKey{Key: testKeyB},
    60  			},
    61  		},
    62  		{
    63  			name:  "simple matching string with C prefix",
    64  			input: testKeyC,
    65  			want: []veles.Secret{
    66  				gcpapikey.GCPAPIKey{Key: testKeyC},
    67  			},
    68  		},
    69  		{
    70  			name:  "simple matching string with D prefix",
    71  			input: testKeyD,
    72  			want: []veles.Secret{
    73  				gcpapikey.GCPAPIKey{Key: testKeyD},
    74  			},
    75  		},
    76  		{
    77  			name:  "match at end of string",
    78  			input: `API_KEY=` + testKey,
    79  			want: []veles.Secret{
    80  				gcpapikey.GCPAPIKey{Key: testKey},
    81  			},
    82  		},
    83  		{
    84  			name:  "match in middle of string",
    85  			input: `API_KEY="` + testKey + `"`,
    86  			want: []veles.Secret{
    87  				gcpapikey.GCPAPIKey{Key: testKey},
    88  			},
    89  		},
    90  		{
    91  			name:  "multiple matches",
    92  			input: testKey + testKey + testKey,
    93  			want: []veles.Secret{
    94  				gcpapikey.GCPAPIKey{Key: testKey},
    95  				gcpapikey.GCPAPIKey{Key: testKey},
    96  				gcpapikey.GCPAPIKey{Key: testKey},
    97  			},
    98  		},
    99  		{
   100  			name:  "multiple distinct matches",
   101  			input: testKey + "\n" + testKey[:len(testKey)-1] + "1\n",
   102  			want: []veles.Secret{
   103  				gcpapikey.GCPAPIKey{Key: testKey},
   104  				gcpapikey.GCPAPIKey{Key: testKey[:len(testKey)-1] + "1"},
   105  			},
   106  		},
   107  		{
   108  			name: "larger_input_containing_key",
   109  			input: fmt.Sprintf(`
   110  CONFIG_FILE=config.txt
   111  API_KEY=%s
   112  CLOUD_PROJECT=my-project
   113  		`, testKey),
   114  			want: []veles.Secret{
   115  				gcpapikey.GCPAPIKey{Key: testKey},
   116  			},
   117  		},
   118  		{
   119  			name:  "potential match longer than max key length",
   120  			input: testKey + `test`,
   121  			want: []veles.Secret{
   122  				gcpapikey.GCPAPIKey{Key: testKey},
   123  			},
   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  // TestDetector_trueNegatives tests for cases where we know the Detector
   140  // will not find a GCP API key.
   141  func TestDetector_trueNegatives(t *testing.T) {
   142  	engine, err := veles.NewDetectionEngine([]veles.Detector{gcpapikey.NewDetector()})
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  	cases := []struct {
   147  		name  string
   148  		input string
   149  		want  []veles.Secret
   150  	}{
   151  		{
   152  			name:  "empty input",
   153  			input: "",
   154  		},
   155  		{
   156  			name:  "wrong prefix",
   157  			input: "AIzaSyEtestTESTt3s7te-_testtesttesttest",
   158  		},
   159  		{
   160  			name:  "short key",
   161  			input: testKey[:len(testKey)-1],
   162  		},
   163  		{
   164  			name:  "incorrect casing of prefix",
   165  			input: `aizaSyAtestTESTt3s7te-_testtesttesttest`,
   166  		},
   167  		{
   168  			name:  "special character in key",
   169  			input: `AIzaSyAtest.TESTt3s7te-_testtesttesttes`,
   170  		},
   171  		{
   172  			name:  "special character in prefix",
   173  			input: `AI.zaSyAtestTESTt3s7te-_testtesttesttes`,
   174  		},
   175  		{
   176  			name:  "special character after prefix",
   177  			input: `AIzaSyA.testTESTt3s7te-_testtesttesttes`,
   178  		},
   179  		{
   180  			// See https://pkg.go.dev/regexp and
   181  			// https://github.com/google/re2/wiki/syntax.
   182  			name:  "overlapping matches not supported",
   183  			input: `AIzaSyA` + testKey,
   184  			want: []veles.Secret{
   185  				gcpapikey.GCPAPIKey{Key: `AIzaSyA` + testKey[:len(testKey)-7]},
   186  			},
   187  		},
   188  	}
   189  	for _, tc := range cases {
   190  		t.Run(tc.name, func(t *testing.T) {
   191  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   192  			if err != nil {
   193  				t.Errorf("Detect() error: %v, want nil", err)
   194  			}
   195  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   196  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   197  			}
   198  		})
   199  	}
   200  }