Files
breakpilot-compliance/breakpilot-compliance-sdk/services/security-scanner/internal/scanner/manager.go
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

410 lines
10 KiB
Go

// 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"`
}