// Package scanner manages security scanning tools package scanner import ( "sync" "time" "github.com/google/uuid" "go.uber.org/zap" ) // Manager orchestrates security scanning tools type Manager struct { logger *zap.Logger tools map[string]*Tool scans map[string]*Scan mu sync.RWMutex } // Tool represents a security scanning tool type Tool struct { Name string `json:"name"` Version string `json:"version"` Available bool `json:"available"` Description string `json:"description"` Category string `json:"category"` // secrets, sast, container, dependencies, sbom } // Scan represents a security scan type Scan struct { ID string `json:"id"` Status string `json:"status"` // PENDING, RUNNING, COMPLETED, FAILED Tools []string `json:"tools"` TargetPath string `json:"target_path"` StartedAt time.Time `json:"started_at"` CompletedAt time.Time `json:"completed_at,omitempty"` Findings []Finding `json:"findings"` Summary Summary `json:"summary"` } // Finding represents a security finding type Finding struct { ID string `json:"id"` Tool string `json:"tool"` Severity string `json:"severity"` // CRITICAL, HIGH, MEDIUM, LOW Title string `json:"title"` Description string `json:"description"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` Recommendation string `json:"recommendation"` Status string `json:"status"` // OPEN, IN_PROGRESS, RESOLVED, FALSE_POSITIVE CreatedAt time.Time `json:"created_at"` CVE string `json:"cve,omitempty"` } // Summary represents scan summary type Summary struct { Critical int `json:"critical"` High int `json:"high"` Medium int `json:"medium"` Low int `json:"low"` Total int `json:"total"` } // SBOM represents a Software Bill of Materials type SBOM struct { ID string `json:"id"` Format string `json:"format"` // cyclonedx, spdx Version string `json:"version"` GeneratedAt time.Time `json:"generated_at"` Components []Component `json:"components"` Dependencies []string `json:"dependencies"` } // Component represents an SBOM component type Component struct { Name string `json:"name"` Version string `json:"version"` Type string `json:"type"` // library, application, framework License string `json:"license"` Category string `json:"category"` Vulnerabilities []string `json:"vulnerabilities,omitempty"` } // NewManager creates a new scanner manager func NewManager(logger *zap.Logger) *Manager { m := &Manager{ logger: logger, tools: make(map[string]*Tool), scans: make(map[string]*Scan), } // Initialize tools m.initializeTools() return m } func (m *Manager) initializeTools() { m.tools = map[string]*Tool{ "gitleaks": { Name: "Gitleaks", Version: "8.18.0", Available: true, Description: "Detect secrets and sensitive data in git repositories", Category: "secrets", }, "semgrep": { Name: "Semgrep", Version: "1.51.0", Available: true, Description: "Static analysis for multiple languages", Category: "sast", }, "bandit": { Name: "Bandit", Version: "1.7.6", Available: true, Description: "Security linter for Python code", Category: "sast", }, "trivy": { Name: "Trivy", Version: "0.48.0", Available: true, Description: "Vulnerability scanner for containers and filesystems", Category: "container", }, "grype": { Name: "Grype", Version: "0.73.0", Available: true, Description: "Vulnerability scanner for dependencies", Category: "dependencies", }, "syft": { Name: "Syft", Version: "0.98.0", Available: true, Description: "SBOM generator", Category: "sbom", }, } } // AvailableTools returns list of available tools func (m *Manager) AvailableTools() []string { tools := make([]string, 0, len(m.tools)) for name, tool := range m.tools { if tool.Available { tools = append(tools, name) } } return tools } // GetTools returns all tools with their status func (m *Manager) GetTools() map[string]*Tool { return m.tools } // StartScan starts a new security scan func (m *Manager) StartScan(tools []string, targetPath string) *Scan { scan := &Scan{ ID: uuid.New().String(), Status: "RUNNING", Tools: tools, TargetPath: targetPath, StartedAt: time.Now(), Findings: []Finding{}, } m.mu.Lock() m.scans[scan.ID] = scan m.mu.Unlock() // Run scan asynchronously go m.runScan(scan) return scan } func (m *Manager) runScan(scan *Scan) { m.logger.Info("Starting scan", zap.String("id", scan.ID), zap.Strings("tools", scan.Tools)) // Simulate scanning time.Sleep(3 * time.Second) // Generate sample findings findings := m.generateSampleFindings(scan.Tools) // Update scan m.mu.Lock() scan.Status = "COMPLETED" scan.CompletedAt = time.Now() scan.Findings = findings scan.Summary = m.calculateSummary(findings) m.mu.Unlock() m.logger.Info("Scan completed", zap.String("id", scan.ID), zap.Int("findings", len(findings))) } func (m *Manager) generateSampleFindings(tools []string) []Finding { findings := []Finding{} for _, tool := range tools { switch tool { case "gitleaks": // Usually no findings in clean repos case "trivy": findings = append(findings, Finding{ ID: uuid.New().String(), Tool: "trivy", Severity: "HIGH", Title: "CVE-2024-1234 in lodash", Description: "Prototype pollution vulnerability in lodash < 4.17.21", Recommendation: "Upgrade lodash to version 4.17.21 or higher", Status: "OPEN", CreatedAt: time.Now(), CVE: "CVE-2024-1234", }) case "semgrep": findings = append(findings, Finding{ ID: uuid.New().String(), Tool: "semgrep", Severity: "MEDIUM", Title: "Potential SQL injection", Description: "User input used in SQL query without proper sanitization", File: "src/db/queries.ts", Line: 42, Recommendation: "Use parameterized queries", Status: "OPEN", CreatedAt: time.Now(), }) case "grype": findings = append(findings, Finding{ ID: uuid.New().String(), Tool: "grype", Severity: "LOW", Title: "Outdated dependency", Description: "axios@0.21.0 has known vulnerabilities", Recommendation: "Update to axios@1.6.0 or higher", Status: "OPEN", CreatedAt: time.Now(), }) } } return findings } func (m *Manager) calculateSummary(findings []Finding) Summary { summary := Summary{} for _, f := range findings { switch f.Severity { case "CRITICAL": summary.Critical++ case "HIGH": summary.High++ case "MEDIUM": summary.Medium++ case "LOW": summary.Low++ } summary.Total++ } return summary } // GetScan returns a scan by ID func (m *Manager) GetScan(scanID string) *Scan { m.mu.RLock() defer m.mu.RUnlock() return m.scans[scanID] } // GetAllFindings returns all findings across scans func (m *Manager) GetAllFindings() []Finding { m.mu.RLock() defer m.mu.RUnlock() findings := []Finding{} for _, scan := range m.scans { findings = append(findings, scan.Findings...) } return findings } // UpdateFindingStatus updates the status of a finding func (m *Manager) UpdateFindingStatus(findingID, status string) bool { m.mu.Lock() defer m.mu.Unlock() for _, scan := range m.scans { for i := range scan.Findings { if scan.Findings[i].ID == findingID { scan.Findings[i].Status = status return true } } } return false } // GenerateSBOM generates a Software Bill of Materials func (m *Manager) GenerateSBOM(targetPath string) *SBOM { sbom := &SBOM{ ID: uuid.New().String(), Format: "cyclonedx", Version: "1.5", GeneratedAt: time.Now(), Components: m.generateSampleComponents(), } return sbom } func (m *Manager) generateSampleComponents() []Component { return []Component{ { Name: "react", Version: "18.2.0", Type: "library", License: "MIT", Category: "frontend", }, { Name: "typescript", Version: "5.3.3", Type: "library", License: "Apache-2.0", Category: "tooling", }, { Name: "express", Version: "4.18.2", Type: "framework", License: "MIT", Category: "backend", }, { Name: "lodash", Version: "4.17.21", Type: "library", License: "MIT", Category: "utility", }, } } // GetRecommendations generates recommendations based on findings func (m *Manager) GetRecommendations() []Recommendation { findings := m.GetAllFindings() recs := []Recommendation{} // Group by category hasVulns := false hasSecrets := false hasSAST := false for _, f := range findings { switch m.tools[f.Tool].Category { case "dependencies", "container": hasVulns = true case "secrets": hasSecrets = true case "sast": hasSAST = true } } if hasVulns { recs = append(recs, Recommendation{ Priority: "HIGH", Category: "DEPENDENCIES", Title: "Update vulnerable dependencies", Description: "Several dependencies have known vulnerabilities. Run 'npm audit fix' or equivalent.", }) } if hasSecrets { recs = append(recs, Recommendation{ Priority: "CRITICAL", Category: "SECRETS", Title: "Remove exposed secrets", Description: "Secrets detected in codebase. Remove and rotate immediately.", }) } if hasSAST { recs = append(recs, Recommendation{ Priority: "MEDIUM", Category: "CODE_QUALITY", Title: "Address code quality issues", Description: "Static analysis found potential security issues in code.", }) } return recs } // Recommendation represents a security recommendation type Recommendation struct { Priority string `json:"priority"` Category string `json:"category"` Title string `json:"title"` Description string `json:"description"` }