📏 Measure similarity between two CVSS vectors
Problem
You want a number — not a list of differing metrics — that says how far apart two CVSS vectors are, for de-duplication, clustering, or "find similar advisories."
Solution
Here's the flow — one calculator, five metrics, choose by use case:
CLI: distance
bash
cvss distance \
"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" \
"CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L"text
Euclidean: 0.9670
Manhattan: 2.4600
Hamming: 7
Jaccard: 0.1250
Score diff: 6.0Add --env to fold the 11 environmental dimensions into the calculation:
bash
cvss distance --env \
"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:T/RC:C/CR:H/IR:H/AR:H" \
"CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L/E:U/RL:O/RC:R/CR:L/IR:L/AR:L"text
Euclidean (with env): 1.9850
Manhattan (with env): 5.5700
Hamming (with env): 13
Jaccard (with env): 0.4091
Score diff: 7.4For machine consumption, --format json:
bash
cvss distance --format json \
"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" \
"CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L"json
{
"euclidean": 0.9669539802906858,
"hamming": 7,
"jaccard": 0.125,
"manhattan": 2.46,
"score_diff": 6.000000000000001,
"vector1": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"vector2": "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L"
}Go SDK: DistanceCalculator
go
package main
import (
"fmt"
"github.com/scagogogo/cvss-skills/pkg/cvss"
"github.com/scagogogo/cvss-skills/pkg/parser"
)
func main() {
a, _ := parser.ParseString("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
b, _ := parser.ParseString("CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L")
dc := cvss.NewDistanceCalculator(a, b)
fmt.Printf("Euclidean : %.4f\n", dc.EuclideanDistance())
fmt.Printf("Manhattan : %.4f\n", dc.ManhattanDistance())
fmt.Printf("Hamming : %d\n", dc.HammingDistance())
fmt.Printf("Jaccard : %.4f\n", dc.JaccardSimilarity())
fmt.Printf("ScoreDiff : %.1f\n", dc.ScoreDifference())
// Environmental variants
fmt.Printf("Euclidean (env): %.4f\n", dc.EuclideanDistanceWithEnv())
// Error-returning variant — surfaces incomplete base metrics
if _, err := dc.EuclideanDistanceChecked(); err != nil {
fmt.Println("checked error:", err)
}
}Choosing a metric
| Metric | What it measures | Range | Use when |
|---|---|---|---|
| Hamming | count of differing metrics | int 0–11 (base) | you want "how many knobs differ," all differences equal |
| Jaccard | same / total metrics | 0–1 (1 = identical) | you want a normalized similarity in [0,1] |
| Euclidean | √(Σ score-diff²) | ≥ 0 | you care about magnitude of score-space differences, outliers weighted more |
| Manhattan | Σ |score-diff| | ≥ 0 | same as Euclidean but robust to one large outlier |
| Score diff | |score(a) − score(b)| | 0–10 | you only care about the final number, single dimension |
Jaccard is a similarity, not a distance
JaccardSimilarity returns 1.0 for identical vectors and 0.0 for fully disjoint ones. Convert to a distance with 1 - jaccard if you need a dissimilarity that's 0 for identical.
Discussion
- Plain variants silently return 0 on incomplete base metrics. If either vector is missing a required base metric,
EuclideanDistance()etc. return0.0— which looks identical to "identical vectors." Use the*Checkedvariants (EuclideanDistanceChecked()) to get an explicit error. - PR and UI use context-adjusted scores. PR's score depends on Scope, and UI:R scores 0.56 in v3.0 vs 0.62 in v3.1 — the distance math accounts for this, so cross-version comparisons are meaningful.
- Scope is 0/1, not a score. Scope has no numeric score, so the distance code treats a scope change as a fixed
1.0contribution. --envonly adds dimensions when both vectors have environmental metrics. If only one side has them, the env variants fall back to the base+temporal behavior.- Not what you want? For a per-metric human-readable breakdown, see Compare two vectors.
See Also
distance— the CLI command- Distance & Comparison —
DistanceCalculatorreference (plain /WithEnv/Checked) - Compare two vectors
- Impact & Sensitivity — single-vector "which metric matters most"