🗄️ Store CVSS vectors in a database and sort by score
Problem
You want to persist CVSS vectors in a SQL database alongside CVE IDs, read them back as typed Cvss3x objects, and return rows ordered by severity — without storing a denormalized score column you have to keep in sync.
Solution
Here's the flow:
1. Schema
Store the vector string in a VARCHAR/TEXT column. A generated/trigger-maintained score column is optional — see the discussion for why you usually don't need one.
CREATE TABLE vulns (
id VARCHAR(32) PRIMARY KEY,
cvss TEXT NOT NULL
);2. Write: Value (driver.Valuer)
Cvss3x.Value() returns the canonical vector string, so you can pass a *Cvss3x straight to db.Exec. (*Cvss3x)(nil).Value() writes SQL NULL.
import (
"database/sql"
"github.com/scagogogo/cvss-skills/pkg/cvss"
)
func insert(db *sql.DB, id string, v *cvss.Cvss3x) error {
_, err := db.Exec("INSERT INTO vulns (id, cvss) VALUES (?, ?)", id, v)
return err
}
// Usage:
// insert(db, "CVE-2024-0001", cvss.CriticalV31())
// insert(db, "CVE-2024-0002", cvss.LowV31())3. Read: Scan (sql.Scanner)
Cvss3x.Scan accepts string, []byte, or nil (NULL → empty Cvss3x). Scan into a cvss.Cvss3x (not a pointer) so the row value is always materialized:
func listAll(db *sql.DB) ([]*cvss.Cvss3x, error) {
rows, err := db.Query("SELECT cvss FROM vulns")
if err != nil {
return nil, err
}
defer rows.Close()
var out []*cvss.Cvss3x
for rows.Next() {
var cv cvss.Cvss3x
if err := rows.Scan(&cv); err != nil {
return nil, err
}
out = append(out, &cv)
}
return out, rows.Err()
}4. Sort by score with Cvss3xSlice
Once you have the slice, sort in memory — NewCvss3xSlice scores each vector once and caches the result:
import (
"fmt"
"github.com/scagogogo/cvss-skills/pkg/cvss"
)
func printBySeverity(db *sql.DB) error {
vectors, err := listAll(db)
if err != nil {
return err
}
// Descending — Critical first (the default).
slice := cvss.NewCvss3xSlice(vectors...).Sort()
for i, cv := range slice.Items() {
fmt.Printf("#%d %.1f %s\n", i+1, slice.ScoreAt(i), cv.String())
}
return nil
}For a given input set, the output looks like:
#1 10.0 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
#2 9.8 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
#3 3.7 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:NDiscussion
Don't store a score column
A score column denormalizes the vector — every metric edit forces a rescore, and a stale score is a silent bug. Store the vector string (single source of truth) and compute scores at read time with NewCvss3xSlice. For large tables, paginate and score a page at a time.
- NULL ↔ empty
Cvss3x.Scan(nil)producesCvss3x{Cvss3xBase: &Cvss3xBase{}}(not a nil pointer), and a nil*Cvss3xwrites SQLNULL. Use*Cvss3x(not**Cvss3x) as the scan target. - Invalid strings fail the scan.
Scanwraps a parse error as"failed to scan Cvss3x: %w", so a corrupt row surfaces duringrows.Scan. If your column may contain legacy junk, scan intosql.NullStringfirst and parse defensively. ORDER BYin SQL won't sort by score. The score isn't a column, and you can't compute CVSS in portable SQL. Either sort in Go withCvss3xSlice(recommended), or maintain ascore REALcolumn kept in sync by application code (only if you need server-side pagination by score).- Invalid vectors sort to −1.
NewCvss3xSlicerecords-1for items that failCalculate; in descending order they sort to the top, so guard withif slice.ScoreAt(i) < 0 { continue }. - Postgres
?placeholder. The examples use?(MySQL/SQLite). For PostgreSQL, use$1,$2; for SQL Server,@p1. TheScan/Valueinterfaces are driver-agnostic.
See Also
- SQL & Sorting —
Scan/Value/Cvss3xSlice/Canonicalizereference - Sort by severity — the same sort, CLI + SDK, without a database
- Export to JSON — turn stored vectors into JSON for an API
- Generate test data — seed the
vulnstable with random vectors