Cybersecurity-Projects/PROJECTS/intermediate/security-news-scraper/internal/enrich/enrich.go

106 lines
2.2 KiB
Go

// ©AngelaMos | 2026
// enrich.go
package enrich
import (
"context"
"time"
"github.com/CarterPerez-dev/nadezhda/internal/cve"
"github.com/CarterPerez-dev/nadezhda/internal/store"
)
const secondsPerHour = 3600
type Clients struct {
Core cve.CVESource
KEV *cve.KEVClient
EPSS *cve.EPSSClient
}
type Stats struct {
Total int
Enriched int
NotFound int
KEVHits int
Errors int
}
func Run(ctx context.Context, st *store.Store, clients Clients, now time.Time, positiveTTLHours, negativeTTLHours int) (Stats, error) {
positiveTTL := int64(positiveTTLHours) * secondsPerHour
negativeTTL := int64(negativeTTLHours) * secondsPerHour
ids, err := st.CVEsNeedingEnrichment(now.Unix(), positiveTTL, negativeTTL)
if err != nil {
return Stats{}, err
}
if len(ids) == 0 {
return Stats{}, nil
}
catalog, err := clients.KEV.Fetch(ctx)
if err != nil {
return Stats{}, err
}
epssScores, _ := clients.EPSS.Fetch(ctx, ids)
if epssScores == nil {
epssScores = map[string]cve.EPSSScore{}
}
stats := Stats{Total: len(ids)}
for _, id := range ids {
coreRes, err := clients.Core.Fetch(ctx, id)
if err != nil {
if ctx.Err() != nil {
return stats, ctx.Err()
}
stats.Errors++
continue
}
rec := store.CVE{ID: id, EnrichedAt: now.Unix()}
if coreRes.Found {
rec.EnrichStatus = store.EnrichStatusOK
rec.Description = coreRes.Description
rec.CVSSScore = coreRes.CVSSScore
rec.CVSSVersion = coreRes.CVSSVersion
rec.CVSSSeverity = coreRes.CVSSSeverity
rec.CVSSVector = coreRes.CVSSVector
rec.CWE = coreRes.CWE
rec.NVDPublished = coreRes.Published
rec.NVDModified = coreRes.Modified
} else {
rec.EnrichStatus = store.EnrichStatusNotFound
}
if entry, ok := catalog.Lookup(id); ok {
rec.IsKEV = true
rec.KEVDateAdded = entry.DateAdded
rec.KEVRansomware = entry.Ransomware
}
if score, ok := epssScores[id]; ok {
epssVal := score.EPSS
percentileVal := score.Percentile
rec.EPSS = &epssVal
rec.EPSSPercentile = &percentileVal
}
if err := st.UpdateCVEEnrichment(rec); err != nil {
stats.Errors++
continue
}
if coreRes.Found {
stats.Enriched++
} else {
stats.NotFound++
}
if rec.IsKEV {
stats.KEVHits++
}
}
return stats, nil
}