mirror of
https://github.com/lukaszraczylo/gohoarder.git
synced 2026-10-12 00:45:16 +00:00
The automated update changed only go.mod, so the build failed on the old v2 imports. This commit moves the code to the v3 API. The commit replaces BodyParser with Bind().Body, Static with the static middleware, and ListenTLS with ListenConfig. It also updates the tests for TestConfig. Fixes #179
165 lines
5.1 KiB
Go
165 lines
5.1 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"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.
|
|
// Normalize Go cache-key names (modulePath/@v/version.zip) to the
|
|
// natural module path so admin bypass targets like
|
|
// "go/github.com/foo/[email protected]" match the package.
|
|
bypassName := name
|
|
if registry == "go" {
|
|
if idx := strings.Index(bypassName, "/@v/"); idx >= 0 {
|
|
bypassName = bypassName[:idx]
|
|
}
|
|
}
|
|
packageKey := registry + "/" + bypassName + "@" + version
|
|
packageKeyNoVersion := registry + "/" + bypassName
|
|
bypassedCVEs := make(map[string]*metadata.CVEBypass)
|
|
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[metadata.NormalizeSeverity(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)
|
|
}
|