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 }