github.com/google/osv-scalibr@v0.4.1/veles/secrets/slacktoken/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 slacktoken_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/slacktoken"
    26  )
    27  
    28  const testAppLevelToken = `xapp-1-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af`
    29  const testAppConfigAccessToken = `xoxe.xoxp-1-Mi0yLTk1NTI2NjcxMzI3ODYtOTU1MjY2NzEzMzI1MC05NTUyODA2ODE4OTk0LTk1NTI4MDY4MzYxOTQtNWI4NzRmYjU0MTdhZGM3MjYyZmQ5MzNjNGQwMWJhZjhmY2VhMzIyMmQ4NGY4MDZlNjkyYjM5NTMwMjFiZTgwNA`
    30  const testAppConfigRefreshToken = `xoxe-1-My0xLTk1NTI2NjcxMzI3ODYtOTU1MjgwNjgxODk5NC05NTUyODA2ODcxNTU0LTk3Y2UxYWRlYWRlZjhhOWY5ZDRlZTVlOTI4MTRjNWZmYWZlZDU4MTU2OGZhNTIyNmVlYzY5MDE1ZmZmY2FkNTY`
    31  
    32  // TestDetector_truePositives tests for cases where we know the Detector
    33  // will find Slack tokens (App Level Tokens, App Configuration Access Tokens,
    34  // and App Configuration Refresh Tokens).
    35  func TestDetector_truePositives(t *testing.T) {
    36  	engine, err := veles.NewDetectionEngine([]veles.Detector{
    37  		slacktoken.NewAppLevelTokenDetector(),
    38  		slacktoken.NewAppConfigAccessTokenDetector(),
    39  		slacktoken.NewAppConfigRefreshTokenDetector(),
    40  	})
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	cases := []struct {
    45  		name  string
    46  		input string
    47  		want  []veles.Secret
    48  	}{{
    49  		name:  "simple matching string - app level token",
    50  		input: testAppLevelToken,
    51  		want: []veles.Secret{
    52  			slacktoken.SlackAppLevelToken{
    53  				Token: testAppLevelToken,
    54  			},
    55  		},
    56  	}, {
    57  		name:  "match at end of string - app config refresh token",
    58  		input: `SL_TOKEN=` + testAppConfigRefreshToken,
    59  		want: []veles.Secret{
    60  			slacktoken.SlackAppConfigRefreshToken{
    61  				Token: testAppConfigRefreshToken,
    62  			},
    63  		},
    64  	}, {
    65  		name:  "match with 5 numbers in second place - app level token",
    66  		input: "xapp-12345-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af",
    67  		want: []veles.Secret{
    68  			slacktoken.SlackAppLevelToken{
    69  				Token: "xapp-12345-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af",
    70  			},
    71  		},
    72  	}, {
    73  		name:  "match in middle of string - app level token",
    74  		input: `SL_TOKEN="` + testAppLevelToken + `"`,
    75  		want: []veles.Secret{
    76  			slacktoken.SlackAppLevelToken{
    77  				Token: testAppLevelToken,
    78  			},
    79  		},
    80  	}, {
    81  		name:  "multiple matches - app level tokens",
    82  		input: testAppLevelToken + testAppLevelToken + testAppLevelToken,
    83  		want: []veles.Secret{
    84  			slacktoken.SlackAppLevelToken{
    85  				Token: testAppLevelToken,
    86  			},
    87  			slacktoken.SlackAppLevelToken{
    88  				Token: testAppLevelToken,
    89  			},
    90  			slacktoken.SlackAppLevelToken{
    91  				Token: testAppLevelToken,
    92  			},
    93  		},
    94  	}, {
    95  		name:  "multiple distinct matches - app level tokens",
    96  		input: testAppLevelToken + "\n" + testAppLevelToken[:len(testAppLevelToken)-1] + "a",
    97  		want: []veles.Secret{
    98  			slacktoken.SlackAppLevelToken{
    99  				Token: testAppLevelToken,
   100  			},
   101  			slacktoken.SlackAppLevelToken{
   102  				Token: testAppLevelToken[:len(testAppLevelToken)-1] + "a",
   103  			},
   104  		},
   105  	}, {
   106  		name: "larger_input_containing_key_-_app_level_token",
   107  		input: fmt.Sprintf(`
   108  :test_api_key: do-test
   109  :SL_TOKEN: %s
   110  		`, testAppLevelToken),
   111  		want: []veles.Secret{
   112  			slacktoken.SlackAppLevelToken{
   113  				Token: testAppLevelToken,
   114  			},
   115  		},
   116  	}, {
   117  		name:  "potential match longer than max key length - app level token",
   118  		input: testAppLevelToken + `extra`,
   119  		want: []veles.Secret{
   120  			slacktoken.SlackAppLevelToken{
   121  				Token: testAppLevelToken,
   122  			},
   123  		},
   124  	}, {
   125  		name:  "app config access token",
   126  		input: testAppConfigAccessToken,
   127  		want: []veles.Secret{
   128  			slacktoken.SlackAppConfigAccessToken{
   129  				Token: testAppConfigAccessToken,
   130  			},
   131  		},
   132  	}, {
   133  		name:  "app config refresh token",
   134  		input: testAppConfigRefreshToken,
   135  		want: []veles.Secret{
   136  			slacktoken.SlackAppConfigRefreshToken{
   137  				Token: testAppConfigRefreshToken,
   138  			},
   139  		},
   140  	}, {
   141  		name:  "multiple token types",
   142  		input: testAppLevelToken + "\n" + testAppConfigAccessToken + "\n" + testAppConfigRefreshToken,
   143  		want: []veles.Secret{
   144  			slacktoken.SlackAppLevelToken{
   145  				Token: testAppLevelToken,
   146  			},
   147  			slacktoken.SlackAppConfigAccessToken{
   148  				Token: testAppConfigAccessToken,
   149  			},
   150  			slacktoken.SlackAppConfigRefreshToken{
   151  				Token: testAppConfigRefreshToken,
   152  			},
   153  		},
   154  	}}
   155  	for _, tc := range cases {
   156  		t.Run(tc.name, func(t *testing.T) {
   157  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   158  			if err != nil {
   159  				t.Errorf("Detect() error: %v, want nil", err)
   160  			}
   161  			fmt.Printf("got = %+v\n", got)
   162  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   163  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   164  			}
   165  		})
   166  	}
   167  }
   168  
   169  // TestDetector_trueNegatives tests for cases where we know the Detector
   170  // will not find Slack tokens (App Level Tokens, App Configuration Access Tokens,
   171  // and App Configuration Refresh Tokens).
   172  func TestDetector_trueNegatives(t *testing.T) {
   173  	engine, err := veles.NewDetectionEngine([]veles.Detector{
   174  		slacktoken.NewAppLevelTokenDetector(),
   175  		slacktoken.NewAppConfigAccessTokenDetector(),
   176  		slacktoken.NewAppConfigRefreshTokenDetector(),
   177  	})
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  	cases := []struct {
   182  		name  string
   183  		input string
   184  		want  []veles.Secret
   185  	}{{
   186  		name:  "empty input",
   187  		input: "",
   188  	}, {
   189  		name:  "short app level token should not match",
   190  		input: testAppLevelToken[:len(testAppLevelToken)-1],
   191  	}, {
   192  		name:  "more than 10 numbers in second place should not match - app level token",
   193  		input: "xapp-12345678910-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af",
   194  	}, {
   195  		name:  "invalid character in app level token should not match",
   196  		input: `xapp-1-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421ag`, // 'g' instead of 'f'
   197  	}, {
   198  		name:  "incorrect prefix for app level token should not match",
   199  		input: `zapp-2-B09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af`,
   200  	}, {
   201  		name:  "app level token prefix missing dash should not match",
   202  		input: `xapp1-A09GDGLM2BE-9538001315143-31fd9c18d0c0c3e9638a7634d01d1ab001d3453ad209e168d5d49b589f0421af`,
   203  	}, {
   204  		name:  "short app config access token should not match",
   205  		input: testAppConfigAccessToken[:len(testAppConfigAccessToken)-1],
   206  	}, {
   207  		name: "invalid_character_in_app_config_access_token_should_not_match",
   208  		input: testAppConfigAccessToken[:len(testAppConfigAccessToken)-2] +
   209  			"@" +
   210  			testAppConfigAccessToken[len(testAppConfigAccessToken)-1:],
   211  	}, {
   212  		name:  "short app config refresh token should not match",
   213  		input: testAppConfigRefreshToken[:len(testAppConfigRefreshToken)-1],
   214  	}, {
   215  		name: "invalid_character_in_app_config_refresh_token_should_not_match",
   216  		input: testAppConfigRefreshToken[:len(testAppConfigRefreshToken)-2] +
   217  			"!" +
   218  			testAppConfigRefreshToken[len(testAppConfigRefreshToken)-1:],
   219  	}, {
   220  		name:  "invalid app config access token prefix should not match",
   221  		input: `xoxe.xoxq-1-Mi0yLTk1NTI2NjcxMzI3ODYtOTU1MjY2NzEzMzI1MC05NTUyODA2ODE4OTk0LTk1NTI4MDY4MzYxOTQtNWI4NzRmYjU0MTdhZGM3MjYyZmQ5MzNjNGQwMWJhZjhmY2VhMzIyMmQ4NGY4MDZlNjkyYjM5NTMwMjFiZTgwNA`, // 'xoxq' instead of 'xoxp'
   222  	}, {
   223  		name:  "invalid app config refresh token prefix should not match",
   224  		input: `xoxf-1-My0xLTk1NTI2NjcxMzI3ODYtOTU1MjgwNjgxODk5NC05NTUyODA2ODcxNTU0LTk3Y2UxYWRlYWRlZjhhOWY5ZDRlZTVlOTI4MTRjNWZmYWZlZDU4MTU2OGZhNTIyNmVlYzY5MDE1ZmZmY2FkNTY`, // 'xoxf' instead of 'xoxe'
   225  	}}
   226  	for _, tc := range cases {
   227  		t.Run(tc.name, func(t *testing.T) {
   228  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   229  			if err != nil {
   230  				t.Errorf("Detect() error: %v, want nil", err)
   231  			}
   232  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   233  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   234  			}
   235  		})
   236  	}
   237  }