Skip to content

Commit 9c17c20

Browse files
Add gitignore support to lint command (#634)
* Add gitignore support to lint command - Respect .gitignore patterns during resource discovery - Support explicit bypass when config path references gitignored directory - Backward compatible - no breaking changes - Comprehensive test coverage with 28 new tests - Performance impact < 1% * fixing hidden files issue * Security fix: Disable global gitignore support Disable loadGlobalGitignoreMatcher() to address critical security vulnerabilities: Security Issues Fixed: - Command execution: exec.Command("git", "config") allowed arbitrary command execution via malicious git binary in PATH - Path traversal: Tilde expansion (~/../../etc/passwd) allowed reading arbitrary files on the system - Resource exhaustion: No timeout on git command execution - Information disclosure: Could expose system files via gitignore parser Changes: - Stubbed out loadGlobalGitignoreMatcher() to return nil - Removed call to load global gitignore in NewGitignoreChecker() - Removed unused "os/exec" import - Added detailed comments explaining security concerns and TODO Impact: - Feature still works for 99% of use cases (repo .gitignore + .git/info/exclude) - All tests pass - No breaking changes to API - Can re-enable global gitignore in future PR with proper security measures Future Work: - Parse ~/.gitconfig directly (no command execution) - Add path validation to prevent traversal attacks - Add comprehensive input sanitization See PR discussion for detailed security analysis. --------- Co-authored-by: Nicholas Mullen <[email protected]>
1 parent 0371a6a commit 9c17c20

File tree

6 files changed

+1714
-28
lines changed

6 files changed

+1714
-28
lines changed

pkg/lint2/discovery.go

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,46 @@ func isHiddenPath(path string) bool {
5656
return false
5757
}
5858

59+
// patternTargetsHiddenPath checks if a user pattern explicitly targets a hidden path.
60+
// Returns true if the pattern starts with or contains a hidden directory component.
61+
// Examples:
62+
// - "./.git/**" -> true (explicitly targets .git)
63+
// - "/tmp/test/.git/**" -> true (explicitly targets .git)
64+
// - "./.github/workflows/**" -> true (explicitly targets .github)
65+
// - "./**" -> false (broad pattern, not targeting hidden)
66+
// - "./charts/**" -> false (normal path)
67+
func patternTargetsHiddenPath(pattern string) bool {
68+
// Clean and normalize the pattern
69+
cleanPattern := filepath.Clean(pattern)
70+
cleanPattern = filepath.ToSlash(cleanPattern)
71+
72+
// Remove leading "./" for easier parsing (handles relative paths)
73+
cleanPattern = strings.TrimPrefix(cleanPattern, "./")
74+
75+
// Split into parts
76+
parts := strings.Split(cleanPattern, "/")
77+
78+
// Check if any non-wildcard part is a hidden directory
79+
for _, part := range parts {
80+
// Skip wildcards
81+
if part == "*" || part == "**" {
82+
continue
83+
}
84+
85+
// Skip empty parts (from double slashes or leading slash)
86+
if part == "" {
87+
continue
88+
}
89+
90+
// Check if this part is a hidden directory
91+
if strings.HasPrefix(part, ".") && part != "." && part != ".." {
92+
return true
93+
}
94+
}
95+
96+
return false
97+
}
98+
5999
// isChartDirectory checks if a directory contains a Chart.yaml or Chart.yml file.
60100
// Returns true if the directory is a valid Helm chart directory.
61101
func isChartDirectory(dirPath string) (bool, error) {
@@ -225,19 +265,29 @@ func validateExplicitYAMLFile(path, kind, resourceName string) ([]string, error)
225265

226266
// filterYAMLFilesByKind expands glob patterns and filters to files with matching kind.
227267
// Silently skips files that can't be read or don't have the target kind.
228-
func filterYAMLFilesByKind(patterns []string, originalPattern, kind string) ([]string, error) {
268+
// Optionally filters by gitignore if checker is provided.
269+
// Optionally skips hidden paths unless skipHidden is false (explicit bypass).
270+
func filterYAMLFilesByKind(patterns []string, originalPattern, kind string, gitignoreChecker *GitignoreChecker, skipHidden bool) ([]string, error) {
229271
var resultPaths []string
230272
seenPaths := make(map[string]bool)
231273

232274
for _, p := range patterns {
233-
matches, err := GlobFiles(p)
275+
// Use gitignore filtering if checker provided
276+
var matches []string
277+
var err error
278+
if gitignoreChecker != nil {
279+
matches, err = GlobFiles(p, WithGitignoreChecker(gitignoreChecker))
280+
} else {
281+
matches, err = GlobFiles(p)
282+
}
234283
if err != nil {
235284
return nil, fmt.Errorf("expanding pattern %s: %w (from user pattern: %s)", p, err, originalPattern)
236285
}
237286

238287
for _, path := range matches {
239-
// Skip hidden paths
240-
if isHiddenPath(path) {
288+
// Skip hidden paths (.git, .github, .vscode, etc.) by default
289+
// But allow bypass if user explicitly specified a hidden path pattern
290+
if skipHidden && isHiddenPath(path) {
241291
continue
242292
}
243293

@@ -265,6 +315,7 @@ func filterYAMLFilesByKind(patterns []string, originalPattern, kind string) ([]s
265315

266316
// discoverYAMLsByKind discovers YAML files containing a specific kind from a pattern.
267317
// Handles both explicit file paths (strict validation) and glob patterns (lenient filtering).
318+
// Respects gitignore unless the pattern explicitly references a gitignored path.
268319
//
269320
// For explicit paths:
270321
// - Validates file exists, is a file, has .yaml/.yml extension
@@ -275,6 +326,7 @@ func filterYAMLFilesByKind(patterns []string, originalPattern, kind string) ([]s
275326
// - Expands pattern to find all YAML files
276327
// - Filters to only files containing the specified kind
277328
// - Silently skips files that don't match (allows mixed directories)
329+
// - Respects gitignore unless pattern explicitly includes gitignored path
278330
func discoverYAMLsByKind(pattern, kind, resourceName string) ([]string, error) {
279331
// Validate empty pattern
280332
if pattern == "" {
@@ -295,14 +347,31 @@ func discoverYAMLsByKind(pattern, kind, resourceName string) ([]string, error) {
295347
return validateExplicitYAMLFile(pattern, kind, resourceName)
296348
}
297349

350+
// Create gitignore checker (returns nil if not in git repo or no gitignore)
351+
gitignoreChecker, _ := NewGitignoreChecker(".")
352+
353+
// Check if pattern explicitly bypasses gitignore
354+
var checkerToUse *GitignoreChecker
355+
if gitignoreChecker != nil && gitignoreChecker.PathMatchesIgnoredPattern(pattern) {
356+
// Pattern explicitly references gitignored path - bypass gitignore
357+
checkerToUse = nil
358+
} else {
359+
// Use gitignore filtering
360+
checkerToUse = gitignoreChecker
361+
}
362+
363+
// Check if pattern explicitly targets hidden paths
364+
// If yes, allow bypass of hidden path filtering (like gitignore bypass)
365+
skipHidden := !patternTargetsHiddenPath(pattern)
366+
298367
// Glob pattern - build search patterns
299368
patterns, err := buildYAMLPatterns(pattern)
300369
if err != nil {
301370
return nil, err
302371
}
303372

304373
// Lenient filtering
305-
return filterYAMLFilesByKind(patterns, originalPattern, kind)
374+
return filterYAMLFilesByKind(patterns, originalPattern, kind, checkerToUse, skipHidden)
306375
}
307376

308377
// validateExplicitChartDir validates an explicit directory path for chart discovery.
@@ -335,23 +404,34 @@ func validateExplicitChartDir(path string) ([]string, error) {
335404

336405
// filterDirsByMarkerFile expands glob patterns to find marker files and returns their parent directories.
337406
// Silently skips hidden paths and deduplicates results.
338-
func filterDirsByMarkerFile(patterns []string, originalPattern string) ([]string, error) {
407+
// Optionally filters by gitignore if checker is provided.
408+
// Optionally skips hidden paths unless skipHidden is false (explicit bypass).
409+
func filterDirsByMarkerFile(patterns []string, originalPattern string, gitignoreChecker *GitignoreChecker, skipHidden bool) ([]string, error) {
339410
var chartDirs []string
340411
seenDirs := make(map[string]bool)
341412

342413
for _, p := range patterns {
343-
matches, err := GlobFiles(p)
414+
// Use gitignore filtering if checker provided
415+
var matches []string
416+
var err error
417+
if gitignoreChecker != nil {
418+
matches, err = GlobFiles(p, WithGitignoreChecker(gitignoreChecker))
419+
} else {
420+
matches, err = GlobFiles(p)
421+
}
344422
if err != nil {
345423
return nil, fmt.Errorf("expanding pattern %s: %w (from user pattern: %s)", p, err, originalPattern)
346424
}
347425

348426
for _, markerPath := range matches {
349-
chartDir := filepath.Dir(markerPath)
350-
351-
if isHiddenPath(chartDir) {
427+
// Skip hidden paths (.git, .github, .vscode, etc.) by default
428+
// But allow bypass if user explicitly specified a hidden path pattern
429+
if skipHidden && isHiddenPath(markerPath) {
352430
continue
353431
}
354432

433+
chartDir := filepath.Dir(markerPath)
434+
355435
if seenDirs[chartDir] {
356436
continue
357437
}
@@ -366,6 +446,7 @@ func filterDirsByMarkerFile(patterns []string, originalPattern string) ([]string
366446

367447
// discoverDirsByMarkerFile discovers directories containing specific marker files.
368448
// Handles both explicit directory paths (strict validation) and glob patterns (lenient filtering).
449+
// Respects gitignore unless the pattern explicitly references a gitignored path.
369450
//
370451
// For explicit paths:
371452
// - Validates path exists and is a directory
@@ -376,6 +457,7 @@ func filterDirsByMarkerFile(patterns []string, originalPattern string) ([]string
376457
// - Expands pattern to find marker files
377458
// - Returns parent directories of found markers
378459
// - Silently skips paths that don't match
460+
// - Respects gitignore unless pattern explicitly includes gitignored path
379461
func discoverDirsByMarkerFile(pattern string, markerFiles []string, resourceName string) ([]string, error) {
380462
if pattern == "" {
381463
return nil, fmt.Errorf("pattern cannot be empty")
@@ -392,6 +474,23 @@ func discoverDirsByMarkerFile(pattern string, markerFiles []string, resourceName
392474
return validateExplicitChartDir(pattern)
393475
}
394476

477+
// Create gitignore checker (returns nil if not in git repo or no gitignore)
478+
gitignoreChecker, _ := NewGitignoreChecker(".")
479+
480+
// Check if pattern explicitly bypasses gitignore
481+
var checkerToUse *GitignoreChecker
482+
if gitignoreChecker != nil && gitignoreChecker.PathMatchesIgnoredPattern(pattern) {
483+
// Pattern explicitly references gitignored path - bypass gitignore
484+
checkerToUse = nil
485+
} else {
486+
// Use gitignore filtering
487+
checkerToUse = gitignoreChecker
488+
}
489+
490+
// Check if pattern explicitly targets hidden paths
491+
// If yes, allow bypass of hidden path filtering (like gitignore bypass)
492+
skipHidden := !patternTargetsHiddenPath(pattern)
493+
395494
// Build patterns for marker files
396495
var patterns []string
397496
if strings.HasSuffix(pattern, markerFiles[0]) || (len(markerFiles) > 1 && strings.HasSuffix(pattern, markerFiles[1])) {
@@ -406,7 +505,7 @@ func discoverDirsByMarkerFile(pattern string, markerFiles []string, resourceName
406505
}
407506

408507
// Filter to directories containing marker files
409-
return filterDirsByMarkerFile(patterns, originalPattern)
508+
return filterDirsByMarkerFile(patterns, originalPattern, checkerToUse, skipHidden)
410509
}
411510

412511
// discoverSupportBundlePaths discovers Support Bundle spec files from a glob pattern.

0 commit comments

Comments
 (0)