save progress
This commit is contained in:
123
tools/slntools/lib/vulnerability_models.py
Normal file
123
tools/slntools/lib/vulnerability_models.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Data models for NuGet vulnerability checking.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class VulnerabilityDetail:
|
||||
"""Details about a specific vulnerability."""
|
||||
|
||||
severity: str # low, moderate, high, critical
|
||||
advisory_url: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class VulnerablePackage:
|
||||
"""A package with known vulnerabilities."""
|
||||
|
||||
package_id: str
|
||||
resolved_version: str
|
||||
requested_version: str
|
||||
vulnerabilities: list[VulnerabilityDetail] = field(default_factory=list)
|
||||
affected_projects: list[Path] = field(default_factory=list)
|
||||
suggested_version: str | None = None
|
||||
fix_risk: str = "unknown" # low, medium, high
|
||||
|
||||
@property
|
||||
def highest_severity(self) -> str:
|
||||
"""Get the highest severity among all vulnerabilities."""
|
||||
severity_order = {"low": 1, "moderate": 2, "high": 3, "critical": 4}
|
||||
if not self.vulnerabilities:
|
||||
return "unknown"
|
||||
return max(
|
||||
self.vulnerabilities,
|
||||
key=lambda v: severity_order.get(v.severity.lower(), 0),
|
||||
).severity
|
||||
|
||||
@property
|
||||
def advisory_urls(self) -> list[str]:
|
||||
"""Get all advisory URLs."""
|
||||
return [v.advisory_url for v in self.vulnerabilities]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SuggestedFix:
|
||||
"""Suggested fix for a vulnerable package."""
|
||||
|
||||
version: str
|
||||
is_major_upgrade: bool
|
||||
is_minor_upgrade: bool
|
||||
is_patch_upgrade: bool
|
||||
breaking_change_risk: str # low, medium, high
|
||||
|
||||
@classmethod
|
||||
def from_versions(
|
||||
cls, current: str, suggested: str, current_parsed: tuple, suggested_parsed: tuple
|
||||
) -> "SuggestedFix":
|
||||
"""Create a SuggestedFix from version tuples."""
|
||||
is_major = suggested_parsed[0] > current_parsed[0]
|
||||
is_minor = not is_major and suggested_parsed[1] > current_parsed[1]
|
||||
is_patch = not is_major and not is_minor and suggested_parsed[2] > current_parsed[2]
|
||||
|
||||
# Estimate breaking change risk
|
||||
if is_major:
|
||||
risk = "high"
|
||||
elif is_minor:
|
||||
risk = "medium"
|
||||
else:
|
||||
risk = "low"
|
||||
|
||||
return cls(
|
||||
version=suggested,
|
||||
is_major_upgrade=is_major,
|
||||
is_minor_upgrade=is_minor,
|
||||
is_patch_upgrade=is_patch,
|
||||
breaking_change_risk=risk,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VulnerabilityReport:
|
||||
"""Complete vulnerability scan report."""
|
||||
|
||||
solution: Path
|
||||
min_severity: str
|
||||
total_packages: int
|
||||
vulnerabilities: list[VulnerablePackage] = field(default_factory=list)
|
||||
unfixable: list[tuple[str, str]] = field(default_factory=list) # (package, reason)
|
||||
|
||||
@property
|
||||
def vulnerable_count(self) -> int:
|
||||
"""Count of vulnerable packages."""
|
||||
return len(self.vulnerabilities)
|
||||
|
||||
@property
|
||||
def fixable_count(self) -> int:
|
||||
"""Count of packages with suggested fixes."""
|
||||
return sum(1 for v in self.vulnerabilities if v.suggested_version)
|
||||
|
||||
@property
|
||||
def unfixable_count(self) -> int:
|
||||
"""Count of packages without fixes."""
|
||||
return len(self.unfixable) + sum(
|
||||
1 for v in self.vulnerabilities if not v.suggested_version
|
||||
)
|
||||
|
||||
|
||||
# Severity level mapping for comparisons
|
||||
SEVERITY_LEVELS = {
|
||||
"low": 1,
|
||||
"moderate": 2,
|
||||
"high": 3,
|
||||
"critical": 4,
|
||||
}
|
||||
|
||||
|
||||
def meets_severity_threshold(vuln_severity: str, min_severity: str) -> bool:
|
||||
"""Check if vulnerability meets minimum severity threshold."""
|
||||
vuln_level = SEVERITY_LEVELS.get(vuln_severity.lower(), 0)
|
||||
min_level = SEVERITY_LEVELS.get(min_severity.lower(), 0)
|
||||
return vuln_level >= min_level
|
||||
Reference in New Issue
Block a user