package app import ( "fmt" "os" "sort" "strings" ) // BrewVulnsReport is the JSON structure emitted on stdout. type BrewVulnsReport struct { Exploited []string `json:"exploited"` NotExploited []string `json:"not_exploited"` NonCVE []string `json:"non_cve"` VulnerablePackages []string `json:"vulnerable_packages"` } // BuildBrewVulnsReport checks each brew-reported CVE against fprox and // classifies it as exploited or not, alongside the vulnerable packages. func BuildBrewVulnsReport() (BrewVulnsReport, error) { r, err := runBrewVulns() if err != nil { return BrewVulnsReport{}, err } // Maintain a CVE -> homebrew package mapping while accumulating the // vulnerable packages and non-CVE identifiers so the report references all. cvePkg := make(map[string]string) pkgSeen := make(map[string]struct{}) nonCveSeen := make(map[string]struct{}) for _, finding := range r.Findings { for _, v := range finding.Vulnerabilities { if v.ID == "" { continue } if strings.HasPrefix(v.ID, "CVE-") { if _, ok := cvePkg[v.ID]; !ok { cvePkg[v.ID] = finding.Formula } pkgSeen[finding.Formula] = struct{}{} } else { nonCveSeen[v.ID] = struct{}{} } } } var exploited, notExploited []string for cve := range cvePkg { has, err := CveHasExploits(cve) if err != nil { // Skip CVEs we could not resolve so they are never mislabeled. fmt.Fprintf(os.Stderr, "warning: skipping %s: %v\n", cve, err) continue } if has { exploited = append(exploited, cve) } else { notExploited = append(notExploited, cve) } } sort.Strings(exploited) sort.Strings(notExploited) pkgs := make([]string, 0, len(pkgSeen)) for pkg := range pkgSeen { pkgs = append(pkgs, pkg) } sort.Strings(pkgs) nonCves := make([]string, 0, len(nonCveSeen)) for id := range nonCveSeen { nonCves = append(nonCves, id) } sort.Strings(nonCves) return BrewVulnsReport{ Exploited: exploited, NotExploited: notExploited, NonCVE: nonCves, VulnerablePackages: pkgs, }, nil }