Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .envguard.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ entropy_threshold: 4.5
min_length: 20
max_file_size_kb: 500
exclude_paths:
- "testdata/**"
- "**/*.test.js"
- "vendor/**"
exclude_extensions:
- ".lock"
- ".svg"
- ".png"
entropy_exclude_paths:
- "testdata/**"
- "fixtures/**"
custom_patterns:
- name: "Internal Token"
pattern: "MYCO_[A-Z0-9]{32}"
severity: "HIGH"
allow_test_fixtures: false
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ The entropy engine tokenizes each scanned line, measures Shannon entropy, and fl
| `max_file_size_kb` | `int` | `500` | Skip files larger than this limit with a warning. |
| `exclude_paths` | `[]string` | `["testdata/**","**/*.test.js","vendor/**"]` | Glob patterns excluded from scanning. |
| `exclude_extensions` | `[]string` | `[".lock",".svg",".png"]` | File extensions excluded from scanning. |
| `entropy_exclude_paths` | `[]string` | `[]` | Glob patterns that skip entropy scanning only while keeping pattern matching enabled for files that are still included by `exclude_paths`. |
| `custom_patterns` | `[]pattern` | `[]` | Extra regex rules added to the built-in pattern library. |
| `allow_test_fixtures` | `bool` | `false` | Skip entropy scanning for files under `testdata/`. |

Example:

Expand All @@ -79,20 +79,24 @@ entropy_threshold: 4.5
min_length: 20
max_file_size_kb: 500
exclude_paths:
- "testdata/**"
- "**/*.test.js"
- "vendor/**"
exclude_extensions:
- ".lock"
- ".png"
- ".svg"
entropy_exclude_paths:
- "testdata/**"
- "fixtures/**"
custom_patterns:
- name: "Internal Token"
pattern: "MYCO_[A-Z0-9]{32}"
severity: "HIGH"
allow_test_fixtures: false
```

Note:
`exclude_paths` is applied before scanning starts. If a path is excluded there, `entropy_exclude_paths` will never see it. To keep pattern matching enabled for `testdata/` while suppressing entropy checks, remove `testdata/**` from `exclude_paths` and add it to `entropy_exclude_paths` instead.

## CLI Reference

### `envguard check [path]`
Expand Down
4 changes: 2 additions & 2 deletions config/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestLoadFindsConfigFromParentDirectory(t *testing.T) {
require.NoError(t, os.MkdirAll(nestedDir, 0o755))

configPath := filepath.Join(repoRoot, ".envguard.yml")
configBody := []byte("entropy_threshold: 5.1\nmin_length: 12\nallow_test_fixtures: true\n")
configBody := []byte("entropy_threshold: 5.1\nmin_length: 12\nentropy_exclude_paths:\n - fixtures/**\n")
require.NoError(t, os.WriteFile(configPath, configBody, 0o644))

cfg, loadedPath, err := Load(nestedDir)
Expand All @@ -24,5 +24,5 @@ func TestLoadFindsConfigFromParentDirectory(t *testing.T) {
assert.Equal(t, configPath, loadedPath)
assert.Equal(t, 5.1, cfg.EntropyThreshold)
assert.Equal(t, 12, cfg.MinLength)
assert.True(t, cfg.AllowTestFixtures)
assert.Equal(t, []string{"fixtures/**"}, cfg.EntropyExcludePaths)
}
4 changes: 2 additions & 2 deletions config/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ type Config struct {
ExcludePaths []string `json:"exclude_paths" yaml:"exclude_paths"`
// ExcludeExtensions contains file extensions that should not be scanned.
ExcludeExtensions []string `json:"exclude_extensions" yaml:"exclude_extensions"`
// EntropyExcludePaths contains glob patterns for files or directories that should skip entropy scanning only.
EntropyExcludePaths []string `json:"entropy_exclude_paths" yaml:"entropy_exclude_paths"`
// CustomPatterns contains user-defined regex rules appended to the built-in pattern set.
CustomPatterns []CustomPattern `json:"custom_patterns" yaml:"custom_patterns"`
// AllowTestFixtures skips entropy scanning for files under testdata when enabled.
AllowTestFixtures bool `json:"allow_test_fixtures" yaml:"allow_test_fixtures"`
}

// CustomPattern defines a user-provided regex-based secret detection rule.
Expand Down
12 changes: 11 additions & 1 deletion scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (e *Engine) scanFile(path string) ([]Finding, error) {
line := scanner.Text()
patternFindings, spans := e.scanPatterns(relative, lineNumber, line)
findings = append(findings, patternFindings...)
if e.cfg.AllowTestFixtures && isTestdataPath(relative) {
if e.shouldSkipEntropy(relative) {
continue
}
findings = append(findings, e.scanEntropy(relative, lineNumber, line, spans)...)
Expand Down Expand Up @@ -339,6 +339,16 @@ func isTestdataPath(path string) bool {
return strings.Contains(filepath.ToSlash(path), "/testdata/") || strings.HasPrefix(filepath.ToSlash(path), "testdata/")
}

func (e *Engine) shouldSkipEntropy(path string) bool {
normalized := filepath.ToSlash(path)
for _, pattern := range e.cfg.EntropyExcludePaths {
if matchGlob(pattern, normalized) {
return true
}
}
return false
}

func matchGlob(pattern string, target string) bool {
pattern = filepath.ToSlash(pattern)
target = filepath.ToSlash(target)
Expand Down
40 changes: 40 additions & 0 deletions scanner/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,46 @@ func TestEnvFileDetectionCanBeAllowlisted(t *testing.T) {
assert.Empty(t, findings)
}

func TestEntropyExcludePathsSkipsNonTestdataFixtures(t *testing.T) {
tempDir := t.TempDir()
fixturesDir := filepath.Join(tempDir, "fixtures")
require.NoError(t, os.MkdirAll(fixturesDir, 0o755))

target := filepath.Join(fixturesDir, "sample.txt")
require.NoError(t, os.WriteFile(target, []byte("token=abcd1234efgh5678ijkl9012mnop3456\n"), 0o644))

cfg := config.Default()
cfg.ExcludePaths = nil
cfg.EntropyExcludePaths = []string{"fixtures/**"}

engine, err := NewEngineWithRoot(cfg, allowlist.Set{}, tempDir)
require.NoError(t, err)

findings, err := engine.ScanPaths([]string{target})
require.NoError(t, err)
assert.Empty(t, findings)
}

func TestEntropyExcludePathsCanSkipTestdataEntropy(t *testing.T) {
tempDir := t.TempDir()
testdataDir := filepath.Join(tempDir, "testdata")
require.NoError(t, os.MkdirAll(testdataDir, 0o755))

target := filepath.Join(testdataDir, "sample.txt")
require.NoError(t, os.WriteFile(target, []byte("token=abcd1234efgh5678ijkl9012mnop3456\n"), 0o644))

cfg := config.Default()
cfg.ExcludePaths = nil
cfg.EntropyExcludePaths = []string{"testdata/**"}

engine, err := NewEngineWithRoot(cfg, allowlist.Set{}, tempDir)
require.NoError(t, err)

findings, err := engine.ScanPaths([]string{target})
require.NoError(t, err)
assert.Empty(t, findings)
}

func chdirForTest(t *testing.T, dir string) {
t.Helper()
wd, err := os.Getwd()
Expand Down
Loading