Integrate metrics from metrics.json into the report pdf.
This commit is contained in:
1 parent
9d7df77923
commit
9247608a9f
5 files changed
+155
-18
No files matched your search
@@ -72,7 +72,7 @@ def build_context(sources: ReportSources, documents: dict[str, Any]) -> dict[str
|
||||
"cover": _cover(sources, metadata, sast, sca),
|
||||
"scan": _scan(sources, metadata),
|
||||
"project": _project(metadata),
|
||||
"summary": _summary(sast, sca),
|
||||
"summary": _summary(sast, sca, documents.get("metrics")),
|
||||
"sections": [],
|
||||
}
|
||||
|
||||
@@ -226,14 +226,14 @@ def _project(metadata: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _summary(sast: SarifReport | None, sca: VdrReport | None) -> dict[str, Any]:
|
||||
def _summary(sast: SarifReport | None, sca: VdrReport | None, metrics: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""The counts, and where they concentrate."""
|
||||
true_sast = _true_findings(sast)
|
||||
sca_total = sca.total if sca else 0
|
||||
raw = (sast.total if sast else 0) + sca_total
|
||||
filtered = sast.false_positive_count if sast else 0
|
||||
|
||||
return {
|
||||
summary = {
|
||||
"title": "Summary",
|
||||
"overview": [
|
||||
_fact("Raw findings", raw or None),
|
||||
@@ -248,6 +248,90 @@ def _summary(sast: SarifReport | None, sca: VdrReport | None) -> dict[str, Any]:
|
||||
"distributions": _distributions(true_sast, sca),
|
||||
}
|
||||
|
||||
dependencies = metrics.get("dependencies") if isinstance(metrics, dict) else None
|
||||
if isinstance(dependencies, dict):
|
||||
summary["metrics"] = _metric_cards(dependencies)
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def _metric_cards(dependencies: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Turn the metrics document into compact summary cards."""
|
||||
scores = dependencies.get("scores") or {}
|
||||
impact = dependencies.get("impact") or {}
|
||||
exploitability = dependencies.get("exploitability") or {}
|
||||
temporal = dependencies.get("temporal") or {}
|
||||
published = temporal.get("published") or {}
|
||||
remediation = dependencies.get("remediation") or {}
|
||||
upgrades = remediation.get("newer_version_available") or {}
|
||||
surface = dependencies.get("affected_surface") or {}
|
||||
taxonomy = dependencies.get("taxonomy") or {}
|
||||
|
||||
return [
|
||||
{
|
||||
"title": "Severity & impact",
|
||||
"rows": [
|
||||
_fact("CVSS", _metric(scores.get("cvss"), "mean")),
|
||||
_fact("Severity band", (scores.get("cvss") or {}).get("band"), kind="severity"),
|
||||
_fact("Confidentiality", _metric(impact.get("scores"), "confidentiality")),
|
||||
_fact("Integrity", _metric(impact.get("scores"), "integrity")),
|
||||
_fact("Availability", _metric(impact.get("scores"), "availability")),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "Exploitability",
|
||||
"rows": [_fact(_label(axis), _metric(exploitability.get(axis), "score")) for axis in (
|
||||
"attack_vector", "attack_complexity", "privileges_required", "user_interaction"
|
||||
)],
|
||||
},
|
||||
{
|
||||
"title": "Temporal",
|
||||
"rows": [
|
||||
_fact("Newest published", _published(published.get("newest"))),
|
||||
_fact("Mean vulnerability age", _days(published.get("mean_days"))),
|
||||
_fact("Median vulnerability age", _days(published.get("median_days"))),
|
||||
_fact("Time to exploit", temporal.get("time_to_exploit_days")),
|
||||
_fact("CVE volume trends", temporal.get("cve_volume_trends")),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "Surface & taxonomy",
|
||||
"rows": [
|
||||
_fact("Affected products", surface.get("products")),
|
||||
_fact("Affected versions", surface.get("versions")),
|
||||
_fact("Vendors", ", ".join(surface.get("vendors") or []) or None),
|
||||
_fact("Potential remedies", _remedy(upgrades)),
|
||||
_fact("CWE coverage", taxonomy.get("coverage")),
|
||||
_fact("CWE abstraction level", taxonomy.get("abstraction_level")),
|
||||
_fact("CWE relationships", taxonomy.get("relationships")),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _metric(value: Any, key: str) -> Any: # noqa: ANN401
|
||||
return value.get(key) if isinstance(value, dict) else None
|
||||
|
||||
|
||||
def _published(value: Any) -> Any: # noqa: ANN401
|
||||
return value.get("cve") if isinstance(value, dict) else None
|
||||
|
||||
|
||||
def _days(value: Any) -> str | None: # noqa: ANN401
|
||||
return f"{value} days" if isinstance(value, int | float) else None
|
||||
|
||||
|
||||
def _remedy(value: dict[str, Any]) -> str | None:
|
||||
findings = value.get("findings")
|
||||
packages = value.get("packages")
|
||||
if isinstance(findings, int) and isinstance(packages, int):
|
||||
return f"{findings} findings across {packages} packages upgradeable"
|
||||
return None
|
||||
|
||||
|
||||
def _label(value: str) -> str:
|
||||
return value.replace("_", " ").title()
|
||||
|
||||
|
||||
def _issue_types(sast: SarifReport | None, sca: VdrReport | None) -> list[str]:
|
||||
"""How many distinct kinds of issue each half of the pipeline found."""
|
||||
|
||||
Reference in new issue
Block a user