Skip to content

Commit 583a731

Browse files
committed
Prefer CVE ids and merge duplicate vuln findings
Use CVE identifiers when GitHub advisories provide them, while keeping alternate advisory ids as aliases for reconciliation. Also merge duplicate findings across the final vulnerability output so the same issue is not reported separately under GHSA and CVE ids.
1 parent 8ac2707 commit 583a731

1 file changed

Lines changed: 46 additions & 4 deletions

File tree

dep_checker/main.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ def default(self, obj):
9696
vulnerableVersionRange
9797
advisory {
9898
ghsaId
99+
identifiers {
100+
type
101+
value
102+
}
99103
permalink
100104
withdrawnAt
101105
}
@@ -105,6 +109,38 @@ def default(self, obj):
105109
"""
106110
)
107111

112+
def preferred_advisory_id(advisory: dict) -> str:
113+
for identifier in advisory.get("identifiers") or []:
114+
if identifier.get("type") == "CVE" and identifier.get("value"):
115+
return identifier["value"]
116+
return advisory["ghsaId"]
117+
118+
119+
def advisory_aliases(advisory: dict, preferred_id: str) -> list[str]:
120+
aliases: list[str] = []
121+
for identifier in advisory.get("identifiers") or []:
122+
value = identifier.get("value")
123+
if value and value != preferred_id and value not in aliases:
124+
aliases.append(value)
125+
return aliases
126+
127+
128+
def merge_vulnerabilities(vulnerabilities: list[Vulnerability]) -> list[Vulnerability]:
129+
merged: dict[str, Vulnerability] = {}
130+
for vuln in vulnerabilities:
131+
candidate_ids = [vuln.id, *(vuln.advisory_aliases or [])]
132+
for candidate_id in candidate_ids:
133+
existing = merged.get(candidate_id)
134+
if existing is None:
135+
continue
136+
for alias in vuln.advisory_aliases or []:
137+
if alias != existing.id and alias not in existing.advisory_aliases:
138+
existing.advisory_aliases.append(alias)
139+
break
140+
else:
141+
merged[vuln.id] = vuln
142+
return list(merged.values())
143+
108144

109145
def resolve_dependencies(
110146
repo_path: Path, repo_branch: str
@@ -202,16 +238,19 @@ def query_ghad(
202238
for v in result["securityVulnerabilities"]["nodes"]
203239
if v["advisory"]["withdrawnAt"] is None
204240
and dep_version in SpecifierSet(v["vulnerableVersionRange"])
205-
and v["advisory"]["ghsaId"] not in ignore_list
241+
and preferred_advisory_id(v["advisory"]) not in ignore_list
206242
]
207243
if matching_vulns:
208244
found_vulnerabilities.extend(
209245
[
210246
Vulnerability(
211-
id=vuln["advisory"]["ghsaId"],
247+
id=preferred_advisory_id(vuln["advisory"]),
212248
url=vuln["advisory"]["permalink"],
213249
dependency=name,
214250
version=dep_version,
251+
advisory_aliases=advisory_aliases(
252+
vuln["advisory"], preferred_advisory_id(vuln["advisory"])
253+
),
215254
)
216255
for vuln in matching_vulns
217256
]
@@ -417,11 +456,14 @@ def main() -> int:
417456
print(f"Warning: npm vulnerability checking failed: {e}", file=sys.stderr)
418457
print(f"Traceback: {traceback.format_exc()}", file=sys.stderr)
419458

459+
merged_vulnerabilities = merge_vulnerabilities(
460+
ghad_vulnerabilities + nvd_vulnerabilities + npm_vulnerabilities
461+
)
420462
all_vulnerabilities = {
421-
"vulnerabilities": ghad_vulnerabilities + nvd_vulnerabilities + npm_vulnerabilities,
463+
"vulnerabilities": merged_vulnerabilities,
422464
"scan_complete": scan_complete,
423465
}
424-
no_vulnerabilities_found = not ghad_vulnerabilities and not nvd_vulnerabilities and not npm_vulnerabilities
466+
no_vulnerabilities_found = not merged_vulnerabilities
425467

426468
if scan_file is not None:
427469
with open(scan_file, "w") as f:

0 commit comments

Comments
 (0)