mirror of
https://github.com/lukaszraczylo/gohoarder.git
synced 2026-06-06 22:59:29 +00:00
157 lines
4.7 KiB
Go
157 lines
4.7 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/lukaszraczylo/gohoarder/pkg/metadata"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// handleVulnerabilities handles /api/packages/{registry}/{name}/{version}/vulnerabilities endpoint
|
|
func (a *App) handleVulnerabilities(c *fiber.Ctx) error {
|
|
c.Set("Content-Type", "application/json")
|
|
c.Set("Access-Control-Allow-Origin", "*")
|
|
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
c.Set("Access-Control-Allow-Headers", "Content-Type")
|
|
|
|
if c.Method() == "OPTIONS" {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|
|
|
|
if c.Method() != "GET" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "method not allowed"})
|
|
}
|
|
|
|
ctx := c.Context()
|
|
|
|
// Parse path: /api/packages/{registry}/{name}/{version}/vulnerabilities
|
|
path := strings.TrimPrefix(c.Path(), "/api/packages/")
|
|
path = strings.TrimSuffix(path, "/vulnerabilities")
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) < 3 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "invalid path format, expected /api/packages/{registry}/{name}/{version}/vulnerabilities",
|
|
})
|
|
}
|
|
|
|
registry := parts[0]
|
|
version := parts[len(parts)-1]
|
|
name := strings.Join(parts[1:len(parts)-1], "/")
|
|
|
|
log.Debug().
|
|
Str("registry", registry).
|
|
Str("name", name).
|
|
Str("version", version).
|
|
Msg("Getting vulnerabilities for package")
|
|
|
|
// Get scan result from metadata store
|
|
scanResult, err := a.metadata.GetScanResult(ctx, registry, name, version)
|
|
if err != nil {
|
|
// Check if package exists
|
|
pkg, pkgErr := a.metadata.GetPackage(ctx, registry, name, version)
|
|
if pkgErr != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "package not found"})
|
|
}
|
|
|
|
// Package exists but not scanned yet
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"package": fiber.Map{
|
|
"registry": registry,
|
|
"name": name,
|
|
"version": version,
|
|
},
|
|
"scanned": false,
|
|
"status": "pending",
|
|
"vulnerabilities": []interface{}{},
|
|
"vulnerability_count": 0,
|
|
"message": "Package not yet scanned for vulnerabilities",
|
|
"security_scanned": pkg.SecurityScanned,
|
|
})
|
|
}
|
|
|
|
// Get active bypasses to show which vulnerabilities are bypassed
|
|
bypasses, err := a.metadata.GetActiveCVEBypasses(ctx)
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("Failed to get CVE bypasses")
|
|
bypasses = []*metadata.CVEBypass{}
|
|
}
|
|
|
|
// Build bypass map for fast lookup
|
|
bypassedCVEs := make(map[string]*metadata.CVEBypass)
|
|
packageKey := registry + "/" + name + "@" + version
|
|
packageKeyNoVersion := registry + "/" + name
|
|
|
|
for _, bypass := range bypasses {
|
|
if bypass.Type == metadata.BypassTypeCVE && bypass.Active {
|
|
// Check if bypass applies to this package
|
|
if bypass.AppliesTo == "" || bypass.AppliesTo == packageKey || bypass.AppliesTo == packageKeyNoVersion {
|
|
bypassedCVEs[strings.ToUpper(bypass.Target)] = bypass
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enrich vulnerabilities with bypass information
|
|
enrichedVulns := make([]map[string]interface{}, 0, len(scanResult.Vulnerabilities))
|
|
severityCounts := make(map[string]int)
|
|
|
|
for _, vuln := range scanResult.Vulnerabilities {
|
|
bypassed := false
|
|
var bypassInfo map[string]interface{}
|
|
|
|
// Check if this CVE is bypassed
|
|
if bypass, ok := bypassedCVEs[strings.ToUpper(vuln.ID)]; ok {
|
|
bypassed = true
|
|
bypassInfo = map[string]interface{}{
|
|
"id": bypass.ID,
|
|
"reason": bypass.Reason,
|
|
"created_by": bypass.CreatedBy,
|
|
"expires_at": bypass.ExpiresAt,
|
|
}
|
|
} else {
|
|
// Count non-bypassed vulnerabilities by severity
|
|
severityCounts[strings.ToUpper(vuln.Severity)]++
|
|
}
|
|
|
|
enrichedVuln := map[string]interface{}{
|
|
"id": vuln.ID,
|
|
"severity": vuln.Severity,
|
|
"title": vuln.Title,
|
|
"description": vuln.Description,
|
|
"references": vuln.References,
|
|
"fixed_in": vuln.FixedIn,
|
|
"bypassed": bypassed,
|
|
}
|
|
|
|
if bypassed {
|
|
enrichedVuln["bypass"] = bypassInfo
|
|
}
|
|
|
|
enrichedVulns = append(enrichedVulns, enrichedVuln)
|
|
}
|
|
|
|
// Build response
|
|
response := fiber.Map{
|
|
"package": fiber.Map{
|
|
"registry": registry,
|
|
"name": name,
|
|
"version": version,
|
|
},
|
|
"scanned": true,
|
|
"scanner": scanResult.Scanner,
|
|
"scanned_at": scanResult.ScannedAt,
|
|
"status": scanResult.Status,
|
|
"vulnerabilities": enrichedVulns,
|
|
"vulnerability_count": scanResult.VulnerabilityCount,
|
|
"severity_counts": fiber.Map{
|
|
"critical": severityCounts["CRITICAL"],
|
|
"high": severityCounts["HIGH"],
|
|
"moderate": severityCounts["MODERATE"],
|
|
"low": severityCounts["LOW"],
|
|
},
|
|
"bypassed_count": len(scanResult.Vulnerabilities) - (severityCounts["CRITICAL"] + severityCounts["HIGH"] + severityCounts["MODERATE"] + severityCounts["LOW"]),
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(response)
|
|
}
|