Skip to content

Commit 0c1b989

Browse files
committed
test(frameworks): pin cve bare-major + reference-url invariants
lock the bare-major boundary matrix in versionAffected (7 vs 70/17/7000, 4.20 vs 4.2, 4.1 vs 4.10) and assert every knownCVEs entry's references exactly match its own CVE ID's nvd detail url, so a future edit can't silently widen the boundary or misattribute a reference. also pins Finding.Line() staying frozen to "[severity] target module title" for a framework result once cve enrichment (version/cves/references) is populated, guarding against enrichment fields leaking into the line.
1 parent d5e7447 commit 0c1b989

3 files changed

Lines changed: 123 additions & 2 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
3+
: :
4+
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
5+
: ▄█ █ █▀ · BSD 3-Clause License :
6+
: :
7+
: (c) 2022-2026 vmfunc, xyzeva, :
8+
: lunchcat alumni & contributors :
9+
: :
10+
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
11+
*/
12+
13+
package finding
14+
15+
import (
16+
"fmt"
17+
"strings"
18+
"testing"
19+
20+
"github.com/vmfunc/sif/internal/scan/frameworks"
21+
)
22+
23+
// a cve-enriched framework result must still collapse to the frozen
24+
// "[severity] target module title" line - version, cves and references stay
25+
// out of Line() even once cve enrichment is populated, so downstream
26+
// notify/grep/awk consumers never see the line shape shift under them.
27+
func TestFlattenFramework_LineFrozenWithCVEEnrichment(t *testing.T) {
28+
fw := &frameworks.FrameworkResult{
29+
Name: "Drupal",
30+
Version: "10",
31+
Confidence: 0.9,
32+
CVEs: []string{"CVE-2023-44487 (high)"},
33+
Suggestions: []string{"Update to Drupal 10.1.4 or later"},
34+
RiskLevel: "high",
35+
References: []string{"https://nvd.nist.gov/vuln/detail/CVE-2023-44487"},
36+
}
37+
38+
findings := Flatten(target, "framework", fw)
39+
if len(findings) != 1 {
40+
t.Fatalf("got %d findings, want 1", len(findings))
41+
}
42+
43+
f := findings[0]
44+
want := fmt.Sprintf("[%s] %s %s %s", f.Severity, target, "framework", "Drupal detected")
45+
if got := f.Line(); got != want {
46+
t.Errorf("Line() = %q, want %q", got, want)
47+
}
48+
if f.Severity != SeverityHigh {
49+
t.Errorf("severity = %v, want %v (from RiskLevel=high)", f.Severity, SeverityHigh)
50+
}
51+
for _, leak := range []string{"CVE-2023-44487", "nvd.nist.gov"} {
52+
if strings.Contains(f.Line(), leak) {
53+
t.Errorf("Line() leaked enrichment detail %q: %q", leak, f.Line())
54+
}
55+
}
56+
}

internal/scan/frameworks/cve_internal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ func TestResolveVersionFeedsCVELookup(t *testing.T) {
6969
// is "10"/"9". the cve lookup must still surface the matching CVE instead of
7070
// missing it because the affected entries are dotted.
7171
func TestGetVulnerabilitiesBareMajor(t *testing.T) {
72-
if cves, _ := getVulnerabilities("Drupal", "10"); len(cves) == 0 {
72+
if cves, _, _ := getVulnerabilities("Drupal", "10"); len(cves) == 0 {
7373
t.Error("expected Drupal 10 to surface CVE-2023-44487, got none")
7474
}
75-
if cves, _ := getVulnerabilities("Drupal", "9"); len(cves) == 0 {
75+
if cves, _, _ := getVulnerabilities("Drupal", "9"); len(cves) == 0 {
7676
t.Error("expected Drupal 9 to surface CVE-2023-44487, got none")
7777
}
7878
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
3+
: :
4+
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
5+
: ▄█ █ █▀ · BSD 3-Clause License :
6+
: :
7+
: (c) 2022-2026 vmfunc, xyzeva, :
8+
: lunchcat alumni & contributors :
9+
: :
10+
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
11+
*/
12+
13+
package frameworks
14+
15+
import "testing"
16+
17+
// bare-major boundary matrix: a coarse detected major ("7") must cover its own
18+
// dotted sub-versions but never a version that merely shares the same leading
19+
// digits ("70.0", "17.0", "7000.0"). this is the off-by-one guard for the
20+
// bare-major fix in versionAffected - it pins the exact boundary, not just one
21+
// example of it.
22+
func TestVersionAffected_BareMajorBoundary(t *testing.T) {
23+
tests := []struct {
24+
version string
25+
affected string
26+
want bool
27+
}{
28+
{"7", "7.0", true},
29+
{"7", "7.4", true},
30+
{"7", "7.9", true},
31+
{"7", "70.0", false},
32+
{"7", "17.0", false},
33+
{"7", "7000.0", false},
34+
{"7", "6.9", false},
35+
{"4.20", "4.2", false},
36+
{"4.1", "4.10", false},
37+
{"4.10", "4.1", false},
38+
}
39+
40+
for _, tt := range tests {
41+
if got := versionAffected(tt.version, tt.affected); got != tt.want {
42+
t.Errorf("versionAffected(%q, %q) = %v, want %v", tt.version, tt.affected, got, tt.want)
43+
}
44+
}
45+
}
46+
47+
// every CVE entry must carry at least one reference, and that reference must
48+
// be exactly the NVD detail link derived from the entry's own CVE ID - no
49+
// stale/typo'd/borrowed reference from a neighboring entry.
50+
func TestKnownCVEs_ReferencesMatchOwnCVEID(t *testing.T) {
51+
for framework, entries := range knownCVEs {
52+
for _, entry := range entries {
53+
if len(entry.References) == 0 {
54+
t.Errorf("%s %s: no references", framework, entry.CVE)
55+
continue
56+
}
57+
want := "https://nvd.nist.gov/vuln/detail/" + entry.CVE
58+
for _, ref := range entry.References {
59+
if ref != want {
60+
t.Errorf("%s %s: reference %q, want %q", framework, entry.CVE, ref, want)
61+
}
62+
}
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)