Skip to content

Commit e4325b1

Browse files
afdeskDmitriyLewen
andauthored
feat(secret): add a way to customize skipped folders, files and exts (aquasecurity#10550)
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
1 parent ea7e9ad commit e4325b1

10 files changed

Lines changed: 346 additions & 60 deletions

File tree

docs/guide/scanner/secret.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,45 @@ disable-allow-rules:
231231
- markdown
232232
```
233233

234+
### Skip Patterns
235+
236+
By default, Trivy skips the following paths during secret scanning (expressed as [doublestar](https://github.com/bmatcuk/doublestar) glob patterns):
237+
238+
```
239+
**/.git/** **/node_modules/**
240+
**/go.mod **/go.sum **/package-lock.json
241+
**/yarn.lock **/pnpm-lock.yaml **/Pipfile.lock **/Gemfile.lock
242+
**/*.jpg **/*.png **/*.gif **/*.doc **/*.pdf **/*.bin
243+
**/*.svg **/*.socket **/*.deb **/*.rpm
244+
**/*.zip **/*.gz **/*.gzip **/*.tar
245+
```
246+
You can see a full list of default skip patterns [here][default-secret-patterns].
247+
248+
You can override this list with `skip-patterns` in the configuration file.
249+
250+
!!! warning
251+
When `skip-patterns` is specified, it **replaces** the default list entirely — defaults are not merged.
252+
To keep the defaults and add new patterns, include them explicitly.
253+
254+
``` yaml
255+
skip-patterns:
256+
- "**/vendor/**"
257+
- "**/testdata/**"
258+
- "**/custom.lock"
259+
- "**/*.xyz"
260+
```
261+
262+
To disable all skipping, set it to an empty list:
263+
264+
``` yaml
265+
skip-patterns: []
266+
```
267+
234268
## Recommendation
235-
We would recommend specifying `--skip-dirs` for faster secret scanning.
269+
We would recommend specifying `--skip-dirs` or `--skip-files` for faster secret scanning. Also there is a way to use [skip-patterns](#skip-patterns) in the secret config to speed up your scanning.
236270
In container image scanning, Trivy walks the file tree rooted at `/` and scans all the files other than [built-in allowed paths][builtin-allow].
237271
It will take a while if your image contains a lot of files even though Trivy tries to avoid scanning layers from a base image.
238-
If you want to make scanning faster, `--skip-dirs` and `--skip-files` helps so that Trivy will skip scanning those files and directories.
272+
Adding glob patterns such as `**/vendor/**` helps so that Trivy will skip those paths entirely.
239273
You can see more options [here](../configuration/others.md).
240274

241275
`allow-rules` is also helpful. See the [allow-rules](#allow-rules) section.
@@ -311,6 +345,7 @@ This feature is inspired by [gitleaks][gitleaks].
311345

312346
[builtin]: https://github.com/aquasecurity/trivy/blob/{{ git.tag }}/pkg/fanal/secret/builtin-rules.go
313347
[builtin-allow]: https://github.com/aquasecurity/trivy/blob/{{ git.tag }}/pkg/fanal/secret/builtin-allow-rules.go
348+
[default-secret-patterns]: https://github.com/aquasecurity/trivy/blob/{{ git.tag }}/pkg/fanal/secret/scanner.go
314349
[gitleaks]: https://github.com/gitleaks/gitleaks
315350

316351
[builtin]: https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go

pkg/fanal/analyzer/secret/secret.go

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"path/filepath"
99
"slices"
10-
"strings"
1110

1211
"github.com/samber/lo"
1312
"golang.org/x/xerrors"
@@ -24,41 +23,9 @@ var _ analyzer.Initializer = &SecretAnalyzer{}
2423

2524
const version = 1
2625

27-
var (
28-
skipFiles = []string{
29-
"go.mod",
30-
"go.sum",
31-
"package-lock.json",
32-
"yarn.lock",
33-
"pnpm-lock.yaml",
34-
"Pipfile.lock",
35-
"Gemfile.lock",
36-
}
37-
skipDirs = []string{
38-
".git",
39-
"node_modules",
40-
}
41-
skipExts = []string{
42-
".jpg",
43-
".png",
44-
".gif",
45-
".doc",
46-
".pdf",
47-
".bin",
48-
".svg",
49-
".socket",
50-
".deb",
51-
".rpm",
52-
".zip",
53-
".gz",
54-
".gzip",
55-
".tar",
56-
}
57-
58-
allowedBinaries = []string{
59-
".pyc",
60-
}
61-
)
26+
var allowedBinaries = []string{
27+
".pyc",
28+
}
6229

6330
func init() {
6431
// The scanner will be initialized later via InitScanner()
@@ -143,35 +110,16 @@ func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput
143110
}
144111

145112
func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool {
146-
// Skip small files
147113
if fi.Size() < 10 {
148114
return false
149115
}
150116

151-
dir, fileName := filepath.Split(filePath)
152-
dir = filepath.ToSlash(dir)
153-
dirs := strings.Split(dir, "/")
154-
155-
// Check if the directory should be skipped
156-
for _, skipDir := range skipDirs {
157-
if slices.Contains(dirs, skipDir) {
158-
return false
159-
}
160-
}
161-
162-
// Check if the file should be skipped
163-
if slices.Contains(skipFiles, fileName) {
164-
return false
165-
}
166-
167117
// Skip the config file for secret scanning
168118
if filepath.Base(a.configPath) == filePath {
169119
return false
170120
}
171121

172-
// Check if the file extension should be skipped
173-
ext := filepath.Ext(fileName)
174-
if slices.Contains(skipExts, ext) {
122+
if a.scanner.IsSkipped(filePath) {
175123
return false
176124
}
177125

pkg/fanal/analyzer/secret/secret_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,106 @@ func TestSecretRequire(t *testing.T) {
258258
})
259259
}
260260
}
261+
262+
func TestSecretRequireCustomSkips(t *testing.T) {
263+
// Custom config replaces the default skip-patterns entirely.
264+
// Verify that custom patterns are skipped and former defaults are no longer skipped.
265+
tests := []struct {
266+
name string
267+
filePath string
268+
want bool
269+
}{
270+
{
271+
name: "skip custom dir (vendor)",
272+
filePath: "testdata/vendor/secret.txt",
273+
want: false,
274+
},
275+
{
276+
name: "no longer skip default dir (node_modules)",
277+
filePath: "testdata/node_modules/secret.txt",
278+
want: true,
279+
},
280+
{
281+
name: "skip custom file (custom.lock)",
282+
filePath: "testdata/custom.lock",
283+
want: false,
284+
},
285+
{
286+
name: "no longer skip default file (package-lock.json)",
287+
filePath: "testdata/package-lock.json",
288+
want: true,
289+
},
290+
{
291+
name: "skip custom extension (.xyz)",
292+
filePath: "testdata/secret.xyz",
293+
want: false,
294+
},
295+
{
296+
name: "no longer skip default extension (.doc)",
297+
filePath: "testdata/secret.doc",
298+
want: true,
299+
},
300+
}
301+
302+
for _, tt := range tests {
303+
t.Run(tt.name, func(t *testing.T) {
304+
a := secret.SecretAnalyzer{}
305+
err := a.Init(analyzer.AnalyzerOptions{
306+
SecretScannerOption: analyzer.SecretScannerOption{
307+
ConfigPath: "testdata/custom-skip-config.yaml",
308+
},
309+
})
310+
require.NoError(t, err)
311+
312+
fi, err := os.Stat(tt.filePath)
313+
require.NoError(t, err)
314+
315+
got := a.Required(tt.filePath, fi)
316+
assert.Equal(t, tt.want, got)
317+
})
318+
}
319+
}
320+
321+
func TestSecretRequireEmptySkips(t *testing.T) {
322+
// When skip-patterns is explicitly set to empty, nothing should be skipped —
323+
// even paths that match the default skip patterns.
324+
tests := []struct {
325+
name string
326+
filePath string
327+
want bool
328+
}{
329+
{
330+
name: "default skip dir (node_modules) is no longer skipped",
331+
filePath: "testdata/node_modules/secret.txt",
332+
want: true,
333+
},
334+
{
335+
name: "default skip file (package-lock.json) is no longer skipped",
336+
filePath: "testdata/package-lock.json",
337+
want: true,
338+
},
339+
{
340+
name: "default skip extension (.doc) is no longer skipped",
341+
filePath: "testdata/secret.doc",
342+
want: true,
343+
},
344+
}
345+
346+
for _, tt := range tests {
347+
t.Run(tt.name, func(t *testing.T) {
348+
a := secret.SecretAnalyzer{}
349+
err := a.Init(analyzer.AnalyzerOptions{
350+
SecretScannerOption: analyzer.SecretScannerOption{
351+
ConfigPath: "testdata/empty-skip-config.yaml",
352+
},
353+
})
354+
require.NoError(t, err)
355+
356+
fi, err := os.Stat(tt.filePath)
357+
require.NoError(t, err)
358+
359+
got := a.Required(tt.filePath, fi)
360+
assert.Equal(t, tt.want, got)
361+
})
362+
}
363+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
disable-allow-rules:
2+
- tests
3+
skip-patterns:
4+
- "**/vendor/**"
5+
- "**/custom.lock"
6+
- "**/*.xyz"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--- ignore block start ---
2+
generic secret line secret="somevalue"
3+
--- ignore block stop ---
4+
secret="othervalue"
5+
credentials: { user: "username" password: "123456789" }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
disable-allow-rules:
2+
- tests
3+
skip-patterns: []
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--- ignore block start ---
2+
generic secret line secret="somevalue"
3+
--- ignore block stop ---
4+
secret="othervalue"
5+
credentials: { user: "username" password: "123456789" }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--- ignore block start ---
2+
generic secret line secret="somevalue"
3+
--- ignore block stop ---
4+
secret="othervalue"
5+
credentials: { user: "username" password: "123456789" }

0 commit comments

Comments
 (0)