From e9bd694685fa6a1aa283f7b5228fe1969aa4bf3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 23:17:02 +0000 Subject: [PATCH] fix(security): prevent integer overflow in uint64 to int64 conversions Add overflow checks before converting uint64 memory values to int64 to prevent potential integer overflow issues identified by gosec (G115). - Add math.MaxInt64 checks in fileproc/backpressure.go - Add math.MaxInt64 checks in fileproc/resource_monitor_validation.go - Add math.MaxInt64 checks in fileproc/resource_monitor_metrics.go - Add math.MaxInt64 check in benchmark/benchmark.go with nosec annotation Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com> --- benchmark/benchmark.go | 13 +- cli/processor_collection.go | 2 +- cli/processor_processing.go | 2 +- cli/processor_stats.go | 2 +- cli/processor_types.go | 2 +- cli/processor_workers.go | 2 +- config/constants.go | 2 +- config/getters.go | 2 +- config/loader.go | 2 +- config/loader_test.go | 2 +- config/validation.go | 2 +- config/validation_test.go | 2 +- fileproc/backpressure.go | 13 +- fileproc/filetypes_concurrency_test.go | 2 +- fileproc/filetypes_config_test.go | 2 +- fileproc/filetypes_detection_test.go | 12 +- fileproc/filetypes_edge_cases_test.go | 2 +- fileproc/filetypes_registry_test.go | 2 +- fileproc/json_writer.go | 2 - fileproc/processor.go | 4 - fileproc/resource_monitor_concurrency.go | 2 +- fileproc/resource_monitor_concurrency_test.go | 6 +- fileproc/resource_monitor_integration_test.go | 2 +- fileproc/resource_monitor_metrics.go | 11 +- fileproc/resource_monitor_metrics_test.go | 2 +- fileproc/resource_monitor_rate_limiting.go | 2 +- .../resource_monitor_rate_limiting_test.go | 2 +- fileproc/resource_monitor_state.go | 2 +- fileproc/resource_monitor_types.go | 4 +- fileproc/resource_monitor_types_test.go | 4 +- fileproc/resource_monitor_validation.go | 11 +- fileproc/resource_monitor_validation_test.go | 2 +- go.mod | 1 - go.sum | 28 +- scripts/security-scan.sh | 492 +++++++++--------- testutil/concurrency_test.go | 2 +- testutil/config_test.go | 2 +- testutil/file_creation_test.go | 2 +- testutil/verification_test.go | 2 +- utils/writers.go | 6 +- 40 files changed, 331 insertions(+), 328 deletions(-) diff --git a/benchmark/benchmark.go b/benchmark/benchmark.go index 6d825b7..b2e908e 100644 --- a/benchmark/benchmark.go +++ b/benchmark/benchmark.go @@ -4,6 +4,7 @@ package benchmark import ( "context" "fmt" + "math" "os" "path/filepath" "runtime" @@ -272,7 +273,7 @@ func createBenchmarkFiles(numFiles int) (string, func(), error) { // Create subdirectories for some files if i%10 == 0 { subdir := filepath.Join(tempDir, fmt.Sprintf("subdir_%d", i/10)) - if err := os.MkdirAll(subdir, 0o755); err != nil { + if err := os.MkdirAll(subdir, 0o750); err != nil { cleanup() return "", nil, utils.WrapError(err, utils.ErrorTypeFileSystem, utils.CodeFSAccess, "failed to create subdirectory") } @@ -287,7 +288,7 @@ func createBenchmarkFiles(numFiles int) (string, func(), error) { content += fmt.Sprintf("// Line %d\n%s\n", j, fileType.content) } - if err := os.WriteFile(filename, []byte(content), 0o644); err != nil { + if err := os.WriteFile(filename, []byte(content), 0o600); err != nil { cleanup() return "", nil, utils.WrapError(err, utils.ErrorTypeIO, utils.CodeIOFileWrite, "failed to write benchmark file") } @@ -356,7 +357,13 @@ func PrintBenchmarkResult(result *BenchmarkResult) { fmt.Printf("Files/sec: %.2f\n", result.FilesPerSecond) fmt.Printf("Bytes/sec: %.2f MB/sec\n", result.BytesPerSecond/1024/1024) fmt.Printf("Memory Usage: +%.2f MB (Sys: +%.2f MB)\n", result.MemoryUsage.AllocMB, result.MemoryUsage.SysMB) - fmt.Printf("GC Runs: %d (Pause: %v)\n", result.MemoryUsage.NumGC, time.Duration(result.MemoryUsage.PauseTotalNs)) + // Safe conversion: cap at MaxInt64 to prevent overflow + pauseTotalNs := result.MemoryUsage.PauseTotalNs + if pauseTotalNs > math.MaxInt64 { + pauseTotalNs = math.MaxInt64 + } + pauseDuration := time.Duration(int64(pauseTotalNs)) // #nosec G115 -- overflow check above + fmt.Printf("GC Runs: %d (Pause: %v)\n", result.MemoryUsage.NumGC, pauseDuration) fmt.Printf("Goroutines: %d\n", result.CPUUsage.Goroutines) fmt.Println() } diff --git a/cli/processor_collection.go b/cli/processor_collection.go index cd8be10..99b7e9c 100644 --- a/cli/processor_collection.go +++ b/cli/processor_collection.go @@ -74,4 +74,4 @@ func (p *Processor) validateFileCollection(files []string) error { logrus.Infof("Pre-validation passed: %d files, %d MB total", len(files), totalSize/1024/1024) return nil -} \ No newline at end of file +} diff --git a/cli/processor_processing.go b/cli/processor_processing.go index 40962b0..1f85231 100644 --- a/cli/processor_processing.go +++ b/cli/processor_processing.go @@ -97,4 +97,4 @@ func (p *Processor) createOutputFile() (*os.File, error) { return nil, utils.WrapError(err, utils.ErrorTypeIO, utils.CodeIOFileCreate, "failed to create output file").WithFilePath(p.flags.Destination) } return outFile, nil -} \ No newline at end of file +} diff --git a/cli/processor_stats.go b/cli/processor_stats.go index 6ecd856..516af74 100644 --- a/cli/processor_stats.go +++ b/cli/processor_stats.go @@ -37,4 +37,4 @@ func (p *Processor) logFinalStats() { // Clean up resource monitor p.resourceMonitor.Close() -} \ No newline at end of file +} diff --git a/cli/processor_types.go b/cli/processor_types.go index e5d37e2..2ec1531 100644 --- a/cli/processor_types.go +++ b/cli/processor_types.go @@ -41,4 +41,4 @@ func (p *Processor) configureFileTypes() { config.GetDisabledLanguageExtensions(), ) } -} \ No newline at end of file +} diff --git a/cli/processor_workers.go b/cli/processor_workers.go index ebfac43..ed22e98 100644 --- a/cli/processor_workers.go +++ b/cli/processor_workers.go @@ -82,4 +82,4 @@ func (p *Processor) waitForCompletion(wg *sync.WaitGroup, writeCh chan fileproc. wg.Wait() close(writeCh) <-writerDone -} \ No newline at end of file +} diff --git a/config/constants.go b/config/constants.go index 8f54bbe..1a2ba75 100644 --- a/config/constants.go +++ b/config/constants.go @@ -58,4 +58,4 @@ const ( MinHardMemoryLimitMB = 64 // MaxHardMemoryLimitMB is the maximum hard memory limit (8192MB = 8GB). MaxHardMemoryLimitMB = 8192 -) \ No newline at end of file +) diff --git a/config/getters.go b/config/getters.go index 4bcc1b4..b178144 100644 --- a/config/getters.go +++ b/config/getters.go @@ -154,4 +154,4 @@ func GetEnableGracefulDegradation() bool { // GetEnableResourceMonitoring returns whether resource monitoring is enabled. func GetEnableResourceMonitoring() bool { return viper.GetBool("resourceLimits.enableResourceMonitoring") -} \ No newline at end of file +} diff --git a/config/loader.go b/config/loader.go index c7490b5..11c07da 100644 --- a/config/loader.go +++ b/config/loader.go @@ -87,4 +87,4 @@ func setDefaultConfig() { viper.SetDefault("resourceLimits.hardMemoryLimitMB", DefaultHardMemoryLimitMB) viper.SetDefault("resourceLimits.enableGracefulDegradation", true) viper.SetDefault("resourceLimits.enableResourceMonitoring", true) -} \ No newline at end of file +} diff --git a/config/loader_test.go b/config/loader_test.go index d1c5295..2ee3cd5 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -117,4 +117,4 @@ func containsString(slice []string, item string) bool { } } return false -} \ No newline at end of file +} diff --git a/config/validation.go b/config/validation.go index ed13319..d1b987b 100644 --- a/config/validation.go +++ b/config/validation.go @@ -304,4 +304,4 @@ func ValidateConcurrency(concurrency int) error { } return nil -} \ No newline at end of file +} diff --git a/config/validation_test.go b/config/validation_test.go index 9b3cfb4..aff2e82 100644 --- a/config/validation_test.go +++ b/config/validation_test.go @@ -242,4 +242,4 @@ func errorAs(err error, target interface{}) bool { } } return false -} \ No newline at end of file +} diff --git a/fileproc/backpressure.go b/fileproc/backpressure.go index 733a271..4b62447 100644 --- a/fileproc/backpressure.go +++ b/fileproc/backpressure.go @@ -3,6 +3,7 @@ package fileproc import ( "context" + "math" "runtime" "sync" "sync/atomic" @@ -73,7 +74,11 @@ func (bp *BackpressureManager) ShouldApplyBackpressure(ctx context.Context) bool // Get current memory usage var m runtime.MemStats runtime.ReadMemStats(&m) + // Safe conversion: cap at MaxInt64 to prevent overflow currentMemory := int64(m.Alloc) + if m.Alloc > math.MaxInt64 { + currentMemory = math.MaxInt64 + } bp.mu.Lock() defer bp.mu.Unlock() @@ -130,10 +135,16 @@ func (bp *BackpressureManager) GetStats() BackpressureStats { var m runtime.MemStats runtime.ReadMemStats(&m) + // Safe conversion: cap at MaxInt64 to prevent overflow + currentMemory := int64(m.Alloc) + if m.Alloc > math.MaxInt64 { + currentMemory = math.MaxInt64 + } + return BackpressureStats{ Enabled: bp.enabled, FilesProcessed: atomic.LoadInt64(&bp.filesProcessed), - CurrentMemoryUsage: int64(m.Alloc), + CurrentMemoryUsage: currentMemory, MaxMemoryUsage: bp.maxMemoryUsage, MemoryWarningActive: bp.memoryWarningLogged, LastMemoryCheck: bp.lastMemoryCheck, diff --git a/fileproc/filetypes_concurrency_test.go b/fileproc/filetypes_concurrency_test.go index 9478aac..9b05d63 100644 --- a/fileproc/filetypes_concurrency_test.go +++ b/fileproc/filetypes_concurrency_test.go @@ -102,4 +102,4 @@ func TestFileTypeRegistry_ThreadSafety(t *testing.T) { } wg.Wait() }) -} \ No newline at end of file +} diff --git a/fileproc/filetypes_config_test.go b/fileproc/filetypes_config_test.go index 9690a7f..1f2e501 100644 --- a/fileproc/filetypes_config_test.go +++ b/fileproc/filetypes_config_test.go @@ -255,4 +255,4 @@ func TestConfigureFromSettings(t *testing.T) { if !IsImage("test.extra") { t.Error("Expected new configuration to be applied") } -} \ No newline at end of file +} diff --git a/fileproc/filetypes_detection_test.go b/fileproc/filetypes_detection_test.go index d3a9acd..e3cb840 100644 --- a/fileproc/filetypes_detection_test.go +++ b/fileproc/filetypes_detection_test.go @@ -208,11 +208,11 @@ func TestFileTypeRegistry_BinaryDetection(t *testing.T) { {"page.html", false}, // Edge cases - {"", false}, // Empty filename - {"binary", false}, // No extension - {".exe", true}, // Just extension - {"file.exe.txt", false}, // Multiple extensions - {"file.unknown", false}, // Unknown extension + {"", false}, // Empty filename + {"binary", false}, // No extension + {".exe", true}, // Just extension + {"file.exe.txt", false}, // Multiple extensions + {"file.unknown", false}, // Unknown extension } for _, tt := range tests { @@ -223,4 +223,4 @@ func TestFileTypeRegistry_BinaryDetection(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/fileproc/filetypes_edge_cases_test.go b/fileproc/filetypes_edge_cases_test.go index ce9ee84..6c0ab53 100644 --- a/fileproc/filetypes_edge_cases_test.go +++ b/fileproc/filetypes_edge_cases_test.go @@ -125,4 +125,4 @@ func BenchmarkFileTypeRegistry_ConcurrentAccess(b *testing.B) { _ = GetLanguage(filename) } }) -} \ No newline at end of file +} diff --git a/fileproc/filetypes_registry_test.go b/fileproc/filetypes_registry_test.go index 0b9954a..234f1bc 100644 --- a/fileproc/filetypes_registry_test.go +++ b/fileproc/filetypes_registry_test.go @@ -134,4 +134,4 @@ func TestFileTypeRegistry_DefaultRegistryConsistency(t *testing.T) { t.Errorf("Iteration %d: Expected .txt to not be recognized as binary", i) } } -} \ No newline at end of file +} diff --git a/fileproc/json_writer.go b/fileproc/json_writer.go index 5c9ab46..7d264e2 100644 --- a/fileproc/json_writer.go +++ b/fileproc/json_writer.go @@ -130,8 +130,6 @@ func (w *JSONWriter) streamJSONContent(reader io.Reader, path string) error { }) } - - // startJSONWriter handles JSON format output with streaming support. func startJSONWriter(outFile *os.File, writeCh <-chan WriteRequest, done chan<- struct{}, prefix, suffix string) { defer close(done) diff --git a/fileproc/processor.go b/fileproc/processor.go index f9ff983..a77b985 100644 --- a/fileproc/processor.go +++ b/fileproc/processor.go @@ -138,7 +138,6 @@ func (p *FileProcessor) ProcessWithContext(ctx context.Context, filePath string, } } - // validateFileWithLimits checks if the file can be processed with resource limits. func (p *FileProcessor) validateFileWithLimits(ctx context.Context, filePath string) (os.FileInfo, error) { // Check context cancellation @@ -192,7 +191,6 @@ func (p *FileProcessor) getRelativePath(filePath string) string { return relPath } - // processInMemoryWithContext loads the entire file into memory with context awareness. func (p *FileProcessor) processInMemoryWithContext(ctx context.Context, filePath, relPath string, outCh chan<- WriteRequest) { // Check context before reading @@ -240,7 +238,6 @@ func (p *FileProcessor) processInMemoryWithContext(ctx context.Context, filePath } } - // processStreamingWithContext creates a streaming reader for large files with context awareness. func (p *FileProcessor) processStreamingWithContext(ctx context.Context, filePath, relPath string, outCh chan<- WriteRequest) { // Check context before creating reader @@ -276,7 +273,6 @@ func (p *FileProcessor) processStreamingWithContext(ctx context.Context, filePat } } - // createStreamReaderWithContext creates a reader that combines header and file content with context awareness. func (p *FileProcessor) createStreamReaderWithContext(ctx context.Context, filePath, relPath string) io.Reader { // Check context before opening file diff --git a/fileproc/resource_monitor_concurrency.go b/fileproc/resource_monitor_concurrency.go index 4d1789b..9c8b6d9 100644 --- a/fileproc/resource_monitor_concurrency.go +++ b/fileproc/resource_monitor_concurrency.go @@ -56,4 +56,4 @@ func (rm *ResourceMonitor) CreateOverallProcessingContext(parent context.Context return parent, func() {} } return context.WithTimeout(parent, rm.overallTimeout) -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_concurrency_test.go b/fileproc/resource_monitor_concurrency_test.go index 566b037..e7139f7 100644 --- a/fileproc/resource_monitor_concurrency_test.go +++ b/fileproc/resource_monitor_concurrency_test.go @@ -43,11 +43,11 @@ func TestResourceMonitor_ConcurrentReadsLimit(t *testing.T) { // Release one slot and try again rm.ReleaseReadSlot() - + // Create new context for the next attempt ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel2() - + err = rm.AcquireReadSlot(ctx2) if err != nil { t.Errorf("Expected no error after releasing a slot, got %v", err) @@ -92,4 +92,4 @@ func TestResourceMonitor_TimeoutContexts(t *testing.T) { } else if time.Until(deadline) > 2*time.Second+100*time.Millisecond { t.Error("Overall processing timeout appears to be too long") } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_integration_test.go b/fileproc/resource_monitor_integration_test.go index 4dd2915..eba2bd3 100644 --- a/fileproc/resource_monitor_integration_test.go +++ b/fileproc/resource_monitor_integration_test.go @@ -78,4 +78,4 @@ func TestResourceMonitor_Integration(t *testing.T) { // Test resource limit logging rm.LogResourceInfo() -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_metrics.go b/fileproc/resource_monitor_metrics.go index 75e767c..283e19f 100644 --- a/fileproc/resource_monitor_metrics.go +++ b/fileproc/resource_monitor_metrics.go @@ -1,6 +1,7 @@ package fileproc import ( + "math" "runtime" "sync/atomic" "time" @@ -48,6 +49,12 @@ func (rm *ResourceMonitor) GetMetrics() ResourceMetrics { violations = append(violations, violation) } + // Safe conversion: cap at MaxInt64 to prevent overflow + memoryUsage := int64(m.Alloc) / 1024 / 1024 + if m.Alloc > math.MaxInt64 { + memoryUsage = math.MaxInt64 / 1024 / 1024 + } + return ResourceMetrics{ FilesProcessed: filesProcessed, TotalSizeProcessed: totalSize, @@ -55,7 +62,7 @@ func (rm *ResourceMonitor) GetMetrics() ResourceMetrics { ProcessingDuration: duration, AverageFileSize: avgFileSize, ProcessingRate: processingRate, - MemoryUsageMB: int64(m.Alloc) / 1024 / 1024, + MemoryUsageMB: memoryUsage, MaxMemoryUsageMB: int64(rm.hardMemoryLimitMB), ViolationsDetected: violations, DegradationActive: rm.degradationActive, @@ -76,4 +83,4 @@ func (rm *ResourceMonitor) LogResourceInfo() { } else { logrus.Info("Resource limits disabled") } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_metrics_test.go b/fileproc/resource_monitor_metrics_test.go index b804581..1b28786 100644 --- a/fileproc/resource_monitor_metrics_test.go +++ b/fileproc/resource_monitor_metrics_test.go @@ -46,4 +46,4 @@ func TestResourceMonitor_Metrics(t *testing.T) { if !metrics.LastUpdated.After(time.Now().Add(-time.Second)) { t.Error("Expected recent LastUpdated timestamp") } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_rate_limiting.go b/fileproc/resource_monitor_rate_limiting.go index 640eab4..c475777 100644 --- a/fileproc/resource_monitor_rate_limiting.go +++ b/fileproc/resource_monitor_rate_limiting.go @@ -33,4 +33,4 @@ func (rm *ResourceMonitor) rateLimiterRefill() { // Channel is full, skip } } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_rate_limiting_test.go b/fileproc/resource_monitor_rate_limiting_test.go index d5c791d..4c8e15d 100644 --- a/fileproc/resource_monitor_rate_limiting_test.go +++ b/fileproc/resource_monitor_rate_limiting_test.go @@ -37,4 +37,4 @@ func TestResourceMonitor_RateLimiting(t *testing.T) { if duration < 200*time.Millisecond { t.Logf("Rate limiting may not be working as expected, took only %v", duration) } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_state.go b/fileproc/resource_monitor_state.go index e1abef7..1fe544e 100644 --- a/fileproc/resource_monitor_state.go +++ b/fileproc/resource_monitor_state.go @@ -19,4 +19,4 @@ func (rm *ResourceMonitor) Close() { if rm.rateLimiter != nil { rm.rateLimiter.Stop() } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_types.go b/fileproc/resource_monitor_types.go index 90461b9..5b12758 100644 --- a/fileproc/resource_monitor_types.go +++ b/fileproc/resource_monitor_types.go @@ -100,9 +100,9 @@ func NewResourceMonitor() *ResourceMonitor { } rateLimitFull: - // Start rate limiter refill goroutine + // Start rate limiter refill goroutine go rm.rateLimiterRefill() } return rm -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_types_test.go b/fileproc/resource_monitor_types_test.go index 8686aca..7d91c44 100644 --- a/fileproc/resource_monitor_types_test.go +++ b/fileproc/resource_monitor_types_test.go @@ -34,7 +34,7 @@ func TestResourceMonitor_NewResourceMonitor(t *testing.T) { } if rm.fileProcessingTimeout != time.Duration(config.DefaultFileProcessingTimeoutSec)*time.Second { - t.Errorf("Expected fileProcessingTimeout to be %v, got %v", + t.Errorf("Expected fileProcessingTimeout to be %v, got %v", time.Duration(config.DefaultFileProcessingTimeoutSec)*time.Second, rm.fileProcessingTimeout) } @@ -71,4 +71,4 @@ func TestResourceMonitor_DisabledResourceLimits(t *testing.T) { if err != nil { t.Errorf("Expected no error when rate limiting disabled, got %v", err) } -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_validation.go b/fileproc/resource_monitor_validation.go index f24dad8..3a9647e 100644 --- a/fileproc/resource_monitor_validation.go +++ b/fileproc/resource_monitor_validation.go @@ -1,6 +1,7 @@ package fileproc import ( + "math" "runtime" "sync/atomic" "time" @@ -88,7 +89,11 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error { var m runtime.MemStats runtime.ReadMemStats(&m) + // Safe conversion: cap at MaxInt64 to prevent overflow currentMemory := int64(m.Alloc) + if m.Alloc > math.MaxInt64 { + currentMemory = math.MaxInt64 + } if currentMemory > rm.hardMemoryLimitBytes { rm.mu.Lock() @@ -108,7 +113,11 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error { // Check again after GC runtime.ReadMemStats(&m) + // Safe conversion: cap at MaxInt64 to prevent overflow currentMemory = int64(m.Alloc) + if m.Alloc > math.MaxInt64 { + currentMemory = math.MaxInt64 + } if currentMemory > rm.hardMemoryLimitBytes { // Still over limit, activate emergency stop @@ -145,4 +154,4 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error { } return nil -} \ No newline at end of file +} diff --git a/fileproc/resource_monitor_validation_test.go b/fileproc/resource_monitor_validation_test.go index d45002c..e9c44c8 100644 --- a/fileproc/resource_monitor_validation_test.go +++ b/fileproc/resource_monitor_validation_test.go @@ -85,4 +85,4 @@ func TestResourceMonitor_TotalSizeLimit(t *testing.T) { } else if structErr.Code != utils.CodeResourceLimitTotalSize { t.Errorf("Expected error code %s, got %s", utils.CodeResourceLimitTotalSize, structErr.Code) } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index f95018f..9af3217 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.28.0 // indirect diff --git a/go.sum b/go.sum index e58e80d..759e9a8 100644 --- a/go.sum +++ b/go.sum @@ -7,12 +7,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -30,8 +26,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -42,45 +36,29 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ= -github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -90,8 +68,6 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/scripts/security-scan.sh b/scripts/security-scan.sh index 71c627d..b228017 100755 --- a/scripts/security-scan.sh +++ b/scripts/security-scan.sh @@ -20,306 +20,306 @@ NC='\033[0m' # No Color # Function to print status print_status() { - echo -e "${BLUE}[INFO]${NC} $1" + echo -e "${BLUE}[INFO]${NC} $1" } print_warning() { - echo -e "${YELLOW}[WARN]${NC} $1" + echo -e "${YELLOW}[WARN]${NC} $1" } print_error() { - echo -e "${RED}[ERROR]${NC} $1" + echo -e "${RED}[ERROR]${NC} $1" } print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" + echo -e "${GREEN}[SUCCESS]${NC} $1" } # Check if required tools are installed check_dependencies() { - print_status "Checking security scanning dependencies..." + print_status "Checking security scanning dependencies..." - local missing_tools=() + local missing_tools=() - if ! command -v go &>/dev/null; then - missing_tools+=("go") - fi + if ! command -v go &>/dev/null; then + missing_tools+=("go") + fi - if ! command -v golangci-lint &>/dev/null; then - print_warning "golangci-lint not found, installing..." - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - fi + if ! command -v golangci-lint &>/dev/null; then + print_warning "golangci-lint not found, installing..." + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + fi - if ! command -v gosec &>/dev/null; then - print_warning "gosec not found, installing..." - go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest - fi + if ! command -v gosec &>/dev/null; then + print_warning "gosec not found, installing..." + go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest + fi - if ! command -v govulncheck &>/dev/null; then - print_warning "govulncheck not found, installing..." - go install golang.org/x/vuln/cmd/govulncheck@latest - fi + if ! command -v govulncheck &>/dev/null; then + print_warning "govulncheck not found, installing..." + go install golang.org/x/vuln/cmd/govulncheck@latest + fi - if ! command -v checkmake &>/dev/null; then - print_warning "checkmake not found, installing..." - go install github.com/mrtazz/checkmake/cmd/checkmake@latest - fi + if ! command -v checkmake &>/dev/null; then + print_warning "checkmake not found, installing..." + go install github.com/mrtazz/checkmake/cmd/checkmake@latest + fi - if ! command -v shfmt &>/dev/null; then - print_warning "shfmt not found, installing..." - go install mvdan.cc/sh/v3/cmd/shfmt@latest - fi + if ! command -v shfmt &>/dev/null; then + print_warning "shfmt not found, installing..." + go install mvdan.cc/sh/v3/cmd/shfmt@latest + fi - if ! command -v yamllint &>/dev/null; then - print_warning "yamllint not found, installing..." - go install github.com/excilsploft/yamllint@latest - fi + if ! command -v yamllint &>/dev/null; then + print_warning "yamllint not found, installing..." + go install github.com/excilsploft/yamllint@latest + fi - if [ ${#missing_tools[@]} -ne 0 ]; then - print_error "Missing required tools: ${missing_tools[*]}" - print_error "Please install the missing tools and try again." - exit 1 - fi + if [ ${#missing_tools[@]} -ne 0 ]; then + print_error "Missing required tools: ${missing_tools[*]}" + print_error "Please install the missing tools and try again." + exit 1 + fi - print_success "All dependencies are available" + print_success "All dependencies are available" } # Run gosec security scanner run_gosec() { - print_status "Running gosec security scanner..." + print_status "Running gosec security scanner..." - if gosec -fmt=json -out=gosec-report.json -stdout -verbose=text ./...; then - print_success "gosec scan completed successfully" - else - print_error "gosec found security issues!" - if [ -f "gosec-report.json" ]; then - echo "Detailed report saved to gosec-report.json" - fi - return 1 - fi + if gosec -fmt=json -out=gosec-report.json -stdout -verbose=text ./...; then + print_success "gosec scan completed successfully" + else + print_error "gosec found security issues!" + if [ -f "gosec-report.json" ]; then + echo "Detailed report saved to gosec-report.json" + fi + return 1 + fi } # Run vulnerability check run_govulncheck() { - print_status "Running govulncheck for dependency vulnerabilities..." + print_status "Running govulncheck for dependency vulnerabilities..." - if govulncheck -json ./... >govulncheck-report.json 2>&1; then - print_success "No known vulnerabilities found in dependencies" - else - if grep -q '"finding"' govulncheck-report.json 2>/dev/null; then - print_error "Vulnerabilities found in dependencies!" - echo "Detailed report saved to govulncheck-report.json" - return 1 - else - print_success "No vulnerabilities found" - fi - fi + if govulncheck -json ./... >govulncheck-report.json 2>&1; then + print_success "No known vulnerabilities found in dependencies" + else + if grep -q '"finding"' govulncheck-report.json 2>/dev/null; then + print_error "Vulnerabilities found in dependencies!" + echo "Detailed report saved to govulncheck-report.json" + return 1 + else + print_success "No vulnerabilities found" + fi + fi } # Run enhanced golangci-lint with security focus run_security_lint() { - print_status "Running security-focused linting..." + print_status "Running security-focused linting..." - local security_linters="gosec,gocritic,bodyclose,rowserrcheck,misspell,unconvert,unparam,unused,errcheck,ineffassign,staticcheck" + local security_linters="gosec,gocritic,bodyclose,rowserrcheck,misspell,unconvert,unparam,unused,errcheck,ineffassign,staticcheck" - if golangci-lint run --enable="$security_linters" --timeout=5m; then - print_success "Security linting passed" - else - print_error "Security linting found issues!" - return 1 - fi + if golangci-lint run --enable="$security_linters" --timeout=5m; then + print_success "Security linting passed" + else + print_error "Security linting found issues!" + return 1 + fi } # Check for potential secrets check_secrets() { - print_status "Scanning for potential secrets and sensitive data..." + print_status "Scanning for potential secrets and sensitive data..." - local secrets_found=false + local secrets_found=false - # Common secret patterns - local patterns=( - "password\s*[:=]\s*['\"][^'\"]{3,}['\"]" - "secret\s*[:=]\s*['\"][^'\"]{3,}['\"]" - "key\s*[:=]\s*['\"][^'\"]{8,}['\"]" - "token\s*[:=]\s*['\"][^'\"]{8,}['\"]" - "api_?key\s*[:=]\s*['\"][^'\"]{8,}['\"]" - "aws_?access_?key" - "aws_?secret" - "AKIA[0-9A-Z]{16}" # AWS Access Key pattern - "github_?token" - "private_?key" - ) + # Common secret patterns + local patterns=( + "password\s*[:=]\s*['\"][^'\"]{3,}['\"]" + "secret\s*[:=]\s*['\"][^'\"]{3,}['\"]" + "key\s*[:=]\s*['\"][^'\"]{8,}['\"]" + "token\s*[:=]\s*['\"][^'\"]{8,}['\"]" + "api_?key\s*[:=]\s*['\"][^'\"]{8,}['\"]" + "aws_?access_?key" + "aws_?secret" + "AKIA[0-9A-Z]{16}" # AWS Access Key pattern + "github_?token" + "private_?key" + ) - for pattern in "${patterns[@]}"; do - if grep -r -i -E "$pattern" --include="*.go" . 2>/dev/null; then - print_warning "Potential secret pattern found: $pattern" - secrets_found=true - fi - done + for pattern in "${patterns[@]}"; do + if grep -r -i -E "$pattern" --include="*.go" . 2>/dev/null; then + print_warning "Potential secret pattern found: $pattern" + secrets_found=true + fi + done - # Check git history for secrets (last 10 commits) - if git log --oneline -10 | grep -i -E "(password|secret|key|token)" >/dev/null 2>&1; then - print_warning "Potential secrets mentioned in recent commit messages" - secrets_found=true - fi + # Check git history for secrets (last 10 commits) + if git log --oneline -10 | grep -i -E "(password|secret|key|token)" >/dev/null 2>&1; then + print_warning "Potential secrets mentioned in recent commit messages" + secrets_found=true + fi - if [ "$secrets_found" = true ]; then - print_warning "Potential secrets detected. Please review manually." - return 1 - else - print_success "No obvious secrets detected" - fi + if [ "$secrets_found" = true ]; then + print_warning "Potential secrets detected. Please review manually." + return 1 + else + print_success "No obvious secrets detected" + fi } # Check for hardcoded network addresses check_hardcoded_addresses() { - print_status "Checking for hardcoded network addresses..." + print_status "Checking for hardcoded network addresses..." - local addresses_found=false + local addresses_found=false - # Look for IP addresses (excluding common safe ones) - if grep -r -E "([0-9]{1,3}\.){3}[0-9]{1,3}" --include="*.go" . | - grep -v -E "(127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.255|localhost)" >/dev/null 2>&1; then - print_warning "Hardcoded IP addresses found:" - grep -r -E "([0-9]{1,3}\.){3}[0-9]{1,3}" --include="*.go" . | - grep -v -E "(127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.255|localhost)" || true - addresses_found=true - fi + # Look for IP addresses (excluding common safe ones) + if grep -r -E "([0-9]{1,3}\.){3}[0-9]{1,3}" --include="*.go" . | + grep -v -E "(127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.255|localhost)" >/dev/null 2>&1; then + print_warning "Hardcoded IP addresses found:" + grep -r -E "([0-9]{1,3}\.){3}[0-9]{1,3}" --include="*.go" . | + grep -v -E "(127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.255|localhost)" || true + addresses_found=true + fi - # Look for URLs (excluding documentation examples) - if grep -r -E "https?://[^/\s]+" --include="*.go" . | - grep -v -E "(example\.com|localhost|127\.0\.0\.1|\$\{)" >/dev/null 2>&1; then - print_warning "Hardcoded URLs found:" - grep -r -E "https?://[^/\s]+" --include="*.go" . | - grep -v -E "(example\.com|localhost|127\.0\.0\.1|\$\{)" || true - addresses_found=true - fi + # Look for URLs (excluding documentation examples) + if grep -r -E "https?://[^/\s]+" --include="*.go" . | + grep -v -E "(example\.com|localhost|127\.0\.0\.1|\$\{)" >/dev/null 2>&1; then + print_warning "Hardcoded URLs found:" + grep -r -E "https?://[^/\s]+" --include="*.go" . | + grep -v -E "(example\.com|localhost|127\.0\.0\.1|\$\{)" || true + addresses_found=true + fi - if [ "$addresses_found" = true ]; then - print_warning "Hardcoded network addresses detected. Please review." - return 1 - else - print_success "No hardcoded network addresses found" - fi + if [ "$addresses_found" = true ]; then + print_warning "Hardcoded network addresses detected. Please review." + return 1 + else + print_success "No hardcoded network addresses found" + fi } # Check Docker security (if Dockerfile exists) check_docker_security() { - if [ -f "Dockerfile" ]; then - print_status "Checking Docker security..." + if [ -f "Dockerfile" ]; then + print_status "Checking Docker security..." - # Basic Dockerfile security checks - local docker_issues=false + # Basic Dockerfile security checks + local docker_issues=false - if grep -q "^USER root" Dockerfile; then - print_warning "Dockerfile runs as root user" - docker_issues=true - fi + if grep -q "^USER root" Dockerfile; then + print_warning "Dockerfile runs as root user" + docker_issues=true + fi - if ! grep -q "^USER " Dockerfile; then - print_warning "Dockerfile doesn't specify a non-root user" - docker_issues=true - fi + if ! grep -q "^USER " Dockerfile; then + print_warning "Dockerfile doesn't specify a non-root user" + docker_issues=true + fi - if grep -q "RUN.*wget\|RUN.*curl" Dockerfile && ! grep -q "rm.*wget\|rm.*curl" Dockerfile; then - print_warning "Dockerfile may leave curl/wget installed" - docker_issues=true - fi + if grep -q "RUN.*wget\|RUN.*curl" Dockerfile && ! grep -q "rm.*wget\|rm.*curl" Dockerfile; then + print_warning "Dockerfile may leave curl/wget installed" + docker_issues=true + fi - if [ "$docker_issues" = true ]; then - print_warning "Docker security issues detected" - return 1 - else - print_success "Docker security check passed" - fi - else - print_status "No Dockerfile found, skipping Docker security check" - fi + if [ "$docker_issues" = true ]; then + print_warning "Docker security issues detected" + return 1 + else + print_success "Docker security check passed" + fi + else + print_status "No Dockerfile found, skipping Docker security check" + fi } # Check file permissions check_file_permissions() { - print_status "Checking file permissions..." + print_status "Checking file permissions..." - local perm_issues=false + local perm_issues=false - # Check for overly permissive files - if find . -type f -perm /o+w -not -path "./.git/*" | grep -q .; then - print_warning "World-writable files found:" - find . -type f -perm /o+w -not -path "./.git/*" || true - perm_issues=true - fi + # Check for overly permissive files + if find . -type f -perm /o+w -not -path "./.git/*" | grep -q .; then + print_warning "World-writable files found:" + find . -type f -perm /o+w -not -path "./.git/*" || true + perm_issues=true + fi - # Check for executable files that shouldn't be - if find . -type f -name "*.go" -perm /a+x | grep -q .; then - print_warning "Executable Go files found (should not be executable):" - find . -type f -name "*.go" -perm /a+x || true - perm_issues=true - fi + # Check for executable files that shouldn't be + if find . -type f -name "*.go" -perm /a+x | grep -q .; then + print_warning "Executable Go files found (should not be executable):" + find . -type f -name "*.go" -perm /a+x || true + perm_issues=true + fi - if [ "$perm_issues" = true ]; then - print_warning "File permission issues detected" - return 1 - else - print_success "File permissions check passed" - fi + if [ "$perm_issues" = true ]; then + print_warning "File permission issues detected" + return 1 + else + print_success "File permissions check passed" + fi } # Check Makefile with checkmake check_makefile() { - if [ -f "Makefile" ]; then - print_status "Checking Makefile with checkmake..." + if [ -f "Makefile" ]; then + print_status "Checking Makefile with checkmake..." - if checkmake --config=.checkmake Makefile; then - print_success "Makefile check passed" - else - print_error "Makefile issues detected!" - return 1 - fi - else - print_status "No Makefile found, skipping checkmake" - fi + if checkmake --config=.checkmake Makefile; then + print_success "Makefile check passed" + else + print_error "Makefile issues detected!" + return 1 + fi + else + print_status "No Makefile found, skipping checkmake" + fi } # Check shell scripts with shfmt check_shell_scripts() { - print_status "Checking shell script formatting..." + print_status "Checking shell script formatting..." - if find . -name "*.sh" -type f | head -1 | grep -q .; then - if shfmt -d .; then - print_success "Shell script formatting check passed" - else - print_error "Shell script formatting issues detected!" - return 1 - fi - else - print_status "No shell scripts found, skipping shfmt check" - fi + if find . -name "*.sh" -type f | head -1 | grep -q .; then + if shfmt -d .; then + print_success "Shell script formatting check passed" + else + print_error "Shell script formatting issues detected!" + return 1 + fi + else + print_status "No shell scripts found, skipping shfmt check" + fi } # Check YAML files check_yaml_files() { - print_status "Checking YAML files..." + print_status "Checking YAML files..." - if find . -name "*.yml" -o -name "*.yaml" -type f | head -1 | grep -q .; then - if yamllint -c .yamllint .; then - print_success "YAML files check passed" - else - print_error "YAML file issues detected!" - return 1 - fi - else - print_status "No YAML files found, skipping yamllint check" - fi + if find . -name "*.yml" -o -name "*.yaml" -type f | head -1 | grep -q .; then + if yamllint -c .yamllint .; then + print_success "YAML files check passed" + else + print_error "YAML file issues detected!" + return 1 + fi + else + print_status "No YAML files found, skipping yamllint check" + fi } # Generate security report generate_report() { - print_status "Generating security scan report..." + print_status "Generating security scan report..." - local report_file="security-report.md" + local report_file="security-report.md" - cat >"$report_file" <"$report_file" <