From 405fcabc7b6556f73a2ae75cbdf4fa9e102d9b30 Mon Sep 17 00:00:00 2001 From: Chu Julung Date: Wed, 4 Mar 2026 18:46:45 +0800 Subject: [PATCH 1/3] feat: check .gitignore covers common sensitive files Fixes #24 Adds a new GitignoreCheck that verifies .gitignore contains patterns for files that should never be committed. Checks for: - .env (environment files with secrets) - *.log (log files) - __pycache__ (Python cache) - node_modules (Node.js dependencies) The check: - Only runs when .gitignore exists in the project root - Returns StatusWarn (not Fail) when patterns are missing - Recognizes pattern variants (e.g., node_modules/ or **/node_modules) - Provides helpful Fix message listing missing patterns Implementation: - Created internal/check/gitignore.go with GitignoreCheck type - Registered in internal/check/registry.go when .gitignore exists - Follows existing check pattern and conventions --- internal/check/gitignore.go | 88 +++++++++++++++++++++++++++++++++++++ internal/check/registry.go | 5 +++ 2 files changed, 93 insertions(+) create mode 100644 internal/check/gitignore.go diff --git a/internal/check/gitignore.go b/internal/check/gitignore.go new file mode 100644 index 0000000..4e3f8d2 --- /dev/null +++ b/internal/check/gitignore.go @@ -0,0 +1,88 @@ +package check + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" +) + +type GitignoreCheck struct { + Dir string +} + +func (c *GitignoreCheck) Name() string { + return ".gitignore covers sensitive files" +} + +func (c *GitignoreCheck) Run(_ context.Context) Result { + gitignorePath := c.Dir + "/.gitignore" + + // Read .gitignore content + file, err := os.Open(gitignorePath) + if err != nil { + return Result{ + Name: c.Name(), + Status: StatusFail, + Message: ".gitignore not found", + Fix: "Create a .gitignore file with common patterns", + } + } + defer file.Close() + + // Parse .gitignore and collect all patterns + patterns := make(map[string]bool) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + // Skip comments and empty lines + if line == "" || strings.HasPrefix(line, "#") { + continue + } + patterns[line] = true + } + + // Define required patterns + requiredPatterns := map[string]string{ + ".env": "Environment files with secrets", + "*.log": "Log files", + "__pycache__": "Python cache", + "node_modules": "Node.js dependencies", + } + + // Check which patterns are missing + var missing []string + for pattern, description := range requiredPatterns { + // Check if pattern or a variant exists + found := false + for existingPattern := range patterns { + // Match exact pattern or with trailing slash (for directories) + if existingPattern == pattern || + existingPattern == pattern+"/" || + existingPattern == "**/"+pattern || + existingPattern == "**/"+pattern+"/" { + found = true + break + } + } + if !found { + missing = append(missing, fmt.Sprintf("%s (%s)", pattern, description)) + } + } + + if len(missing) > 0 { + return Result{ + Name: c.Name(), + Status: StatusWarn, + Message: fmt.Sprintf("Missing %d sensitive file patterns", len(missing)), + Fix: fmt.Sprintf("Add these patterns to .gitignore:\n %s", strings.Join(missing, "\n ")), + } + } + + return Result{ + Name: c.Name(), + Status: StatusPass, + Message: "Common sensitive files are gitignored", + } +} diff --git a/internal/check/registry.go b/internal/check/registry.go index 7a03230..dd6059f 100644 --- a/internal/check/registry.go +++ b/internal/check/registry.go @@ -62,6 +62,11 @@ func Build(stack detector.DetectedStack) []Check { cs = append(cs, &EnvCheck{Dir: "."}) } + // Check .gitignore for sensitive file patterns + if fileExists(".gitignore") { + cs = append(cs, &GitignoreCheck{Dir: "."}) + } + return cs } From eaafc2ca7a3c36d50987278ac6141f5c8524c8ee Mon Sep 17 00:00:00 2001 From: Vidya Sagar Desu <35026133+vidya381@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:38:15 -0500 Subject: [PATCH 2/3] Refactor GitignoreCheck to use requiredPattern slice Refactor GitignoreCheck to use a slice of required patterns instead of a hardcoded map. This allows for more flexible pattern management. --- internal/check/gitignore.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/internal/check/gitignore.go b/internal/check/gitignore.go index 4e3f8d2..54ad1d9 100644 --- a/internal/check/gitignore.go +++ b/internal/check/gitignore.go @@ -8,8 +8,14 @@ import ( "strings" ) +type requiredPattern struct { + pattern string + description string +} + type GitignoreCheck struct { - Dir string + Dir string + Patterns []requiredPattern } func (c *GitignoreCheck) Name() string { @@ -18,7 +24,7 @@ func (c *GitignoreCheck) Name() string { func (c *GitignoreCheck) Run(_ context.Context) Result { gitignorePath := c.Dir + "/.gitignore" - + // Read .gitignore content file, err := os.Open(gitignorePath) if err != nil { @@ -43,31 +49,23 @@ func (c *GitignoreCheck) Run(_ context.Context) Result { patterns[line] = true } - // Define required patterns - requiredPatterns := map[string]string{ - ".env": "Environment files with secrets", - "*.log": "Log files", - "__pycache__": "Python cache", - "node_modules": "Node.js dependencies", - } - // Check which patterns are missing var missing []string - for pattern, description := range requiredPatterns { + for _, rp := range c.Patterns { // Check if pattern or a variant exists found := false for existingPattern := range patterns { // Match exact pattern or with trailing slash (for directories) - if existingPattern == pattern || - existingPattern == pattern+"/" || - existingPattern == "**/"+pattern || - existingPattern == "**/"+pattern+"/" { + if existingPattern == rp.pattern || + existingPattern == rp.pattern+"/" || + existingPattern == "**/"+rp.pattern || + existingPattern == "**/"+rp.pattern+"/" { found = true break } } if !found { - missing = append(missing, fmt.Sprintf("%s (%s)", pattern, description)) + missing = append(missing, fmt.Sprintf("%s (%s)", rp.pattern, rp.description)) } } From b73c8920a588cce6472dbee921959f7304243864 Mon Sep 17 00:00:00 2001 From: Vidya Sagar Desu <35026133+vidya381@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:44:26 -0500 Subject: [PATCH 3/3] Enhance GitignoreCheck with additional patterns --- internal/check/registry.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/check/registry.go b/internal/check/registry.go index f311a69..b9e0ca4 100644 --- a/internal/check/registry.go +++ b/internal/check/registry.go @@ -86,9 +86,26 @@ func Build(stack detector.DetectedStack) []Check { cs = append(cs, &EnvCheck{Dir: "."}) } + // Check .gitignore for sensitive file patterns if fileExists(".gitignore") { - cs = append(cs, &GitignoreCheck{Dir: "."}) + gitignorePatterns := []requiredPattern{ + {pattern: ".env", description: "Environment files with secrets"}, + {pattern: "*.log", description: "Log files"}, + } + if stack.Node { + gitignorePatterns = append(gitignorePatterns, requiredPattern{ + pattern: "node_modules", + description: "Node.js dependencies", + }) + } + if stack.Python { + gitignorePatterns = append(gitignorePatterns, requiredPattern{ + pattern: "__pycache__", + description: "Python cache", + }) + } + cs = append(cs, &GitignoreCheck{Dir: ".", Patterns: gitignorePatterns}) } return cs