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>
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
// 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user