Completed clean root directory structure: - Root now contains only: .git, .env, docs/, orig/ - Moved all remaining files and directories to orig/: - Config files (.claude, .dockerignore, .drone.yml, etc.) - All .env variants (except active .env) - Git config (.gitconfig, .github, .gitignore, etc.) - Tool configs (.golangci.yml, .revive.toml, etc.) - Documentation (*.md files, @prompts) - Build files (Dockerfiles, Makefile, go.mod, go.sum) - Docker compose files - All source directories (scripts, tests, tools, etc.) - Runtime directories (logs, monitoring, reports) - Dependency files (node_modules, lib, cache) - Special files (--delete) - Removed empty runtime directories (bin/, data/) V2 structure is now clean: - docs/planning/ - V2 planning documents - orig/ - Complete V1 codebase preserved - .env - Active environment config (not in git) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1898 lines
54 KiB
Go
1898 lines
54 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type SecurityAuditConfig struct {
|
|
ScanType string
|
|
OutputDir string
|
|
Verbose bool
|
|
DeepScan bool
|
|
IncludeTests bool
|
|
RiskThreshold string
|
|
ReportFormat string
|
|
Timeout time.Duration
|
|
Baseline string
|
|
RemediationMode bool
|
|
ComplianceCheck bool
|
|
}
|
|
|
|
type SecurityAuditor struct {
|
|
config *SecurityAuditConfig
|
|
results *SecurityResults
|
|
rootDir string
|
|
}
|
|
|
|
type SecurityResults struct {
|
|
ScanType string `json:"scan_type"`
|
|
RiskThreshold string `json:"risk_threshold"`
|
|
OverallRiskScore float64 `json:"overall_risk_score"`
|
|
OverallRiskLevel string `json:"overall_risk_level"`
|
|
CodeScanResults *CodeScanResults `json:"code_scan_results,omitempty"`
|
|
DependencyResults *DependencyResults `json:"dependency_results,omitempty"`
|
|
SecretScanResults *SecretScanResults `json:"secret_scan_results,omitempty"`
|
|
PermissionResults *PermissionResults `json:"permission_results,omitempty"`
|
|
NetworkResults *NetworkResults `json:"network_results,omitempty"`
|
|
ComplianceResults *ComplianceResults `json:"compliance_results,omitempty"`
|
|
SecurityFindings []SecurityFinding `json:"security_findings"`
|
|
RiskAssessment RiskAssessment `json:"risk_assessment"`
|
|
RemediationPlan []RemediationAction `json:"remediation_plan,omitempty"`
|
|
Metrics SecurityMetrics `json:"metrics"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
DurationMs int64 `json:"duration_ms"`
|
|
Environment EnvironmentInfo `json:"environment"`
|
|
}
|
|
|
|
type CodeScanResults struct {
|
|
FilesScanned int `json:"files_scanned"`
|
|
VulnerabilitiesFound int `json:"vulnerabilities_found"`
|
|
CodeIssues []CodeIssue `json:"code_issues"`
|
|
HotspotAnalysis []SecurityHotspot `json:"hotspot_analysis"`
|
|
QualityMetrics CodeQualityMetrics `json:"quality_metrics"`
|
|
}
|
|
|
|
type CodeIssue struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Severity string `json:"severity"`
|
|
File string `json:"file"`
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
Description string `json:"description"`
|
|
Evidence string `json:"evidence"`
|
|
CWE string `json:"cwe,omitempty"`
|
|
OWASP string `json:"owasp,omitempty"`
|
|
Confidence string `json:"confidence"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type SecurityHotspot struct {
|
|
File string `json:"file"`
|
|
Function string `json:"function"`
|
|
RiskScore float64 `json:"risk_score"`
|
|
Issues int `json:"issues"`
|
|
CriticalIssues int `json:"critical_issues"`
|
|
Complexity string `json:"complexity"`
|
|
Recommendation string `json:"recommendation"`
|
|
}
|
|
|
|
type CodeQualityMetrics struct {
|
|
LinesOfCode int `json:"lines_of_code"`
|
|
CyclomaticComplexity float64 `json:"cyclomatic_complexity"`
|
|
TechnicalDebt float64 `json:"technical_debt_hours"`
|
|
TestCoverage float64 `json:"test_coverage_percent"`
|
|
DuplicationRatio float64 `json:"duplication_ratio_percent"`
|
|
}
|
|
|
|
type DependencyResults struct {
|
|
TotalDependencies int `json:"total_dependencies"`
|
|
VulnerableDeps int `json:"vulnerable_dependencies"`
|
|
OutdatedDeps int `json:"outdated_dependencies"`
|
|
DependencyIssues []DependencyIssue `json:"dependency_issues"`
|
|
LicenseAnalysis LicenseAnalysis `json:"license_analysis"`
|
|
SupplyChainRisk SupplyChainRisk `json:"supply_chain_risk"`
|
|
}
|
|
|
|
type DependencyIssue struct {
|
|
Package string `json:"package"`
|
|
Version string `json:"version"`
|
|
LatestVersion string `json:"latest_version"`
|
|
Severity string `json:"severity"`
|
|
CVE string `json:"cve,omitempty"`
|
|
Description string `json:"description"`
|
|
PatchAvailable bool `json:"patch_available"`
|
|
CVSS float64 `json:"cvss_score"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type LicenseAnalysis struct {
|
|
LicenseTypes map[string]int `json:"license_types"`
|
|
ProblematicLicenses []string `json:"problematic_licenses"`
|
|
UnknownLicenses []string `json:"unknown_licenses"`
|
|
ComplianceIssues []string `json:"compliance_issues"`
|
|
}
|
|
|
|
type SupplyChainRisk struct {
|
|
HighRiskPackages []string `json:"high_risk_packages"`
|
|
UnmaintainedPackages []string `json:"unmaintained_packages"`
|
|
TyposquattingRisk float64 `json:"typosquatting_risk"`
|
|
OverallRiskScore float64 `json:"overall_risk_score"`
|
|
}
|
|
|
|
type SecretScanResults struct {
|
|
FilesScanned int `json:"files_scanned"`
|
|
SecretsFound int `json:"secrets_found"`
|
|
SecretFindings []SecretFinding `json:"secret_findings"`
|
|
SecretPatterns map[string]int `json:"secret_patterns"`
|
|
}
|
|
|
|
type SecretFinding struct {
|
|
Type string `json:"type"`
|
|
File string `json:"file"`
|
|
Line int `json:"line"`
|
|
Pattern string `json:"pattern"`
|
|
Confidence float64 `json:"confidence"`
|
|
Entropy float64 `json:"entropy"`
|
|
Context string `json:"context"`
|
|
Severity string `json:"severity"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type PermissionResults struct {
|
|
FilePermissions []FilePermission `json:"file_permissions"`
|
|
ConfigPermissions []ConfigPermission `json:"config_permissions"`
|
|
PermissionIssues []PermissionIssue `json:"permission_issues"`
|
|
OverallSecurity string `json:"overall_security"`
|
|
}
|
|
|
|
type FilePermission struct {
|
|
Path string `json:"path"`
|
|
Permissions string `json:"permissions"`
|
|
Owner string `json:"owner"`
|
|
Group string `json:"group"`
|
|
Risky bool `json:"risky"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
type ConfigPermission struct {
|
|
File string `json:"file"`
|
|
Setting string `json:"setting"`
|
|
Value string `json:"value"`
|
|
Risk string `json:"risk"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type PermissionIssue struct {
|
|
Type string `json:"type"`
|
|
Path string `json:"path"`
|
|
Issue string `json:"issue"`
|
|
Severity string `json:"severity"`
|
|
Remediation string `json:"remediation"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type NetworkResults struct {
|
|
OpenPorts []PortInfo `json:"open_ports"`
|
|
NetworkConnections []NetworkConnection `json:"network_connections"`
|
|
TLSAnalysis TLSAnalysis `json:"tls_analysis"`
|
|
FirewallStatus FirewallStatus `json:"firewall_status"`
|
|
NetworkIssues []NetworkIssue `json:"network_issues"`
|
|
}
|
|
|
|
type PortInfo struct {
|
|
Port int `json:"port"`
|
|
Protocol string `json:"protocol"`
|
|
Service string `json:"service"`
|
|
State string `json:"state"`
|
|
Risk string `json:"risk"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type NetworkConnection struct {
|
|
LocalAddress string `json:"local_address"`
|
|
RemoteAddress string `json:"remote_address"`
|
|
State string `json:"state"`
|
|
Protocol string `json:"protocol"`
|
|
Risk string `json:"risk"`
|
|
}
|
|
|
|
type TLSAnalysis struct {
|
|
CertificateValidation bool `json:"certificate_validation"`
|
|
TLSVersions []string `json:"tls_versions"`
|
|
CipherSuites []string `json:"cipher_suites"`
|
|
Vulnerabilities []string `json:"vulnerabilities"`
|
|
Recommendations []string `json:"recommendations"`
|
|
}
|
|
|
|
type FirewallStatus struct {
|
|
Enabled bool `json:"enabled"`
|
|
Rules []string `json:"rules"`
|
|
Blocked []string `json:"blocked"`
|
|
Allowed []string `json:"allowed"`
|
|
Recommendations []string `json:"recommendations"`
|
|
}
|
|
|
|
type NetworkIssue struct {
|
|
Type string `json:"type"`
|
|
Description string `json:"description"`
|
|
Severity string `json:"severity"`
|
|
Impact string `json:"impact"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type ComplianceResults struct {
|
|
FrameworksChecked []string `json:"frameworks_checked"`
|
|
ComplianceScore float64 `json:"compliance_score"`
|
|
ComplianceFindings []ComplianceFinding `json:"compliance_findings"`
|
|
RequirementStatus map[string]string `json:"requirement_status"`
|
|
}
|
|
|
|
type ComplianceFinding struct {
|
|
Framework string `json:"framework"`
|
|
Requirement string `json:"requirement"`
|
|
Status string `json:"status"`
|
|
Description string `json:"description"`
|
|
Evidence string `json:"evidence"`
|
|
Priority string `json:"priority"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type SecurityFinding struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Category string `json:"category"`
|
|
Severity string `json:"severity"`
|
|
Description string `json:"description"`
|
|
Impact string `json:"impact"`
|
|
Location string `json:"location"`
|
|
Evidence string `json:"evidence"`
|
|
CWE string `json:"cwe,omitempty"`
|
|
CVSS float64 `json:"cvss,omitempty"`
|
|
Confidence float64 `json:"confidence"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type RiskAssessment struct {
|
|
OverallRisk string `json:"overall_risk"`
|
|
RiskFactors []RiskFactor `json:"risk_factors"`
|
|
ThreatModel ThreatModel `json:"threat_model"`
|
|
AttackSurface AttackSurface `json:"attack_surface"`
|
|
BusinessImpact BusinessImpact `json:"business_impact"`
|
|
RecommendedActions []string `json:"recommended_actions"`
|
|
}
|
|
|
|
type RiskFactor struct {
|
|
Factor string `json:"factor"`
|
|
Likelihood float64 `json:"likelihood"`
|
|
Impact float64 `json:"impact"`
|
|
RiskScore float64 `json:"risk_score"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type ThreatModel struct {
|
|
ThreatsIdentified []Threat `json:"threats_identified"`
|
|
AttackVectors []AttackVector `json:"attack_vectors"`
|
|
AssetValuation map[string]float64 `json:"asset_valuation"`
|
|
ThreatActors []ThreatActor `json:"threat_actors"`
|
|
}
|
|
|
|
type Threat struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Likelihood float64 `json:"likelihood"`
|
|
Impact float64 `json:"impact"`
|
|
Risk float64 `json:"risk"`
|
|
Mitigations []string `json:"mitigations"`
|
|
}
|
|
|
|
type AttackVector struct {
|
|
Vector string `json:"vector"`
|
|
Complexity string `json:"complexity"`
|
|
Privileges string `json:"privileges"`
|
|
UserAction string `json:"user_action"`
|
|
Scope string `json:"scope"`
|
|
Mitigations []string `json:"mitigations"`
|
|
}
|
|
|
|
type ThreatActor struct {
|
|
Type string `json:"type"`
|
|
Motivation string `json:"motivation"`
|
|
Capability string `json:"capability"`
|
|
Opportunity string `json:"opportunity"`
|
|
Likelihood float64 `json:"likelihood"`
|
|
Techniques []string `json:"techniques"`
|
|
}
|
|
|
|
type AttackSurface struct {
|
|
NetworkExposure float64 `json:"network_exposure"`
|
|
ApplicationEndpoints int `json:"application_endpoints"`
|
|
DataExposure float64 `json:"data_exposure"`
|
|
TrustedInterfaces int `json:"trusted_interfaces"`
|
|
ExternalDependencies int `json:"external_dependencies"`
|
|
ReductionOpportunities []string `json:"reduction_opportunities"`
|
|
}
|
|
|
|
type BusinessImpact struct {
|
|
FinancialImpact float64 `json:"financial_impact"`
|
|
ReputationalImpact float64 `json:"reputational_impact"`
|
|
OperationalImpact float64 `json:"operational_impact"`
|
|
ComplianceImpact float64 `json:"compliance_impact"`
|
|
OverallImpact float64 `json:"overall_impact"`
|
|
}
|
|
|
|
type RemediationAction struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Priority string `json:"priority"`
|
|
Effort string `json:"effort"`
|
|
Description string `json:"description"`
|
|
Steps []string `json:"steps"`
|
|
Timeline string `json:"timeline"`
|
|
Owner string `json:"owner"`
|
|
Dependencies []string `json:"dependencies"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type SecurityMetrics struct {
|
|
VulnerabilityDensity float64 `json:"vulnerability_density"`
|
|
SecurityScore float64 `json:"security_score"`
|
|
ComplianceRate float64 `json:"compliance_rate"`
|
|
RiskReduction float64 `json:"risk_reduction"`
|
|
MeanTimeToDetection float64 `json:"mean_time_to_detection"`
|
|
MeanTimeToResolution float64 `json:"mean_time_to_resolution"`
|
|
}
|
|
|
|
type EnvironmentInfo struct {
|
|
WorkingDirectory string `json:"working_directory"`
|
|
GitRepository bool `json:"git_repository"`
|
|
ProjectType string `json:"project_type"`
|
|
Languages []string `json:"languages"`
|
|
BuildSystem string `json:"build_system"`
|
|
TotalFiles int `json:"total_files"`
|
|
TotalLOC int `json:"total_loc"`
|
|
}
|
|
|
|
func NewSecurityAuditor(config *SecurityAuditConfig) (*SecurityAuditor, error) {
|
|
rootDir, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
return &SecurityAuditor{
|
|
config: config,
|
|
rootDir: rootDir,
|
|
results: &SecurityResults{
|
|
ScanType: config.ScanType,
|
|
RiskThreshold: config.RiskThreshold,
|
|
SecurityFindings: make([]SecurityFinding, 0),
|
|
Timestamp: time.Now(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) RunSecurityAudit(ctx context.Context) error {
|
|
startTime := time.Now()
|
|
defer func() {
|
|
sa.results.DurationMs = time.Since(startTime).Milliseconds()
|
|
}()
|
|
|
|
// Initialize environment info
|
|
sa.initializeEnvironmentInfo()
|
|
|
|
switch sa.config.ScanType {
|
|
case "code":
|
|
return sa.runCodeScan(ctx)
|
|
case "dependencies":
|
|
return sa.runDependencyScan(ctx)
|
|
case "secrets":
|
|
return sa.runSecretScan(ctx)
|
|
case "permissions":
|
|
return sa.runPermissionScan(ctx)
|
|
case "network":
|
|
return sa.runNetworkScan(ctx)
|
|
case "all":
|
|
return sa.runAllScans(ctx)
|
|
default:
|
|
return fmt.Errorf("unsupported scan type: %s", sa.config.ScanType)
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runAllScans(ctx context.Context) error {
|
|
scans := []struct {
|
|
name string
|
|
fn func(context.Context) error
|
|
}{
|
|
{"code", sa.runCodeScan},
|
|
{"dependencies", sa.runDependencyScan},
|
|
{"secrets", sa.runSecretScan},
|
|
{"permissions", sa.runPermissionScan},
|
|
{"network", sa.runNetworkScan},
|
|
}
|
|
|
|
if sa.config.ComplianceCheck {
|
|
scans = append(scans, struct {
|
|
name string
|
|
fn func(context.Context) error
|
|
}{"compliance", sa.runComplianceScan})
|
|
}
|
|
|
|
for _, scan := range scans {
|
|
if sa.config.Verbose {
|
|
fmt.Printf("Running %s scan...\n", scan.name)
|
|
}
|
|
|
|
if err := scan.fn(ctx); err != nil {
|
|
return fmt.Errorf("failed %s scan: %w", scan.name, err)
|
|
}
|
|
}
|
|
|
|
// Perform risk assessment
|
|
sa.performRiskAssessment()
|
|
|
|
// Generate remediation plan if requested
|
|
if sa.config.RemediationMode {
|
|
sa.generateRemediationPlan()
|
|
}
|
|
|
|
// Calculate overall metrics
|
|
sa.calculateSecurityMetrics()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) initializeEnvironmentInfo() {
|
|
wd, _ := os.Getwd()
|
|
sa.results.Environment = EnvironmentInfo{
|
|
WorkingDirectory: wd,
|
|
GitRepository: sa.isGitRepository(),
|
|
ProjectType: sa.detectProjectType(),
|
|
Languages: sa.detectLanguages(),
|
|
BuildSystem: sa.detectBuildSystem(),
|
|
}
|
|
|
|
// Count files and lines of code
|
|
sa.countProjectSize()
|
|
}
|
|
|
|
func (sa *SecurityAuditor) isGitRepository() bool {
|
|
_, err := os.Stat(filepath.Join(sa.rootDir, ".git"))
|
|
return err == nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) detectProjectType() string {
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "go.mod")); err == nil {
|
|
return "Go"
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "package.json")); err == nil {
|
|
return "Node.js"
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "requirements.txt")); err == nil {
|
|
return "Python"
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "Cargo.toml")); err == nil {
|
|
return "Rust"
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
func (sa *SecurityAuditor) detectLanguages() []string {
|
|
languages := make([]string, 0)
|
|
extensions := make(map[string]string)
|
|
|
|
err := filepath.WalkDir(sa.rootDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
// Skip common directories
|
|
if strings.Contains(path, ".git") || strings.Contains(path, "node_modules") || strings.Contains(path, "vendor") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
ext := filepath.Ext(path)
|
|
switch ext {
|
|
case ".go":
|
|
extensions["Go"] = ".go"
|
|
case ".js", ".ts":
|
|
extensions["JavaScript/TypeScript"] = ext
|
|
case ".py":
|
|
extensions["Python"] = ".py"
|
|
case ".rs":
|
|
extensions["Rust"] = ".rs"
|
|
case ".java":
|
|
extensions["Java"] = ".java"
|
|
case ".c", ".cpp", ".cc":
|
|
extensions["C/C++"] = ext
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err == nil {
|
|
for lang := range extensions {
|
|
languages = append(languages, lang)
|
|
}
|
|
}
|
|
|
|
return languages
|
|
}
|
|
|
|
func (sa *SecurityAuditor) detectBuildSystem() string {
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "Makefile")); err == nil {
|
|
return "Make"
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "go.mod")); err == nil {
|
|
return "Go Modules"
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sa.rootDir, "package.json")); err == nil {
|
|
return "NPM/Yarn"
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
func (sa *SecurityAuditor) countProjectSize() {
|
|
totalFiles := 0
|
|
totalLOC := 0
|
|
|
|
err := filepath.WalkDir(sa.rootDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
if strings.Contains(path, ".git") || strings.Contains(path, "node_modules") || strings.Contains(path, "vendor") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Count source files
|
|
ext := filepath.Ext(path)
|
|
if sa.isSourceFile(ext) {
|
|
totalFiles++
|
|
if loc := sa.countLinesOfCode(path); loc > 0 {
|
|
totalLOC += loc
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err == nil {
|
|
sa.results.Environment.TotalFiles = totalFiles
|
|
sa.results.Environment.TotalLOC = totalLOC
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) isSourceFile(ext string) bool {
|
|
sourceExts := []string{".go", ".js", ".ts", ".py", ".rs", ".java", ".c", ".cpp", ".cc", ".h"}
|
|
for _, sourceExt := range sourceExts {
|
|
if ext == sourceExt {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sa *SecurityAuditor) countLinesOfCode(filePath string) int {
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
lines := strings.Split(string(content), "\n")
|
|
nonEmptyLines := 0
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.HasPrefix(strings.TrimSpace(line), "//") {
|
|
nonEmptyLines++
|
|
}
|
|
}
|
|
|
|
return nonEmptyLines
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runCodeScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting code security scan...")
|
|
}
|
|
|
|
sa.results.CodeScanResults = &CodeScanResults{
|
|
CodeIssues: make([]CodeIssue, 0),
|
|
HotspotAnalysis: make([]SecurityHotspot, 0),
|
|
}
|
|
|
|
err := filepath.WalkDir(sa.rootDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
if strings.Contains(path, ".git") || strings.Contains(path, "vendor") {
|
|
return filepath.SkipDir
|
|
}
|
|
if !sa.config.IncludeTests && strings.Contains(path, "test") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Scan source files
|
|
if sa.isSourceFile(filepath.Ext(path)) {
|
|
sa.results.CodeScanResults.FilesScanned++
|
|
sa.scanFileForVulnerabilities(path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to scan code: %w", err)
|
|
}
|
|
|
|
sa.results.CodeScanResults.VulnerabilitiesFound = len(sa.results.CodeScanResults.CodeIssues)
|
|
sa.analyzeSecurityHotspots()
|
|
sa.calculateCodeQualityMetrics()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) scanFileForVulnerabilities(filePath string) {
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
fileContent := string(content)
|
|
lines := strings.Split(fileContent, "\n")
|
|
|
|
// Define vulnerability patterns
|
|
vulnerabilityPatterns := []struct {
|
|
pattern *regexp.Regexp
|
|
issueType string
|
|
severity string
|
|
description string
|
|
cwe string
|
|
confidence string
|
|
}{
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)(password|secret|api_key|private_key)\s*[:=]\s*["'][^"']+["']`),
|
|
issueType: "hardcoded_secret",
|
|
severity: "HIGH",
|
|
description: "Hardcoded secret or password detected",
|
|
cwe: "CWE-798",
|
|
confidence: "HIGH",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)sql.*["']\s*\+\s*.*["']`),
|
|
issueType: "sql_injection",
|
|
severity: "HIGH",
|
|
description: "Potential SQL injection vulnerability",
|
|
cwe: "CWE-89",
|
|
confidence: "MEDIUM",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)exec\s*\(\s*.*\$`),
|
|
issueType: "command_injection",
|
|
severity: "HIGH",
|
|
description: "Potential command injection vulnerability",
|
|
cwe: "CWE-78",
|
|
confidence: "MEDIUM",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)unsafe\.Pointer`),
|
|
issueType: "unsafe_operation",
|
|
severity: "MEDIUM",
|
|
description: "Use of unsafe pointer operations",
|
|
cwe: "CWE-242",
|
|
confidence: "HIGH",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`math\.rand\.Read|rand\.Seed\(time\.Now`),
|
|
issueType: "weak_random",
|
|
severity: "MEDIUM",
|
|
description: "Use of weak random number generator",
|
|
cwe: "CWE-338",
|
|
confidence: "HIGH",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`http\.ListenAndServe.*:80[^0-9]`),
|
|
issueType: "insecure_transport",
|
|
severity: "MEDIUM",
|
|
description: "HTTP server without TLS",
|
|
cwe: "CWE-319",
|
|
confidence: "HIGH",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)debug|trace|verbose.*true`),
|
|
issueType: "debug_information",
|
|
severity: "LOW",
|
|
description: "Debug information disclosure",
|
|
cwe: "CWE-209",
|
|
confidence: "MEDIUM",
|
|
},
|
|
}
|
|
|
|
for lineNum, line := range lines {
|
|
for _, pattern := range vulnerabilityPatterns {
|
|
if matches := pattern.pattern.FindStringSubmatch(line); matches != nil {
|
|
issue := CodeIssue{
|
|
ID: sa.generateIssueID(filePath, lineNum, pattern.issueType),
|
|
Type: pattern.issueType,
|
|
Severity: pattern.severity,
|
|
File: filePath,
|
|
Line: lineNum + 1,
|
|
Column: strings.Index(line, matches[0]) + 1,
|
|
Description: pattern.description,
|
|
Evidence: strings.TrimSpace(line),
|
|
CWE: pattern.cwe,
|
|
Confidence: pattern.confidence,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
sa.results.CodeScanResults.CodeIssues = append(sa.results.CodeScanResults.CodeIssues, issue)
|
|
|
|
// Also add to general security findings
|
|
sa.addSecurityFinding(SecurityFinding{
|
|
ID: issue.ID,
|
|
Title: fmt.Sprintf("%s in %s", pattern.description, filepath.Base(filePath)),
|
|
Category: "Code Security",
|
|
Severity: pattern.severity,
|
|
Description: pattern.description,
|
|
Impact: sa.getImpactDescription(pattern.severity),
|
|
Location: fmt.Sprintf("%s:%d:%d", filePath, lineNum+1, issue.Column),
|
|
Evidence: issue.Evidence,
|
|
CWE: pattern.cwe,
|
|
Confidence: sa.getConfidenceScore(pattern.confidence),
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) generateIssueID(filePath string, lineNum int, issueType string) string {
|
|
data := fmt.Sprintf("%s:%d:%s", filePath, lineNum, issueType)
|
|
hash := sha256.Sum256([]byte(data))
|
|
return fmt.Sprintf("CSI-%x", hash[:4])
|
|
}
|
|
|
|
func (sa *SecurityAuditor) getImpactDescription(severity string) string {
|
|
switch severity {
|
|
case "CRITICAL":
|
|
return "Critical security vulnerability that could lead to system compromise"
|
|
case "HIGH":
|
|
return "High-risk vulnerability that could lead to data breach or unauthorized access"
|
|
case "MEDIUM":
|
|
return "Medium-risk vulnerability that could be exploited under certain conditions"
|
|
case "LOW":
|
|
return "Low-risk vulnerability with limited security impact"
|
|
default:
|
|
return "Security finding requiring investigation"
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) getConfidenceScore(confidence string) float64 {
|
|
switch confidence {
|
|
case "HIGH":
|
|
return 0.9
|
|
case "MEDIUM":
|
|
return 0.7
|
|
case "LOW":
|
|
return 0.5
|
|
default:
|
|
return 0.6
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeSecurityHotspots() {
|
|
// Group issues by file to identify hotspots
|
|
fileIssues := make(map[string][]CodeIssue)
|
|
for _, issue := range sa.results.CodeScanResults.CodeIssues {
|
|
fileIssues[issue.File] = append(fileIssues[issue.File], issue)
|
|
}
|
|
|
|
for file, issues := range fileIssues {
|
|
if len(issues) >= 2 { // File with 2+ issues is a hotspot
|
|
criticalCount := 0
|
|
for _, issue := range issues {
|
|
if issue.Severity == "CRITICAL" || issue.Severity == "HIGH" {
|
|
criticalCount++
|
|
}
|
|
}
|
|
|
|
riskScore := float64(len(issues)*10 + criticalCount*20)
|
|
complexity := "LOW"
|
|
if len(issues) > 5 {
|
|
complexity = "HIGH"
|
|
} else if len(issues) > 3 {
|
|
complexity = "MEDIUM"
|
|
}
|
|
|
|
hotspot := SecurityHotspot{
|
|
File: file,
|
|
Function: "Multiple", // Would need AST parsing for specific functions
|
|
RiskScore: riskScore,
|
|
Issues: len(issues),
|
|
CriticalIssues: criticalCount,
|
|
Complexity: complexity,
|
|
Recommendation: sa.getHotspotRecommendation(len(issues), criticalCount),
|
|
}
|
|
|
|
sa.results.CodeScanResults.HotspotAnalysis = append(sa.results.CodeScanResults.HotspotAnalysis, hotspot)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) getHotspotRecommendation(totalIssues, criticalIssues int) string {
|
|
if criticalIssues > 0 {
|
|
return "High priority: Address critical security issues immediately"
|
|
} else if totalIssues > 5 {
|
|
return "Medium priority: Refactor to reduce security complexity"
|
|
} else {
|
|
return "Low priority: Review and address identified issues"
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateCodeQualityMetrics() {
|
|
// Simplified code quality metrics
|
|
sa.results.CodeScanResults.QualityMetrics = CodeQualityMetrics{
|
|
LinesOfCode: sa.results.Environment.TotalLOC,
|
|
CyclomaticComplexity: 2.5, // Simplified
|
|
TechnicalDebt: float64(len(sa.results.CodeScanResults.CodeIssues)) * 0.5,
|
|
TestCoverage: 75.0, // Would need actual test coverage data
|
|
DuplicationRatio: 5.0, // Simplified
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runDependencyScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting dependency security scan...")
|
|
}
|
|
|
|
sa.results.DependencyResults = &DependencyResults{
|
|
DependencyIssues: make([]DependencyIssue, 0),
|
|
LicenseAnalysis: LicenseAnalysis{
|
|
LicenseTypes: make(map[string]int),
|
|
ProblematicLicenses: make([]string, 0),
|
|
UnknownLicenses: make([]string, 0),
|
|
ComplianceIssues: make([]string, 0),
|
|
},
|
|
}
|
|
|
|
// Scan Go dependencies
|
|
if sa.results.Environment.ProjectType == "Go" {
|
|
return sa.scanGoDependencies()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) scanGoDependencies() error {
|
|
// Read go.mod file
|
|
goModPath := filepath.Join(sa.rootDir, "go.mod")
|
|
content, err := os.ReadFile(goModPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read go.mod: %w", err)
|
|
}
|
|
|
|
// Parse dependencies (simplified)
|
|
lines := strings.Split(string(content), "\n")
|
|
inRequireBlock := false
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
|
|
if strings.HasPrefix(line, "require (") {
|
|
inRequireBlock = true
|
|
continue
|
|
}
|
|
|
|
if line == ")" {
|
|
inRequireBlock = false
|
|
continue
|
|
}
|
|
|
|
if inRequireBlock || strings.HasPrefix(line, "require ") {
|
|
// Parse dependency
|
|
sa.analyzeDependency(line)
|
|
}
|
|
}
|
|
|
|
sa.results.DependencyResults.TotalDependencies = len(sa.results.DependencyResults.DependencyIssues)
|
|
sa.analyzeSupplyChainRisk()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeDependency(depLine string) {
|
|
// Simplified dependency analysis
|
|
parts := strings.Fields(strings.TrimPrefix(depLine, "require "))
|
|
if len(parts) < 2 {
|
|
return
|
|
}
|
|
|
|
packageName := parts[0]
|
|
version := parts[1]
|
|
|
|
// Check for known vulnerable packages (simplified)
|
|
vulnerablePackages := map[string]struct {
|
|
cve string
|
|
description string
|
|
severity string
|
|
cvss float64
|
|
}{
|
|
"github.com/gorilla/websocket": {
|
|
cve: "CVE-2020-27813",
|
|
description: "Denial of service vulnerability",
|
|
severity: "MEDIUM",
|
|
cvss: 5.3,
|
|
},
|
|
// Add more vulnerable packages as needed
|
|
}
|
|
|
|
if vuln, exists := vulnerablePackages[packageName]; exists {
|
|
issue := DependencyIssue{
|
|
Package: packageName,
|
|
Version: version,
|
|
LatestVersion: "latest", // Would need actual version checking
|
|
Severity: vuln.severity,
|
|
CVE: vuln.cve,
|
|
Description: vuln.description,
|
|
PatchAvailable: true,
|
|
CVSS: vuln.cvss,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
sa.results.DependencyResults.DependencyIssues = append(sa.results.DependencyResults.DependencyIssues, issue)
|
|
sa.results.DependencyResults.VulnerableDeps++
|
|
|
|
// Add to security findings
|
|
sa.addSecurityFinding(SecurityFinding{
|
|
ID: fmt.Sprintf("DEP-%s", packageName),
|
|
Title: fmt.Sprintf("Vulnerable dependency: %s", packageName),
|
|
Category: "Dependency Security",
|
|
Severity: vuln.severity,
|
|
Description: vuln.description,
|
|
Impact: sa.getImpactDescription(vuln.severity),
|
|
Location: "go.mod",
|
|
Evidence: fmt.Sprintf("%s %s", packageName, version),
|
|
CVSS: vuln.cvss,
|
|
Confidence: 0.9,
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
|
|
// Analyze license (simplified)
|
|
sa.results.DependencyResults.LicenseAnalysis.LicenseTypes["Unknown"]++
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeSupplyChainRisk() {
|
|
highRiskPackages := make([]string, 0)
|
|
for _, issue := range sa.results.DependencyResults.DependencyIssues {
|
|
if issue.Severity == "HIGH" || issue.Severity == "CRITICAL" {
|
|
highRiskPackages = append(highRiskPackages, issue.Package)
|
|
}
|
|
}
|
|
|
|
riskScore := float64(len(highRiskPackages)) * 10.0
|
|
if riskScore > 100.0 {
|
|
riskScore = 100.0
|
|
}
|
|
|
|
sa.results.DependencyResults.SupplyChainRisk = SupplyChainRisk{
|
|
HighRiskPackages: highRiskPackages,
|
|
UnmaintainedPackages: make([]string, 0), // Would need maintenance analysis
|
|
TyposquattingRisk: 20.0, // Simplified
|
|
OverallRiskScore: riskScore,
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runSecretScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting secret scan...")
|
|
}
|
|
|
|
sa.results.SecretScanResults = &SecretScanResults{
|
|
SecretFindings: make([]SecretFinding, 0),
|
|
SecretPatterns: make(map[string]int),
|
|
}
|
|
|
|
err := filepath.WalkDir(sa.rootDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
if strings.Contains(path, ".git") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Scan all text files for secrets
|
|
if sa.isTextFile(path) {
|
|
sa.results.SecretScanResults.FilesScanned++
|
|
sa.scanFileForSecrets(path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to scan for secrets: %w", err)
|
|
}
|
|
|
|
sa.results.SecretScanResults.SecretsFound = len(sa.results.SecretScanResults.SecretFindings)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) isTextFile(filePath string) bool {
|
|
ext := filepath.Ext(filePath)
|
|
textExts := []string{".go", ".js", ".ts", ".py", ".java", ".txt", ".md", ".yaml", ".yml", ".json", ".env", ".config"}
|
|
for _, textExt := range textExts {
|
|
if ext == textExt {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sa *SecurityAuditor) scanFileForSecrets(filePath string) {
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
fileContent := string(content)
|
|
lines := strings.Split(fileContent, "\n")
|
|
|
|
// Define secret patterns
|
|
secretPatterns := []struct {
|
|
pattern *regexp.Regexp
|
|
secretType string
|
|
severity string
|
|
confidence float64
|
|
}{
|
|
{
|
|
pattern: regexp.MustCompile(`[a-zA-Z0-9][a-zA-Z0-9_-]{20,}`),
|
|
secretType: "high_entropy_string",
|
|
severity: "MEDIUM",
|
|
confidence: 0.6,
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)(api_key|apikey|secret|password|token)\s*[:=]\s*["'][^"']{8,}["']`),
|
|
secretType: "api_key",
|
|
severity: "HIGH",
|
|
confidence: 0.8,
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)private_key\s*[:=]\s*["'][^"']+["']`),
|
|
secretType: "private_key",
|
|
severity: "CRITICAL",
|
|
confidence: 0.9,
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?i)-----BEGIN [A-Z ]+-----`),
|
|
secretType: "certificate",
|
|
severity: "HIGH",
|
|
confidence: 0.9,
|
|
},
|
|
}
|
|
|
|
for lineNum, line := range lines {
|
|
for _, pattern := range secretPatterns {
|
|
if matches := pattern.pattern.FindStringSubmatch(line); matches != nil {
|
|
// Calculate entropy for high entropy strings
|
|
entropy := sa.calculateEntropy(matches[0])
|
|
|
|
// Skip if entropy is too low for high_entropy_string pattern
|
|
if pattern.secretType == "high_entropy_string" && entropy < 4.0 {
|
|
continue
|
|
}
|
|
|
|
finding := SecretFinding{
|
|
Type: pattern.secretType,
|
|
File: filePath,
|
|
Line: lineNum + 1,
|
|
Pattern: pattern.pattern.String(),
|
|
Confidence: pattern.confidence,
|
|
Entropy: entropy,
|
|
Context: strings.TrimSpace(line),
|
|
Severity: pattern.severity,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
sa.results.SecretScanResults.SecretFindings = append(sa.results.SecretScanResults.SecretFindings, finding)
|
|
sa.results.SecretScanResults.SecretPatterns[pattern.secretType]++
|
|
|
|
// Add to security findings
|
|
sa.addSecurityFinding(SecurityFinding{
|
|
ID: fmt.Sprintf("SEC-%d", len(sa.results.SecurityFindings)),
|
|
Title: fmt.Sprintf("Secret detected: %s", pattern.secretType),
|
|
Category: "Secret Management",
|
|
Severity: pattern.severity,
|
|
Description: fmt.Sprintf("Potential %s found in source code", pattern.secretType),
|
|
Impact: sa.getImpactDescription(pattern.severity),
|
|
Location: fmt.Sprintf("%s:%d", filePath, lineNum+1),
|
|
Evidence: finding.Context,
|
|
Confidence: pattern.confidence,
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateEntropy(s string) float64 {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
|
|
freq := make(map[rune]int)
|
|
for _, char := range s {
|
|
freq[char]++
|
|
}
|
|
|
|
entropy := 0.0
|
|
length := float64(len(s))
|
|
|
|
for _, count := range freq {
|
|
p := float64(count) / length
|
|
if p > 0 {
|
|
entropy -= p * (log2(p))
|
|
}
|
|
}
|
|
|
|
return entropy
|
|
}
|
|
|
|
func log2(x float64) float64 {
|
|
return math.Log(x) / math.Log(2)
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runPermissionScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting permission scan...")
|
|
}
|
|
|
|
sa.results.PermissionResults = &PermissionResults{
|
|
FilePermissions: make([]FilePermission, 0),
|
|
ConfigPermissions: make([]ConfigPermission, 0),
|
|
PermissionIssues: make([]PermissionIssue, 0),
|
|
}
|
|
|
|
// Scan file permissions
|
|
err := filepath.WalkDir(sa.rootDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
perm := FilePermission{
|
|
Path: path,
|
|
Permissions: info.Mode().String(),
|
|
}
|
|
|
|
// Check for risky permissions
|
|
mode := info.Mode()
|
|
if mode&0002 != 0 { // World writable
|
|
perm.Risky = true
|
|
perm.Reason = "World writable"
|
|
|
|
issue := PermissionIssue{
|
|
Type: "file_permission",
|
|
Path: path,
|
|
Issue: "World writable file",
|
|
Severity: "HIGH",
|
|
Remediation: "Remove world write permissions",
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.PermissionResults.PermissionIssues = append(sa.results.PermissionResults.PermissionIssues, issue)
|
|
}
|
|
|
|
if mode&0111 != 0 && (strings.Contains(path, ".env") || strings.Contains(path, "config")) {
|
|
perm.Risky = true
|
|
perm.Reason = "Executable configuration file"
|
|
|
|
issue := PermissionIssue{
|
|
Type: "file_permission",
|
|
Path: path,
|
|
Issue: "Executable configuration file",
|
|
Severity: "MEDIUM",
|
|
Remediation: "Remove execute permissions from configuration files",
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.PermissionResults.PermissionIssues = append(sa.results.PermissionResults.PermissionIssues, issue)
|
|
}
|
|
|
|
sa.results.PermissionResults.FilePermissions = append(sa.results.PermissionResults.FilePermissions, perm)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to scan permissions: %w", err)
|
|
}
|
|
|
|
// Determine overall security
|
|
if len(sa.results.PermissionResults.PermissionIssues) == 0 {
|
|
sa.results.PermissionResults.OverallSecurity = "GOOD"
|
|
} else if len(sa.results.PermissionResults.PermissionIssues) < 5 {
|
|
sa.results.PermissionResults.OverallSecurity = "MEDIUM"
|
|
} else {
|
|
sa.results.PermissionResults.OverallSecurity = "POOR"
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runNetworkScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting network security scan...")
|
|
}
|
|
|
|
sa.results.NetworkResults = &NetworkResults{
|
|
OpenPorts: make([]PortInfo, 0),
|
|
NetworkConnections: make([]NetworkConnection, 0),
|
|
NetworkIssues: make([]NetworkIssue, 0),
|
|
TLSAnalysis: TLSAnalysis{
|
|
TLSVersions: make([]string, 0),
|
|
CipherSuites: make([]string, 0),
|
|
Vulnerabilities: make([]string, 0),
|
|
Recommendations: make([]string, 0),
|
|
},
|
|
FirewallStatus: FirewallStatus{
|
|
Rules: make([]string, 0),
|
|
Blocked: make([]string, 0),
|
|
Allowed: make([]string, 0),
|
|
Recommendations: make([]string, 0),
|
|
},
|
|
}
|
|
|
|
// Simplified network analysis (in reality would use actual network scanning tools)
|
|
sa.analyzeNetworkConfiguration()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeNetworkConfiguration() {
|
|
// Check for common network security configurations in code
|
|
configFiles := []string{"main.go", "server.go", "config.yaml", "docker-compose.yml"}
|
|
|
|
for _, file := range configFiles {
|
|
filePath := filepath.Join(sa.rootDir, file)
|
|
if content, err := os.ReadFile(filePath); err == nil {
|
|
sa.analyzeNetworkConfigFile(string(content), file)
|
|
}
|
|
}
|
|
|
|
// Add default recommendations
|
|
sa.results.NetworkResults.TLSAnalysis.Recommendations = append(
|
|
sa.results.NetworkResults.TLSAnalysis.Recommendations,
|
|
"Use TLS 1.2 or higher for all network communications",
|
|
"Implement proper certificate validation",
|
|
"Use strong cipher suites",
|
|
)
|
|
|
|
sa.results.NetworkResults.FirewallStatus.Recommendations = append(
|
|
sa.results.NetworkResults.FirewallStatus.Recommendations,
|
|
"Implement network segmentation",
|
|
"Use firewall rules to restrict unnecessary network access",
|
|
"Monitor network traffic for suspicious activity",
|
|
)
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeNetworkConfigFile(content, filename string) {
|
|
// Look for network security issues in configuration
|
|
if strings.Contains(content, ":80") && !strings.Contains(content, "redirect") {
|
|
issue := NetworkIssue{
|
|
Type: "insecure_transport",
|
|
Description: "HTTP server without TLS redirection",
|
|
Severity: "MEDIUM",
|
|
Impact: "Data transmitted in plaintext",
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.NetworkResults.NetworkIssues = append(sa.results.NetworkResults.NetworkIssues, issue)
|
|
}
|
|
|
|
if strings.Contains(content, "0.0.0.0") {
|
|
issue := NetworkIssue{
|
|
Type: "broad_network_binding",
|
|
Description: "Service binding to all interfaces",
|
|
Severity: "LOW",
|
|
Impact: "Increased attack surface",
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.NetworkResults.NetworkIssues = append(sa.results.NetworkResults.NetworkIssues, issue)
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) runComplianceScan(ctx context.Context) error {
|
|
if sa.config.Verbose {
|
|
fmt.Println("Starting compliance scan...")
|
|
}
|
|
|
|
sa.results.ComplianceResults = &ComplianceResults{
|
|
FrameworksChecked: []string{"OWASP", "CIS", "NIST"},
|
|
ComplianceFindings: make([]ComplianceFinding, 0),
|
|
RequirementStatus: make(map[string]string),
|
|
}
|
|
|
|
// Check OWASP compliance
|
|
sa.checkOWASPCompliance()
|
|
|
|
// Calculate compliance score
|
|
total := len(sa.results.ComplianceResults.RequirementStatus)
|
|
passed := 0
|
|
for _, status := range sa.results.ComplianceResults.RequirementStatus {
|
|
if status == "PASS" {
|
|
passed++
|
|
}
|
|
}
|
|
|
|
if total > 0 {
|
|
sa.results.ComplianceResults.ComplianceScore = float64(passed) / float64(total) * 100.0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) checkOWASPCompliance() {
|
|
// OWASP Top 10 checks (simplified)
|
|
owaspChecks := []struct {
|
|
id string
|
|
name string
|
|
description string
|
|
}{
|
|
{"A01", "Broken Access Control", "Access control enforcement"},
|
|
{"A02", "Cryptographic Failures", "Data protection in transit and at rest"},
|
|
{"A03", "Injection", "Input validation and sanitization"},
|
|
{"A04", "Insecure Design", "Secure design patterns"},
|
|
{"A05", "Security Misconfiguration", "Secure configuration"},
|
|
{"A06", "Vulnerable Components", "Component vulnerability management"},
|
|
{"A07", "Authentication Failures", "Authentication implementation"},
|
|
{"A08", "Software Integrity Failures", "Software integrity"},
|
|
{"A09", "Security Logging", "Logging and monitoring"},
|
|
{"A10", "Server-Side Request Forgery", "SSRF protection"},
|
|
}
|
|
|
|
for _, check := range owaspChecks {
|
|
// Simplified compliance check
|
|
status := "PASS" // Would implement actual checks
|
|
if len(sa.results.SecurityFindings) > 5 {
|
|
status = "FAIL"
|
|
}
|
|
|
|
sa.results.ComplianceResults.RequirementStatus[check.id] = status
|
|
|
|
if status == "FAIL" {
|
|
finding := ComplianceFinding{
|
|
Framework: "OWASP",
|
|
Requirement: check.name,
|
|
Status: status,
|
|
Description: check.description,
|
|
Evidence: "Multiple security findings detected",
|
|
Priority: "HIGH",
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.ComplianceResults.ComplianceFindings = append(sa.results.ComplianceResults.ComplianceFindings, finding)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) performRiskAssessment() {
|
|
sa.results.RiskAssessment = RiskAssessment{
|
|
RiskFactors: make([]RiskFactor, 0),
|
|
RecommendedActions: make([]string, 0),
|
|
}
|
|
|
|
// Analyze risk factors
|
|
sa.analyzeRiskFactors()
|
|
sa.performThreatModeling()
|
|
sa.analyzeAttackSurface()
|
|
sa.assessBusinessImpact()
|
|
|
|
// Determine overall risk
|
|
sa.calculateOverallRisk()
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeRiskFactors() {
|
|
riskFactors := []RiskFactor{
|
|
{
|
|
Factor: "Code Vulnerabilities",
|
|
Likelihood: sa.calculateVulnerabilityLikelihood(),
|
|
Impact: 0.8,
|
|
Description: "Risk from identified code vulnerabilities",
|
|
},
|
|
{
|
|
Factor: "Dependency Vulnerabilities",
|
|
Likelihood: sa.calculateDependencyRisk(),
|
|
Impact: 0.6,
|
|
Description: "Risk from vulnerable dependencies",
|
|
},
|
|
{
|
|
Factor: "Secret Exposure",
|
|
Likelihood: sa.calculateSecretRisk(),
|
|
Impact: 0.9,
|
|
Description: "Risk from exposed secrets",
|
|
},
|
|
{
|
|
Factor: "Network Security",
|
|
Likelihood: sa.calculateNetworkRisk(),
|
|
Impact: 0.7,
|
|
Description: "Risk from network configuration issues",
|
|
},
|
|
}
|
|
|
|
for i := range riskFactors {
|
|
riskFactors[i].RiskScore = riskFactors[i].Likelihood * riskFactors[i].Impact
|
|
}
|
|
|
|
sa.results.RiskAssessment.RiskFactors = riskFactors
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateVulnerabilityLikelihood() float64 {
|
|
if sa.results.CodeScanResults == nil {
|
|
return 0.1
|
|
}
|
|
|
|
criticalHigh := 0
|
|
for _, issue := range sa.results.CodeScanResults.CodeIssues {
|
|
if issue.Severity == "CRITICAL" || issue.Severity == "HIGH" {
|
|
criticalHigh++
|
|
}
|
|
}
|
|
|
|
likelihood := float64(criticalHigh) * 0.1
|
|
if likelihood > 1.0 {
|
|
likelihood = 1.0
|
|
}
|
|
|
|
return likelihood
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateDependencyRisk() float64 {
|
|
if sa.results.DependencyResults == nil {
|
|
return 0.1
|
|
}
|
|
|
|
risk := float64(sa.results.DependencyResults.VulnerableDeps) * 0.2
|
|
if risk > 1.0 {
|
|
risk = 1.0
|
|
}
|
|
|
|
return risk
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateSecretRisk() float64 {
|
|
if sa.results.SecretScanResults == nil {
|
|
return 0.1
|
|
}
|
|
|
|
risk := float64(sa.results.SecretScanResults.SecretsFound) * 0.3
|
|
if risk > 1.0 {
|
|
risk = 1.0
|
|
}
|
|
|
|
return risk
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateNetworkRisk() float64 {
|
|
if sa.results.NetworkResults == nil {
|
|
return 0.1
|
|
}
|
|
|
|
risk := float64(len(sa.results.NetworkResults.NetworkIssues)) * 0.15
|
|
if risk > 1.0 {
|
|
risk = 1.0
|
|
}
|
|
|
|
return risk
|
|
}
|
|
|
|
func (sa *SecurityAuditor) performThreatModeling() {
|
|
threats := []Threat{
|
|
{
|
|
Name: "Malicious Actor Exploitation",
|
|
Description: "External attacker exploiting vulnerabilities",
|
|
Likelihood: 0.6,
|
|
Impact: 0.9,
|
|
Risk: 0.54,
|
|
Mitigations: []string{"Regular security updates", "Access controls", "Monitoring"},
|
|
},
|
|
{
|
|
Name: "Insider Threat",
|
|
Description: "Malicious insider with system access",
|
|
Likelihood: 0.2,
|
|
Impact: 0.8,
|
|
Risk: 0.16,
|
|
Mitigations: []string{"Least privilege", "Activity monitoring", "Background checks"},
|
|
},
|
|
{
|
|
Name: "Supply Chain Attack",
|
|
Description: "Compromise through third-party dependencies",
|
|
Likelihood: 0.3,
|
|
Impact: 0.7,
|
|
Risk: 0.21,
|
|
Mitigations: []string{"Dependency scanning", "Vendor assessment", "Code signing"},
|
|
},
|
|
}
|
|
|
|
attackVectors := []AttackVector{
|
|
{
|
|
Vector: "Network",
|
|
Complexity: "Low",
|
|
Privileges: "None",
|
|
UserAction: "None",
|
|
Scope: "Changed",
|
|
Mitigations: []string{"Network segmentation", "Firewall rules", "TLS"},
|
|
},
|
|
{
|
|
Vector: "Application",
|
|
Complexity: "Low",
|
|
Privileges: "Low",
|
|
UserAction: "Required",
|
|
Scope: "Unchanged",
|
|
Mitigations: []string{"Input validation", "Authentication", "Authorization"},
|
|
},
|
|
}
|
|
|
|
threatActors := []ThreatActor{
|
|
{
|
|
Type: "External Attacker",
|
|
Motivation: "Financial",
|
|
Capability: "High",
|
|
Opportunity: "Medium",
|
|
Likelihood: 0.6,
|
|
Techniques: []string{"Social engineering", "Vulnerability exploitation", "Credential theft"},
|
|
},
|
|
}
|
|
|
|
sa.results.RiskAssessment.ThreatModel = ThreatModel{
|
|
ThreatsIdentified: threats,
|
|
AttackVectors: attackVectors,
|
|
AssetValuation: map[string]float64{
|
|
"Trading Algorithm": 100.0,
|
|
"Private Keys": 90.0,
|
|
"Market Data": 70.0,
|
|
"Configuration": 50.0,
|
|
},
|
|
ThreatActors: threatActors,
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) analyzeAttackSurface() {
|
|
networkExposure := 30.0 // Simplified
|
|
if sa.results.NetworkResults != nil {
|
|
networkExposure += float64(len(sa.results.NetworkResults.NetworkIssues)) * 10.0
|
|
}
|
|
|
|
dataExposure := 20.0 // Simplified
|
|
if sa.results.SecretScanResults != nil {
|
|
dataExposure += float64(sa.results.SecretScanResults.SecretsFound) * 15.0
|
|
}
|
|
|
|
sa.results.RiskAssessment.AttackSurface = AttackSurface{
|
|
NetworkExposure: networkExposure,
|
|
ApplicationEndpoints: 5, // Simplified
|
|
DataExposure: dataExposure,
|
|
TrustedInterfaces: 3, // Simplified
|
|
ExternalDependencies: sa.results.DependencyResults.TotalDependencies,
|
|
ReductionOpportunities: []string{
|
|
"Minimize exposed network services",
|
|
"Implement stronger access controls",
|
|
"Reduce external dependencies",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) assessBusinessImpact() {
|
|
sa.results.RiskAssessment.BusinessImpact = BusinessImpact{
|
|
FinancialImpact: 80.0, // High for MEV bot
|
|
ReputationalImpact: 70.0,
|
|
OperationalImpact: 85.0,
|
|
ComplianceImpact: 60.0,
|
|
OverallImpact: 75.0,
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateOverallRisk() {
|
|
totalRisk := 0.0
|
|
for _, factor := range sa.results.RiskAssessment.RiskFactors {
|
|
totalRisk += factor.RiskScore
|
|
}
|
|
|
|
avgRisk := totalRisk / float64(len(sa.results.RiskAssessment.RiskFactors))
|
|
|
|
switch {
|
|
case avgRisk < 0.3:
|
|
sa.results.RiskAssessment.OverallRisk = "LOW"
|
|
sa.results.OverallRiskLevel = "LOW"
|
|
sa.results.OverallRiskScore = avgRisk * 100.0
|
|
case avgRisk < 0.6:
|
|
sa.results.RiskAssessment.OverallRisk = "MEDIUM"
|
|
sa.results.OverallRiskLevel = "MEDIUM"
|
|
sa.results.OverallRiskScore = avgRisk * 100.0
|
|
case avgRisk < 0.8:
|
|
sa.results.RiskAssessment.OverallRisk = "HIGH"
|
|
sa.results.OverallRiskLevel = "HIGH"
|
|
sa.results.OverallRiskScore = avgRisk * 100.0
|
|
default:
|
|
sa.results.RiskAssessment.OverallRisk = "CRITICAL"
|
|
sa.results.OverallRiskLevel = "CRITICAL"
|
|
sa.results.OverallRiskScore = avgRisk * 100.0
|
|
}
|
|
|
|
// Generate recommended actions
|
|
if avgRisk > 0.6 {
|
|
sa.results.RiskAssessment.RecommendedActions = append(
|
|
sa.results.RiskAssessment.RecommendedActions,
|
|
"Immediate security review required",
|
|
"Address critical and high severity vulnerabilities",
|
|
"Implement comprehensive monitoring",
|
|
)
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) generateRemediationPlan() {
|
|
// Generate remediation actions based on findings
|
|
if sa.results.CodeScanResults != nil {
|
|
for _, issue := range sa.results.CodeScanResults.CodeIssues {
|
|
if issue.Severity == "CRITICAL" || issue.Severity == "HIGH" {
|
|
action := RemediationAction{
|
|
ID: fmt.Sprintf("REM-%s", issue.ID),
|
|
Title: fmt.Sprintf("Fix %s in %s", issue.Type, filepath.Base(issue.File)),
|
|
Priority: issue.Severity,
|
|
Effort: "Medium",
|
|
Description: fmt.Sprintf("Address %s at line %d", issue.Description, issue.Line),
|
|
Steps: []string{
|
|
"Review the identified code",
|
|
"Implement secure alternative",
|
|
"Test the fix",
|
|
"Update documentation",
|
|
},
|
|
Timeline: "1-2 days",
|
|
Owner: "Development Team",
|
|
Dependencies: make([]string, 0),
|
|
Timestamp: time.Now(),
|
|
}
|
|
sa.results.RemediationPlan = append(sa.results.RemediationPlan, action)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort remediation plan by priority
|
|
sort.Slice(sa.results.RemediationPlan, func(i, j int) bool {
|
|
priorityOrder := map[string]int{"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
|
return priorityOrder[sa.results.RemediationPlan[i].Priority] < priorityOrder[sa.results.RemediationPlan[j].Priority]
|
|
})
|
|
}
|
|
|
|
func (sa *SecurityAuditor) calculateSecurityMetrics() {
|
|
totalIssues := len(sa.results.SecurityFindings)
|
|
criticalHigh := 0
|
|
for _, finding := range sa.results.SecurityFindings {
|
|
if finding.Severity == "CRITICAL" || finding.Severity == "HIGH" {
|
|
criticalHigh++
|
|
}
|
|
}
|
|
|
|
// Calculate metrics
|
|
vulnerabilityDensity := 0.0
|
|
if sa.results.Environment.TotalLOC > 0 {
|
|
vulnerabilityDensity = float64(totalIssues) / float64(sa.results.Environment.TotalLOC) * 1000.0
|
|
}
|
|
|
|
securityScore := 100.0 - float64(criticalHigh)*10.0 - float64(totalIssues)*2.0
|
|
if securityScore < 0 {
|
|
securityScore = 0
|
|
}
|
|
|
|
complianceRate := 100.0
|
|
if sa.results.ComplianceResults != nil {
|
|
complianceRate = sa.results.ComplianceResults.ComplianceScore
|
|
}
|
|
|
|
sa.results.Metrics = SecurityMetrics{
|
|
VulnerabilityDensity: vulnerabilityDensity,
|
|
SecurityScore: securityScore,
|
|
ComplianceRate: complianceRate,
|
|
RiskReduction: 30.0, // Simplified
|
|
MeanTimeToDetection: 120.0, // 2 minutes (automated)
|
|
MeanTimeToResolution: 4320.0, // 3 days (estimated)
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) addSecurityFinding(finding SecurityFinding) {
|
|
sa.results.SecurityFindings = append(sa.results.SecurityFindings, finding)
|
|
}
|
|
|
|
func (sa *SecurityAuditor) GenerateReport() error {
|
|
// Sort security findings by severity
|
|
sort.Slice(sa.results.SecurityFindings, func(i, j int) bool {
|
|
severityOrder := map[string]int{"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
|
return severityOrder[sa.results.SecurityFindings[i].Severity] < severityOrder[sa.results.SecurityFindings[j].Severity]
|
|
})
|
|
|
|
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
|
|
|
switch sa.config.ReportFormat {
|
|
case "json":
|
|
return sa.generateJSONReport(timestamp)
|
|
case "sarif":
|
|
return sa.generateSARIFReport(timestamp)
|
|
case "txt":
|
|
return sa.generateTextReport(timestamp)
|
|
default:
|
|
// Generate all formats
|
|
if err := sa.generateJSONReport(timestamp); err != nil {
|
|
return err
|
|
}
|
|
if err := sa.generateTextReport(timestamp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (sa *SecurityAuditor) generateJSONReport(timestamp string) error {
|
|
jsonReport, err := json.MarshalIndent(sa.results, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal results: %w", err)
|
|
}
|
|
|
|
jsonPath := filepath.Join(sa.config.OutputDir, fmt.Sprintf("security_audit_%s.json", timestamp))
|
|
if err := os.WriteFile(jsonPath, jsonReport, 0644); err != nil {
|
|
return fmt.Errorf("failed to write JSON report: %w", err)
|
|
}
|
|
|
|
if sa.config.Verbose {
|
|
fmt.Printf("JSON report generated: %s\n", jsonPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) generateSARIFReport(timestamp string) error {
|
|
// SARIF format implementation would go here
|
|
// This is a simplified placeholder
|
|
sarifPath := filepath.Join(sa.config.OutputDir, fmt.Sprintf("security_audit_%s.sarif", timestamp))
|
|
|
|
if sa.config.Verbose {
|
|
fmt.Printf("SARIF report placeholder: %s\n", sarifPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sa *SecurityAuditor) generateTextReport(timestamp string) error {
|
|
summary := fmt.Sprintf(`Security Audit Report
|
|
Generated: %s
|
|
Scan Type: %s
|
|
Risk Threshold: %s
|
|
Overall Risk Score: %.1f%% (%s)
|
|
|
|
ENVIRONMENT
|
|
===========
|
|
Project Type: %s
|
|
Languages: %s
|
|
Total Files: %d
|
|
Total LOC: %d
|
|
|
|
SUMMARY
|
|
=======
|
|
Security Findings: %d
|
|
Risk Level: %s
|
|
Security Score: %.1f%%
|
|
|
|
`, sa.results.Timestamp.Format("2006-01-02 15:04:05"),
|
|
sa.results.ScanType,
|
|
sa.results.RiskThreshold,
|
|
sa.results.OverallRiskScore,
|
|
sa.results.OverallRiskLevel,
|
|
sa.results.Environment.ProjectType,
|
|
strings.Join(sa.results.Environment.Languages, ", "),
|
|
sa.results.Environment.TotalFiles,
|
|
sa.results.Environment.TotalLOC,
|
|
len(sa.results.SecurityFindings),
|
|
sa.results.OverallRiskLevel,
|
|
sa.results.Metrics.SecurityScore)
|
|
|
|
// Add detailed results for each scan type
|
|
if sa.results.CodeScanResults != nil {
|
|
summary += fmt.Sprintf(`CODE SECURITY
|
|
=============
|
|
Files Scanned: %d
|
|
Vulnerabilities Found: %d
|
|
Security Hotspots: %d
|
|
|
|
`, sa.results.CodeScanResults.FilesScanned,
|
|
sa.results.CodeScanResults.VulnerabilitiesFound,
|
|
len(sa.results.CodeScanResults.HotspotAnalysis))
|
|
}
|
|
|
|
if sa.results.DependencyResults != nil {
|
|
summary += fmt.Sprintf(`DEPENDENCY SECURITY
|
|
==================
|
|
Total Dependencies: %d
|
|
Vulnerable Dependencies: %d
|
|
Supply Chain Risk Score: %.1f%%
|
|
|
|
`, sa.results.DependencyResults.TotalDependencies,
|
|
sa.results.DependencyResults.VulnerableDeps,
|
|
sa.results.DependencyResults.SupplyChainRisk.OverallRiskScore)
|
|
}
|
|
|
|
if sa.results.SecretScanResults != nil {
|
|
summary += fmt.Sprintf(`SECRET MANAGEMENT
|
|
================
|
|
Files Scanned: %d
|
|
Secrets Found: %d
|
|
|
|
`, sa.results.SecretScanResults.FilesScanned,
|
|
sa.results.SecretScanResults.SecretsFound)
|
|
}
|
|
|
|
// Top security findings
|
|
if len(sa.results.SecurityFindings) > 0 {
|
|
summary += "\nTOP SECURITY FINDINGS\n====================\n"
|
|
for i, finding := range sa.results.SecurityFindings {
|
|
if i >= 10 { // Show top 10
|
|
break
|
|
}
|
|
summary += fmt.Sprintf("%d. [%s] %s\n Location: %s\n Description: %s\n\n",
|
|
i+1, finding.Severity, finding.Title, finding.Location, finding.Description)
|
|
}
|
|
}
|
|
|
|
// Risk assessment
|
|
summary += fmt.Sprintf(`RISK ASSESSMENT
|
|
===============
|
|
Overall Risk: %s
|
|
Risk Factors:
|
|
`, sa.results.RiskAssessment.OverallRisk)
|
|
|
|
for _, factor := range sa.results.RiskAssessment.RiskFactors {
|
|
summary += fmt.Sprintf("- %s: %.1f%% risk\n", factor.Factor, factor.RiskScore*100)
|
|
}
|
|
|
|
// Recommendations
|
|
if len(sa.results.RiskAssessment.RecommendedActions) > 0 {
|
|
summary += "\nRECOMMENDATIONS\n===============\n"
|
|
for i, action := range sa.results.RiskAssessment.RecommendedActions {
|
|
summary += fmt.Sprintf("%d. %s\n", i+1, action)
|
|
}
|
|
}
|
|
|
|
summaryPath := filepath.Join(sa.config.OutputDir, fmt.Sprintf("security_summary_%s.txt", timestamp))
|
|
if err := os.WriteFile(summaryPath, []byte(summary), 0644); err != nil {
|
|
return fmt.Errorf("failed to write summary report: %w", err)
|
|
}
|
|
|
|
if sa.config.Verbose {
|
|
fmt.Printf("Summary report generated: %s\n", summaryPath)
|
|
}
|
|
|
|
return nil
|
|
}
|