github.com/google/osv-scalibr@v0.4.1/veles/secrets/postmanapikey/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 postmanapikey_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  	postmanapikey "github.com/google/osv-scalibr/veles/secrets/postmanapikey"
    26  )
    27  
    28  const (
    29  	// Example valid Postman API and Collection tokens.
    30  	detectorPMAK = "PMAK-68b96bd4ae8d2b0001db8a86-" +
    31  		"192b1cb49020c70a4d0c814ab71de822d7"
    32  	detectorPMAT = "PMAT-01K4A58P2HS2Q43TXHSXFRDBZX"
    33  )
    34  
    35  // TestAPIKeyDetector_truePositives tests PMAK detection.
    36  func TestAPIKeyDetector_truePositives(t *testing.T) {
    37  	engine, err := veles.NewDetectionEngine(
    38  		[]veles.Detector{postmanapikey.NewAPIKeyDetector()},
    39  	)
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	cases := []struct {
    45  		name  string
    46  		input string
    47  		want  []veles.Secret
    48  	}{{
    49  		name:  "simple matching string",
    50  		input: detectorPMAK,
    51  		want: []veles.Secret{
    52  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    53  		},
    54  	}, {
    55  		name:  "match at end of string",
    56  		input: `POSTMAN_KEY=` + detectorPMAK,
    57  		want: []veles.Secret{
    58  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    59  		},
    60  	}, {
    61  		name:  "match in quotes",
    62  		input: `key="` + detectorPMAK + `"`,
    63  		want: []veles.Secret{
    64  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    65  		},
    66  	}, {
    67  		name:  "multiple matches",
    68  		input: detectorPMAK + "\n" + detectorPMAK,
    69  		want: []veles.Secret{
    70  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    71  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    72  		},
    73  	}, {
    74  		name: "larger_input_containing_key",
    75  		input: fmt.Sprintf("config:\n  api_key: %s\n",
    76  			detectorPMAK),
    77  		want: []veles.Secret{
    78  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    79  		},
    80  	}, {
    81  		name:  "potential match longer than max key length",
    82  		input: detectorPMAK + "EXTRA",
    83  		want: []veles.Secret{
    84  			postmanapikey.PostmanAPIKey{Key: detectorPMAK},
    85  		},
    86  	}}
    87  
    88  	for _, tc := range cases {
    89  		t.Run(tc.name, func(t *testing.T) {
    90  			got, err := engine.Detect(t.Context(),
    91  				strings.NewReader(tc.input))
    92  			if err != nil {
    93  				t.Errorf("Detect() error: %v, want nil", err)
    94  			}
    95  			if diff := cmp.Diff(tc.want, got,
    96  				cmpopts.EquateEmpty()); diff != "" {
    97  				t.Errorf("Detect() diff (-want +got):\n%s",
    98  					diff)
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  // TestAPIKeyDetector_trueNegatives tests PMAK false negatives.
   105  func TestAPIKeyDetector_trueNegatives(t *testing.T) {
   106  	engine, err := veles.NewDetectionEngine(
   107  		[]veles.Detector{postmanapikey.NewAPIKeyDetector()},
   108  	)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	cases := []struct {
   114  		name  string
   115  		input string
   116  		want  []veles.Secret
   117  	}{{
   118  		name:  "empty input",
   119  		input: "",
   120  	}, {
   121  		name:  "short key should not match",
   122  		input: detectorPMAK[:len(detectorPMAK)-5],
   123  	}, {
   124  		name: "invalid_character_in_key_should_not_match",
   125  		input: "PMAK-" + strings.ReplaceAll(
   126  			detectorPMAK[5:], "a", "!",
   127  		),
   128  	}, {
   129  		name:  "incorrect prefix should not match",
   130  		input: "XMAK-" + detectorPMAK[5:],
   131  	}, {
   132  		name:  "prefix missing dash should not match",
   133  		input: "PMAK" + detectorPMAK[5:], // removes the dash
   134  	}}
   135  
   136  	for _, tc := range cases {
   137  		t.Run(tc.name, func(t *testing.T) {
   138  			got, err := engine.Detect(t.Context(),
   139  				strings.NewReader(tc.input))
   140  			if err != nil {
   141  				t.Errorf("Detect() error: %v, want nil", err)
   142  			}
   143  			if diff := cmp.Diff(tc.want, got,
   144  				cmpopts.EquateEmpty()); diff != "" {
   145  				t.Errorf("Detect() diff (-want +got):\n%s",
   146  					diff)
   147  			}
   148  		})
   149  	}
   150  }
   151  
   152  // TestCollectionTokenDetector_truePositives tests PMAT detection.
   153  func TestCollectionTokenDetector_truePositives(t *testing.T) {
   154  	engine, err := veles.NewDetectionEngine(
   155  		[]veles.Detector{postmanapikey.NewCollectionTokenDetector()},
   156  	)
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  
   161  	cases := []struct {
   162  		name  string
   163  		input string
   164  		want  []veles.Secret
   165  	}{{
   166  		name:  "simple matching string",
   167  		input: detectorPMAT,
   168  		want: []veles.Secret{
   169  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   170  		},
   171  	}, {
   172  		name:  "match at end of string",
   173  		input: `PMAT_KEY=` + detectorPMAT,
   174  		want: []veles.Secret{
   175  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   176  		},
   177  	}, {
   178  		name:  "match in quotes",
   179  		input: `secret="` + detectorPMAT + `"`,
   180  		want: []veles.Secret{
   181  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   182  		},
   183  	}, {
   184  		name:  "multiple matches",
   185  		input: detectorPMAT + " " + detectorPMAT,
   186  		want: []veles.Secret{
   187  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   188  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   189  		},
   190  	}, {
   191  		name: "larger_input_containing_key",
   192  		input: fmt.Sprintf("token:\n  value: %s\n",
   193  			detectorPMAT),
   194  		want: []veles.Secret{
   195  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   196  		},
   197  	}, {
   198  		name:  "potential match longer than max key length",
   199  		input: detectorPMAT + "EXTRA",
   200  		want: []veles.Secret{
   201  			postmanapikey.PostmanCollectionToken{Key: detectorPMAT},
   202  		},
   203  	}}
   204  
   205  	for _, tc := range cases {
   206  		t.Run(tc.name, func(t *testing.T) {
   207  			got, err := engine.Detect(t.Context(),
   208  				strings.NewReader(tc.input))
   209  			if err != nil {
   210  				t.Errorf("Detect() error: %v, want nil", err)
   211  			}
   212  			if diff := cmp.Diff(tc.want, got,
   213  				cmpopts.EquateEmpty()); diff != "" {
   214  				t.Errorf("Detect() diff (-want +got):\n%s",
   215  					diff)
   216  			}
   217  		})
   218  	}
   219  }
   220  
   221  // TestCollectionTokenDetector_trueNegatives tests PMAT false negatives.
   222  func TestCollectionTokenDetector_trueNegatives(t *testing.T) {
   223  	engine, err := veles.NewDetectionEngine(
   224  		[]veles.Detector{postmanapikey.NewCollectionTokenDetector()},
   225  	)
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  
   230  	cases := []struct {
   231  		name  string
   232  		input string
   233  		want  []veles.Secret
   234  	}{{
   235  		name:  "empty input",
   236  		input: "",
   237  	}, {
   238  		name:  "short key should not match",
   239  		input: detectorPMAT[:len(detectorPMAT)-2],
   240  	}, {
   241  		name: "invalid_character_in_key_should_not_match",
   242  		input: "PMAT-" + strings.ReplaceAll(
   243  			detectorPMAT[5:], "A", "#",
   244  		),
   245  	}, {
   246  		name:  "incorrect prefix should not match",
   247  		input: "PMAX-" + detectorPMAT[5:],
   248  	}, {
   249  		name:  "prefix missing dash should not match",
   250  		input: "PMAT" + detectorPMAT[5:], // removes the dash
   251  	}}
   252  
   253  	for _, tc := range cases {
   254  		t.Run(tc.name, func(t *testing.T) {
   255  			got, err := engine.Detect(t.Context(),
   256  				strings.NewReader(tc.input))
   257  			if err != nil {
   258  				t.Errorf("Detect() error: %v, want nil", err)
   259  			}
   260  			if diff := cmp.Diff(tc.want, got,
   261  				cmpopts.EquateEmpty()); diff != "" {
   262  				t.Errorf("Detect() diff (-want +got):\n%s",
   263  					diff)
   264  			}
   265  		})
   266  	}
   267  }