Skip to content

Commit 5ae3648

Browse files
committed
Generalize vulnerability sorting
Signed-off-by: Luiz Carvalho <[email protected]>
1 parent 65c6c52 commit 5ae3648

File tree

2 files changed

+73
-30
lines changed

2 files changed

+73
-30
lines changed

modules/fundamental/src/vulnerability/service/mod.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,20 @@ impl VulnerabilityService {
5959
})
6060
.add_expr(
6161
"id_sort_key",
62-
// Create a normalized sort key that preserves prefixes but sorts numbers numerically
63-
// For CVE IDs: converts "CVE-2024-288" to "CVE-2024-0000000000000000288"
64-
// - Year is always 4 digits, no padding needed
65-
// - Pad sequence to 19 digits (max length per CVE schema)
66-
// CVE schema: https://github.com/CVEProject/cve-schema/blob/main/schema/CVE_Record_Format.json
67-
// For other IDs: returns the ID as-is for alphabetical sorting
68-
// This ensures: ABC-123 < CVE-2023-9000 < CVE-2024-10000 < GHSA-xxx
62+
// Pad numberic segments with zeros to achieve the expected numeric sorting.
63+
// The padding is done into two steps. First add 19 zeros to each number
64+
// segment. Second, keep only the 19 right-most digits for each number segment.
65+
// This behaves like LPAD which cannot be used here because that would be
66+
// evaluated before the REGEXP matching.
67+
// The number 19 is used as that is the largest segment defined, coming from the
68+
// CVE ID spec.
6969
Expr::cust(
70-
"CASE WHEN id ~ '^CVE-[0-9]{4}-[0-9]{4,19}$' THEN \
71-
SUBSTRING(id FROM '^CVE-[0-9]{4}-') || \
72-
LPAD(SUBSTRING(id FROM '-([0-9]+)$'), 19, '0') \
73-
ELSE id END"
70+
"REGEXP_REPLACE( \
71+
REGEXP_REPLACE(id, '\\y([0-9]+)\\y', '0000000000000000000\\1', 'g'), \
72+
'\\y([0-9]+)([0-9]{19})\\y', \
73+
'\\2', \
74+
'g' \
75+
)"
7476
),
7577
sea_orm::ColumnType::Text,
7678
),

modules/fundamental/src/vulnerability/service/test.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -545,12 +545,36 @@ async fn vulnerability_queries(ctx: &TrustifyContext) -> Result<(), anyhow::Erro
545545
async fn vulnerability_numeric_sorting(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
546546
let service = VulnerabilityService::new();
547547

548+
// Test various OSV ID formats to ensure generic numeric sorting works
549+
// CVE format
548550
ctx.graph.ingest_vulnerability("CVE-2024-40000", (), &ctx.db).await?;
549551
ctx.graph.ingest_vulnerability("CVE-2024-10288000", (), &ctx.db).await?;
550-
ctx.graph.ingest_vulnerability("CVE-2023-1234", (), &ctx.db).await?;
551-
ctx.graph.ingest_vulnerability("CVE-2024-9000", (), &ctx.db).await?;
552-
ctx.graph.ingest_vulnerability("CVE-2023-5100", (), &ctx.db).await?;
553-
ctx.graph.ingest_vulnerability("GHSA-xxxx-yyyy-zzzz", (), &ctx.db).await?;
552+
553+
// GHSA format (alphanumeric)
554+
ctx.graph.ingest_vulnerability("GHSA-r9p9-mrjm-926w", (), &ctx.db).await?;
555+
ctx.graph.ingest_vulnerability("GHSA-vp9c-fpxx-744v", (), &ctx.db).await?;
556+
557+
// Go format
558+
ctx.graph.ingest_vulnerability("GO-2024-268", (), &ctx.db).await?;
559+
ctx.graph.ingest_vulnerability("GO-2024-1234", (), &ctx.db).await?;
560+
561+
// RustSec format
562+
ctx.graph.ingest_vulnerability("RUSTSEC-2019-0033", (), &ctx.db).await?;
563+
ctx.graph.ingest_vulnerability("RUSTSEC-2024-0001", (), &ctx.db).await?;
564+
565+
// Alpine format
566+
ctx.graph.ingest_vulnerability("ALPINE-12345", (), &ctx.db).await?;
567+
ctx.graph.ingest_vulnerability("ALPINE-6789", (), &ctx.db).await?;
568+
569+
// PyPI format
570+
ctx.graph.ingest_vulnerability("PYSEC-2021-1234", (), &ctx.db).await?;
571+
ctx.graph.ingest_vulnerability("PYSEC-2024-5678", (), &ctx.db).await?;
572+
573+
// OSV format
574+
ctx.graph.ingest_vulnerability("OSV-2020-111", (), &ctx.db).await?;
575+
ctx.graph.ingest_vulnerability("OSV-2020-58", (), &ctx.db).await?;
576+
577+
// Generic test prefix
554578
ctx.graph.ingest_vulnerability("ABC-xxxx-yyyy", (), &ctx.db).await?;
555579

556580
// Test ascending sort
@@ -562,14 +586,23 @@ async fn vulnerability_numeric_sorting(ctx: &TrustifyContext) -> Result<(), anyh
562586
&ctx.db,
563587
)
564588
.await?;
565-
assert_eq!(7, vulns.items.len());
589+
assert_eq!(15, vulns.items.len());
590+
// Alphabetical by prefix, then numeric within each prefix
566591
assert_eq!(vulns.items[0].head.identifier, "ABC-xxxx-yyyy");
567-
assert_eq!(vulns.items[1].head.identifier, "CVE-2023-1234");
568-
assert_eq!(vulns.items[2].head.identifier, "CVE-2023-5100");
569-
assert_eq!(vulns.items[3].head.identifier, "CVE-2024-9000");
570-
assert_eq!(vulns.items[4].head.identifier, "CVE-2024-40000");
571-
assert_eq!(vulns.items[5].head.identifier, "CVE-2024-10288000");
572-
assert_eq!(vulns.items[6].head.identifier, "GHSA-xxxx-yyyy-zzzz");
592+
assert_eq!(vulns.items[1].head.identifier, "ALPINE-6789");
593+
assert_eq!(vulns.items[2].head.identifier, "ALPINE-12345");
594+
assert_eq!(vulns.items[3].head.identifier, "CVE-2024-40000");
595+
assert_eq!(vulns.items[4].head.identifier, "CVE-2024-10288000");
596+
assert_eq!(vulns.items[5].head.identifier, "GHSA-r9p9-mrjm-926w");
597+
assert_eq!(vulns.items[6].head.identifier, "GHSA-vp9c-fpxx-744v");
598+
assert_eq!(vulns.items[7].head.identifier, "GO-2024-268");
599+
assert_eq!(vulns.items[8].head.identifier, "GO-2024-1234");
600+
assert_eq!(vulns.items[9].head.identifier, "OSV-2020-58");
601+
assert_eq!(vulns.items[10].head.identifier, "OSV-2020-111");
602+
assert_eq!(vulns.items[11].head.identifier, "PYSEC-2021-1234");
603+
assert_eq!(vulns.items[12].head.identifier, "PYSEC-2024-5678");
604+
assert_eq!(vulns.items[13].head.identifier, "RUSTSEC-2019-0033");
605+
assert_eq!(vulns.items[14].head.identifier, "RUSTSEC-2024-0001");
573606

574607
// Test descending sort
575608
let vulns = service
@@ -580,14 +613,22 @@ async fn vulnerability_numeric_sorting(ctx: &TrustifyContext) -> Result<(), anyh
580613
&ctx.db,
581614
)
582615
.await?;
583-
assert_eq!(7, vulns.items.len());
584-
assert_eq!(vulns.items[0].head.identifier, "GHSA-xxxx-yyyy-zzzz");
585-
assert_eq!(vulns.items[1].head.identifier, "CVE-2024-10288000");
586-
assert_eq!(vulns.items[2].head.identifier, "CVE-2024-40000");
587-
assert_eq!(vulns.items[3].head.identifier, "CVE-2024-9000");
588-
assert_eq!(vulns.items[4].head.identifier, "CVE-2023-5100");
589-
assert_eq!(vulns.items[5].head.identifier, "CVE-2023-1234");
590-
assert_eq!(vulns.items[6].head.identifier, "ABC-xxxx-yyyy");
616+
assert_eq!(15, vulns.items.len());
617+
assert_eq!(vulns.items[0].head.identifier, "RUSTSEC-2024-0001");
618+
assert_eq!(vulns.items[1].head.identifier, "RUSTSEC-2019-0033");
619+
assert_eq!(vulns.items[2].head.identifier, "PYSEC-2024-5678");
620+
assert_eq!(vulns.items[3].head.identifier, "PYSEC-2021-1234");
621+
assert_eq!(vulns.items[4].head.identifier, "OSV-2020-111");
622+
assert_eq!(vulns.items[5].head.identifier, "OSV-2020-58");
623+
assert_eq!(vulns.items[6].head.identifier, "GO-2024-1234");
624+
assert_eq!(vulns.items[7].head.identifier, "GO-2024-268");
625+
assert_eq!(vulns.items[8].head.identifier, "GHSA-vp9c-fpxx-744v");
626+
assert_eq!(vulns.items[9].head.identifier, "GHSA-r9p9-mrjm-926w");
627+
assert_eq!(vulns.items[10].head.identifier, "CVE-2024-10288000");
628+
assert_eq!(vulns.items[11].head.identifier, "CVE-2024-40000");
629+
assert_eq!(vulns.items[12].head.identifier, "ALPINE-12345");
630+
assert_eq!(vulns.items[13].head.identifier, "ALPINE-6789");
631+
assert_eq!(vulns.items[14].head.identifier, "ABC-xxxx-yyyy");
591632

592633
Ok(())
593634
}

0 commit comments

Comments
 (0)