feat(monitor/collectors/cve): cve_events repository with EPSS-only patch

This commit is contained in:
CarterPerez-dev 2026-05-01 22:38:08 -04:00
parent ee358e38f1
commit 2a61953cb9
2 changed files with 219 additions and 0 deletions

View File

@ -0,0 +1,84 @@
// ©AngelaMos | 2026
// repo.go
package cve
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/jmoiron/sqlx"
)
type Row struct {
CveID string `db:"cve_id"`
Published time.Time `db:"published"`
LastModified time.Time `db:"last_modified"`
Severity string `db:"severity"`
CVSS float64 `db:"cvss_score"`
EPSSScore *float64 `db:"epss_score"`
EPSSPercentile *float64 `db:"epss_percentile"`
InKEV bool `db:"in_kev"`
Payload json.RawMessage `db:"payload"`
}
type Repo struct {
db *sqlx.DB
}
func NewRepo(db *sqlx.DB) *Repo { return &Repo{db: db} }
func (r *Repo) Upsert(ctx context.Context, row Row) error {
_, err := r.db.ExecContext(ctx, `
INSERT INTO cve_events (cve_id, published, last_modified, severity, cvss_score, payload)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (cve_id) DO UPDATE SET
last_modified = EXCLUDED.last_modified,
severity = EXCLUDED.severity,
cvss_score = EXCLUDED.cvss_score,
payload = EXCLUDED.payload`,
row.CveID, row.Published, row.LastModified, row.Severity, row.CVSS, []byte(row.Payload),
)
if err != nil {
return fmt.Errorf("upsert cve %s: %w", row.CveID, err)
}
return nil
}
func (r *Repo) UpdateEPSS(ctx context.Context, cveID string, score, percentile float64) error {
_, err := r.db.ExecContext(ctx,
`UPDATE cve_events SET epss_score = $2, epss_percentile = $3 WHERE cve_id = $1`,
cveID, score, percentile,
)
if err != nil {
return fmt.Errorf("update epss %s: %w", cveID, err)
}
return nil
}
func (r *Repo) Get(ctx context.Context, cveID string) (Row, error) {
var row Row
err := r.db.GetContext(ctx, &row, `
SELECT cve_id, published, last_modified, severity, cvss_score,
epss_score, epss_percentile, in_kev, payload
FROM cve_events WHERE cve_id = $1`, cveID)
if err != nil {
return Row{}, fmt.Errorf("get cve %s: %w", cveID, err)
}
return row, nil
}
func (r *Repo) RecentByLastModified(ctx context.Context, limit int) ([]Row, error) {
var rows []Row
err := r.db.SelectContext(ctx, &rows, `
SELECT cve_id, published, last_modified, severity, cvss_score,
epss_score, epss_percentile, in_kev, payload
FROM cve_events
ORDER BY last_modified DESC LIMIT $1`, limit)
if err != nil {
return nil, fmt.Errorf("recent cves: %w", err)
}
return rows, nil
}

View File

@ -0,0 +1,135 @@
// ©AngelaMos | 2026
// repo_test.go
package cve_test
import (
"context"
"encoding/json"
"testing"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/collectors/cve"
)
func setupDB(t *testing.T) *sqlx.DB {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
pg, err := postgres.Run(ctx, "postgres:17-alpine",
postgres.WithDatabase("monitor"),
postgres.WithUsername("monitor"),
postgres.WithPassword("monitor"),
postgres.BasicWaitStrategies(),
)
require.NoError(t, err)
t.Cleanup(func() { _ = pg.Terminate(context.Background()) })
dsn, err := pg.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sqlx.ConnectContext(ctx, "pgx", dsn)
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
_, err = db.ExecContext(ctx, `
CREATE TABLE cve_events (
cve_id text PRIMARY KEY,
published timestamptz,
last_modified timestamptz NOT NULL,
severity text NOT NULL DEFAULT '',
cvss_score numeric(3,1) NOT NULL DEFAULT 0,
epss_score numeric(5,4),
epss_percentile numeric(5,4),
in_kev boolean NOT NULL DEFAULT false,
payload jsonb NOT NULL
)`)
require.NoError(t, err)
return db
}
func TestRepo_UpsertThenEPSSUpdate(t *testing.T) {
db := setupDB(t)
repo := cve.NewRepo(db)
ctx := context.Background()
row := cve.Row{
CveID: "CVE-2026-0001",
Published: time.Now().UTC().Add(-time.Hour),
LastModified: time.Now().UTC(),
Severity: "CRITICAL",
CVSS: 9.8,
Payload: json.RawMessage(`{"id":"CVE-2026-0001"}`),
}
require.NoError(t, repo.Upsert(ctx, row))
require.NoError(t, repo.UpdateEPSS(ctx, "CVE-2026-0001", 0.9512, 0.9988))
got, err := repo.Get(ctx, "CVE-2026-0001")
require.NoError(t, err)
require.NotNil(t, got.EPSSScore)
require.InDelta(t, 0.9512, *got.EPSSScore, 0.0001)
require.NotNil(t, got.EPSSPercentile)
require.InDelta(t, 0.9988, *got.EPSSPercentile, 0.0001)
require.Equal(t, "CRITICAL", got.Severity)
require.InDelta(t, 9.8, got.CVSS, 0.0001)
}
func TestRepo_UpsertOnConflictUpdatesLastModified(t *testing.T) {
db := setupDB(t)
repo := cve.NewRepo(db)
ctx := context.Background()
first := cve.Row{
CveID: "CVE-2026-0002",
Published: time.Now().UTC().Add(-2 * time.Hour),
LastModified: time.Now().UTC().Add(-time.Hour),
Severity: "MEDIUM",
CVSS: 5.0,
Payload: json.RawMessage(`{"v":1}`),
}
require.NoError(t, repo.Upsert(ctx, first))
updated := first
updated.LastModified = time.Now().UTC()
updated.Severity = "HIGH"
updated.CVSS = 7.5
updated.Payload = json.RawMessage(`{"v":2}`)
require.NoError(t, repo.Upsert(ctx, updated))
got, err := repo.Get(ctx, "CVE-2026-0002")
require.NoError(t, err)
require.Equal(t, "HIGH", got.Severity)
require.InDelta(t, 7.5, got.CVSS, 0.0001)
}
func TestRepo_RecentByLastModifiedOrdered(t *testing.T) {
db := setupDB(t)
repo := cve.NewRepo(db)
ctx := context.Background()
now := time.Now().UTC()
for i, name := range []string{"CVE-A", "CVE-B", "CVE-C"} {
require.NoError(t, repo.Upsert(ctx, cve.Row{
CveID: name,
Published: now.Add(-time.Duration(10-i) * time.Hour),
LastModified: now.Add(-time.Duration(10-i) * time.Hour),
Severity: "LOW",
Payload: json.RawMessage(`{}`),
}))
}
rows, err := repo.RecentByLastModified(ctx, 3)
require.NoError(t, err)
require.Len(t, rows, 3)
require.Equal(t, "CVE-C", rows[0].CveID)
require.Equal(t, "CVE-B", rows[1].CveID)
require.Equal(t, "CVE-A", rows[2].CveID)
}