package security import ( "fmt" "github.com/user/package-review/internal/inventory" ) // Finding represents a security finding (CVE) type Finding struct { PackageName string `json:"package_name"` PackageVersion string `json:"package_version"` CVE string `json:"cve_id"` CVSS float64 `json:"cvss_score"` Summary string `json:"summary"` Link string `json:"link"` Severity string `json:"severity"` // LOW, MEDIUM, HIGH, CRITICAL } // Mitigation represents Claude's recommended mitigation type Mitigation struct { Finding Finding `json:"finding"` Recommendation string `json:"recommendation"` // update, replace, protect Rationale string `json:"rationale"` NextSteps string `json:"next_steps"` } // Audit represents the full security audit type Audit struct { Inventory *inventory.Inventory Findings []Finding `json:"findings"` Mitigations []Mitigation `json:"mitigations"` } // NewAudit creates a new audit for an inventory func NewAudit(inv *inventory.Inventory) *Audit { return &Audit{ Inventory: inv, Findings: []Finding{}, Mitigations: []Mitigation{}, } } // ScanOSV scans all packages against OSV.dev func (a *Audit) ScanOSV() error { fmt.Println(" Querying OSV.dev...") for _, pkg := range a.Inventory.AllPackages() { findings, err := queryOSV(pkg) if err != nil { // Log but continue fmt.Printf(" ⚠️ Error scanning %s: %v\n", pkg.Name, err) continue } a.Findings = append(a.Findings, findings...) } return nil } // FilterByMinSeverity returns findings above a CVSS threshold func (a *Audit) FilterByMinSeverity(minCVSS float64) []Finding { var filtered []Finding for _, f := range a.Findings { if f.CVSS >= minCVSS { filtered = append(filtered, f) } } return filtered } // CountVulnerable returns count of packages with CVEs func (a *Audit) CountVulnerable() int { seen := make(map[string]bool) for _, f := range a.Findings { seen[f.PackageName] = true } return len(seen) } // AnalyzeWithClaude sends high-severity findings to Claude for analysis func (a *Audit) AnalyzeWithClaude(mode string) error { highSev := a.FilterByMinSeverity(7.0) if len(highSev) == 0 { return nil } fmt.Printf(" Sending %d findings to Claude...\n", len(highSev)) if mode == "sdk" { return analyzeWithSDK(a, highSev) } // Default: CLI mode via `claude` command return analyzeWithCLI(a, highSev) }