6
6
"fmt"
7
7
"io"
8
8
"net/http"
9
+ "path"
10
+ "strings"
9
11
10
12
regexp "github.com/wasilibs/go-re2"
11
13
@@ -15,13 +17,41 @@ import (
15
17
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
16
18
)
17
19
20
+ // Define error code constants for better maintainability
21
+ const (
22
+ // Success codes
23
+ CodeSuccess = 0
24
+ CodeSpecialSuccess = 99991672 // Based on old code - couldn't find documentation for this code
25
+
26
+ // HTTP 200 response codes - token invalidity
27
+ CodeInvalidToken = 20005 // The user access token passed is invalid
28
+ CodeUserNotExist = 20008 // User not exist
29
+ CodeUserResigned = 20021 // User resigned
30
+ CodeUserFrozen = 20022 // User frozen
31
+ CodeUserNotRegistered = 20023 // User not registered
32
+
33
+ // Undocumented error codes - these are found in the API responses but not in the documentation
34
+ CodeUndocumentedInvalid1 = 99991663 // Invalid access token for authorization. Please make a request with token attached.
35
+ CodeUndocumentedInvalid2 = 99991668 // Invalid access token for authorization. Please make a request with token attached.
36
+ )
37
+
38
+ // VerificationResult represents the outcome of token verification
39
+ type VerificationResult int
40
+
41
+ const (
42
+ VerificationValid VerificationResult = iota // Token is valid
43
+ VerificationInvalid // Token is definitively invalid
44
+ VerificationError // Error occurred during verification
45
+ )
46
+
18
47
type Scanner struct {
19
48
detectors.DefaultMultiPartCredentialProvider
20
49
client * http.Client
21
50
}
22
51
23
52
// Check that the LarkSuite scanner implements the SecretScanner interface at compile time.
24
53
var _ detectors.Detector = Scanner {}
54
+ var _ detectors.CustomFalsePositiveChecker = (* Scanner )(nil )
25
55
26
56
type tokenType string
27
57
@@ -52,6 +82,14 @@ func (s Scanner) Keywords() []string {
52
82
return []string {"lark" , "larksuite" }
53
83
}
54
84
85
+ func (s Scanner ) Type () detectorspb.DetectorType {
86
+ return detectorspb .DetectorType_LarkSuite
87
+ }
88
+
89
+ func (s Scanner ) Description () string {
90
+ return "LarkSuite is a collaborative suite that includes chat, calendar, and cloud storage features. The detected token can be used to access and interact with these services."
91
+ }
92
+
55
93
// FromData will find and optionally verify Larksuite secrets in a given set of bytes.
56
94
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
57
95
dataStr := string (data )
@@ -93,20 +131,14 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
93
131
return results , nil
94
132
}
95
133
96
- func (s Scanner ) Type () detectorspb.DetectorType {
97
- return detectorspb .DetectorType_LarkSuite
98
- }
99
-
100
- func (s Scanner ) Description () string {
101
- return "LarkSuite is a collaborative suite that includes chat, calendar, and cloud storage features. The detected token can be used to access and interact with these services."
102
- }
103
-
104
134
func verifyAccessToken (ctx context.Context , client * http.Client , url string , token string ) (bool , error ) {
105
135
req , err := http .NewRequestWithContext (ctx , http .MethodGet , url , nil )
106
136
if err != nil {
107
137
return false , err
108
138
}
139
+
109
140
req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , token ))
141
+
110
142
res , err := client .Do (req )
111
143
if err != nil {
112
144
return false , err
@@ -115,26 +147,146 @@ func verifyAccessToken(ctx context.Context, client *http.Client, url string, tok
115
147
_ , _ = io .Copy (io .Discard , res .Body )
116
148
_ = res .Body .Close ()
117
149
}()
118
- switch res .StatusCode {
119
- case http .StatusOK , http .StatusBadRequest :
120
- var bodyResponse verificationResponse
121
- if err := json .NewDecoder (res .Body ).Decode (& bodyResponse ); err != nil {
122
- err = fmt .Errorf ("failed to decode response: %w" , err )
123
- return false , err
124
- } else {
125
- if bodyResponse .Code == 0 || bodyResponse .Code == 99991672 {
126
- return true , nil
127
- } else {
128
- return false , fmt .Errorf ("unexpected verification response code %d, message %s" , bodyResponse .Code , bodyResponse .Message )
129
- }
130
- }
150
+
151
+ // For LarkSuite API, we expect JSON responses for all documented status codes
152
+ var bodyResponse verificationResponse
153
+ if err := json .NewDecoder (res .Body ).Decode (& bodyResponse ); err != nil {
154
+ return false , fmt .Errorf ("failed to decode response (status %d): %w" , res .StatusCode , err )
155
+ }
156
+
157
+ // Handle based on error code classification
158
+ result := classifyErrorCode (bodyResponse .Code )
159
+
160
+ switch result {
161
+ case VerificationValid :
162
+ return true , nil
163
+
164
+ case VerificationInvalid :
165
+ return false , nil
166
+
167
+ case VerificationError :
168
+ // All other cases: system errors, rate limits, permission issues, etc.
169
+ // Return error so token is marked as "unverified" (couldn't verify)
170
+ return false , fmt .Errorf ("verification failed (status %d, code %d): %s" ,
171
+ res .StatusCode , bodyResponse .Code , bodyResponse .Message )
172
+
131
173
default :
132
- // 500 larksuite was unable to generate a result
133
- return false , err
174
+ return false , fmt .Errorf ("unexpected status code: %d" , res .StatusCode )
134
175
}
135
176
}
136
177
137
178
type verificationResponse struct {
138
179
Code int `json:"code"`
139
180
Message string `json:"msg"`
140
181
}
182
+
183
+ // classifyErrorCode determines how to handle different error codes based on their meaning
184
+ func classifyErrorCode (code int ) VerificationResult {
185
+ // based on the documentation, API fails if response code is not zero
186
+ // https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/user_info/get
187
+ // https://open.larksuite.com/document/server-docs/calendar-v4/calendar/list
188
+ // https://open.larksuite.com/document/server-docs/tenant-v2/tenant/query
189
+ switch code {
190
+ case CodeSuccess , CodeSpecialSuccess :
191
+ return VerificationValid
192
+
193
+ // HTTP 200 codes that indicate definitive token invalidity
194
+ case CodeInvalidToken , CodeUserNotExist , CodeUserResigned , CodeUserFrozen , CodeUserNotRegistered , CodeUndocumentedInvalid1 ,
195
+ CodeUndocumentedInvalid2 :
196
+ return VerificationInvalid
197
+
198
+ // All other error codes are treated as verification errors
199
+ // This includes:
200
+ // - Invalid requests (20001)
201
+ // - System errors (20050, 190003)
202
+ // - Rate limits (190004, 190005, 190010)
203
+ // - Permission issues (190006, 191002, 191003, 191004, 1184001)
204
+ // - Resource not found (190007, 191000, 195100, 1184000)
205
+ // - Parameter issues (190002, 190008, 190009, 191001)
206
+ default :
207
+ return VerificationError
208
+ }
209
+ }
210
+
211
+ func (s Scanner ) IsFalsePositive (result detectors.Result ) (bool , string ) {
212
+ if _ , ok := commonFileExtensions [strings .ToLower (path .Ext (string (result .Raw )))]; ok {
213
+ return ok , ""
214
+ }
215
+
216
+ // back to the default false positive checks
217
+ return detectors .IsKnownFalsePositive (string (result .Raw ), detectors .DefaultFalsePositives , true )
218
+ }
219
+
220
+ var commonFileExtensions = map [string ]struct {}{
221
+ // Web files
222
+ ".html" : {},
223
+ ".htm" : {},
224
+ ".css" : {},
225
+ ".js" : {},
226
+ ".json" : {},
227
+ ".xml" : {},
228
+ ".svg" : {},
229
+ ".php" : {},
230
+ ".asp" : {},
231
+ ".aspx" : {},
232
+ ".jsp" : {},
233
+
234
+ // Document files
235
+ ".txt" : {},
236
+ ".md" : {},
237
+ ".pdf" : {},
238
+ ".doc" : {},
239
+ ".docx" : {},
240
+ ".rtf" : {},
241
+
242
+ // Data files
243
+ ".csv" : {},
244
+ ".xlsx" : {},
245
+ ".xls" : {},
246
+ ".sql" : {},
247
+ ".db" : {},
248
+
249
+ // Config files
250
+ ".conf" : {},
251
+ ".config" : {},
252
+ ".ini" : {},
253
+ ".yaml" : {},
254
+ ".yml" : {},
255
+ ".toml" : {},
256
+
257
+ // Log files
258
+ ".log" : {},
259
+ ".out" : {},
260
+ ".err" : {},
261
+
262
+ // Archive files
263
+ ".zip" : {},
264
+ ".tar" : {},
265
+ ".gz" : {},
266
+ ".rar" : {},
267
+
268
+ // Image files
269
+ ".png" : {},
270
+ ".jpg" : {},
271
+ ".jpeg" : {},
272
+ ".gif" : {},
273
+ ".bmp" : {},
274
+ ".ico" : {},
275
+
276
+ // Source code files
277
+ ".go" : {},
278
+ ".py" : {},
279
+ ".java" : {},
280
+ ".cpp" : {},
281
+ ".c" : {},
282
+ ".h" : {},
283
+ ".rb" : {},
284
+ ".rs" : {},
285
+ ".ts" : {},
286
+
287
+ // Other common files
288
+ ".tmp" : {},
289
+ ".bak" : {},
290
+ ".old" : {},
291
+ ".lock" : {},
292
+ }
0 commit comments