@@ -21,6 +21,7 @@ import (
21
21
"io"
22
22
"os/user"
23
23
"regexp"
24
+ "sort"
24
25
"strings"
25
26
"sync"
26
27
"time"
@@ -31,8 +32,18 @@ import (
31
32
"github.com/snyk/go-application-framework/pkg/configuration"
32
33
)
33
34
34
- const redactMask string = "***"
35
35
const MAX_WRITE_RETRIES = 10
36
+ const SANITIZE_REPLACEMENT_STRING string = "***"
37
+
38
+ // SENSITIVE_FIELD_NAMES is a list of field names that should be sanitized.
39
+ var SENSITIVE_FIELD_NAMES = []string {
40
+ "headers" ,
41
+ "user" ,
42
+ "passw" ,
43
+ "token" ,
44
+ "key" ,
45
+ "secret" ,
46
+ }
36
47
37
48
type ScrubbingLogWriter interface {
38
49
AddTerm (term string , matchGroup int )
@@ -146,43 +157,51 @@ func addMandatoryMasking(dict ScrubbingDict) ScrubbingDict {
146
157
groupToRedact : 3 ,
147
158
regex : regexp .MustCompile (s ),
148
159
}
160
+
149
161
s = fmt .Sprintf (`([t|T]oken )(%s)` , charGroup )
150
162
dict [s ] = scrubStruct {
151
163
groupToRedact : 2 ,
152
164
regex : regexp .MustCompile (s ),
153
165
}
166
+
154
167
s = fmt .Sprintf (`([b|B]earer )(%s)` , charGroup )
155
168
dict [s ] = scrubStruct {
156
169
groupToRedact : 2 ,
157
170
regex : regexp .MustCompile (s ),
158
171
}
172
+
159
173
s = fmt .Sprintf (`([b|B]asic )(%s)` , charGroup )
160
174
dict [s ] = scrubStruct {
161
175
groupToRedact : 2 ,
162
176
regex : regexp .MustCompile (s ),
163
177
}
178
+
164
179
s = fmt .Sprintf ("(gh[ps])_(%s)" , charGroup )
165
180
dict [s ] = scrubStruct {
166
181
groupToRedact : 2 ,
167
182
regex : regexp .MustCompile (s ),
168
183
}
184
+
169
185
s = fmt .Sprintf ("(github_pat_)(%s)" , charGroup )
170
186
dict [s ] = scrubStruct {
171
187
groupToRedact : 2 ,
172
188
regex : regexp .MustCompile (s ),
173
189
}
190
+
174
191
// github
175
- s = fmt .Sprintf (" (access_token= )(%s)&" , charGroup )
192
+ s = fmt .Sprintf (` (access_token[\\="\s:]+ )(%s)&?` , charGroup )
176
193
dict [s ] = scrubStruct {
177
194
groupToRedact : 2 ,
178
195
regex : regexp .MustCompile (s ),
179
196
}
180
- s = fmt .Sprintf ("(refresh_token=)(%s)&" , charGroup )
197
+
198
+ s = fmt .Sprintf (`(refresh_token[\\="\s:]+)(%s)&?` , charGroup )
181
199
dict [s ] = scrubStruct {
182
200
groupToRedact : 2 ,
183
201
regex : regexp .MustCompile (s ),
184
202
}
185
- s = fmt .Sprintf (`("token":)"(%s)"` , charGroup )
203
+
204
+ s = fmt .Sprintf (`(token[\\="\s:]+)(%s)&?` , charGroup )
186
205
dict [s ] = scrubStruct {
187
206
groupToRedact : 2 ,
188
207
regex : regexp .MustCompile (s ),
@@ -194,12 +213,72 @@ func addMandatoryMasking(dict ScrubbingDict) ScrubbingDict {
194
213
regex : regexp .MustCompile (s ),
195
214
}
196
215
216
+ // Hide whatever is the current username
197
217
u , err := user .Current ()
198
218
if err == nil {
199
219
s = fmt .Sprintf (`\b%s\b` , regexp .QuoteMeta (u .Username ))
200
220
addTermToDict (s , 0 , dict )
201
221
}
202
222
223
+ // The legacy CLI's snyk-config package prints the entire configuration in debug mode.
224
+ // It begins with some pseudo-JSON structure, which we can redact.
225
+ s = `(?s)_:\s*\[(?<everything_inside_hard_brackets>.*)\]`
226
+ dict [s ] = scrubStruct {
227
+ groupToRedact : 1 ,
228
+ regex : regexp .MustCompile (s ),
229
+ }
230
+
231
+ // JSON-formatted data, in general
232
+ kws := strings .Join (SENSITIVE_FIELD_NAMES , "|" )
233
+ s = fmt .Sprintf (`(?i)"[^"]*(?<json_key>%s)[^"]*"\s*:\s*"(?<json_value>[^"]*)"` , kws )
234
+ dict [s ] = scrubStruct {
235
+ groupToRedact : 2 ,
236
+ regex : regexp .MustCompile (s ),
237
+ }
238
+
239
+ // CLI argument mapping from the snyk-config debug logging
240
+ // I.e., if --argument=value is passed, it will be logged as { 'argument=value': true }
241
+ s = fmt .Sprintf (`(?im)(%s)[^=]*=(?P<value>.*)['"]` , kws )
242
+ dict [s ] = scrubStruct {
243
+ groupToRedact : 2 ,
244
+ regex : regexp .MustCompile (s ),
245
+ }
246
+
247
+ // Same as above, only with short form
248
+ shorts := []string {"p" , "u" }
249
+ shortForm := strings .Join (shorts , "" )
250
+ s = fmt .Sprintf (`(?im)'[%s]=(?<value>.*)'` , shortForm )
251
+ dict [s ] = scrubStruct {
252
+ groupToRedact : 2 ,
253
+ regex : regexp .MustCompile (s ),
254
+ }
255
+
256
+ // Specific short-form scrubbing of the JSON-ish log structures
257
+ // Appear in the snyk-config debug logging as various constellations of { 'u': 'john.doe', } with or without quotes,
258
+ // and values can contain spaces, double and/or single quotes.
259
+
260
+ s = fmt .Sprintf (`(?i)(?<short_form_key>\b[%s]\b)[,'":]+\s*(?:['"](?<short_form_value>.*)['"]|([^,'"\s]+))[,}]?` , shortForm )
261
+ dict [s ] = scrubStruct {
262
+ groupToRedact : 2 ,
263
+ regex : regexp .MustCompile (s ),
264
+ }
265
+
266
+ // CLI argument-style-specific scrubbing
267
+ // Many cases are already covered by the JSON scrubbing above, thus this might seem incomplete.
268
+ // Refer to the unit tests for the full set of covered cases.
269
+ s = fmt .Sprintf (`(?im)\-[%s][\s=](?<short_form_value>\S*)` , shortForm )
270
+ dict [s ] = scrubStruct {
271
+ groupToRedact : 1 ,
272
+ regex : regexp .MustCompile (s ),
273
+ }
274
+
275
+ // Long-form, rest is covered by the JSON scrubbing above
276
+ s = fmt .Sprintf (`(?im)--(?<argument_key>[^=\s]*(?:%s)[^=\s]*)[\s=]['"]?(?<argument_value>\S*)['"]?` , kws )
277
+ dict [s ] = scrubStruct {
278
+ groupToRedact : 2 ,
279
+ regex : regexp .MustCompile (s ),
280
+ }
281
+
203
282
return dict
204
283
}
205
284
@@ -212,10 +291,22 @@ func (w *scrubbingLevelWriter) Write(p []byte) (int, error) {
212
291
213
292
func scrub (p []byte , scrubDict ScrubbingDict ) []byte {
214
293
s := string (p )
215
- for _ , entry := range scrubDict {
294
+
295
+ // The dictionary order is important here, as we want potentially overlapping regexes to be applied
296
+ // in a specific order every time. Since dictionaries are unordered, we sort the keys here.
297
+ keys := make ([]string , 0 , len (scrubDict ))
298
+ for k := range scrubDict {
299
+ keys = append (keys , k )
300
+ }
301
+ sort .Strings (keys )
302
+ for _ , key := range keys {
303
+ entry := scrubDict [key ]
216
304
matches := entry .regex .FindAllStringSubmatch (s , - 1 )
217
305
for _ , match := range matches {
218
- s = strings .Replace (s , match [entry .groupToRedact ], redactMask , - 1 )
306
+ if entry .groupToRedact >= len (match ) || match [entry .groupToRedact ] == "" {
307
+ continue
308
+ }
309
+ s = strings .Replace (s , match [entry .groupToRedact ], SANITIZE_REPLACEMENT_STRING , - 1 )
219
310
}
220
311
}
221
312
return []byte (s )
0 commit comments