Report CipherCell SCA first, then Lynkeus, then SAST, everywhere

The dependency check is named CipherCell SCA throughout, and the sources
keep one order on the cover, in the summary, the most-critical table and
the sections. Lynkeus before SAST no longer depends on the run order in
the SARIF file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
alexanderandClaude Opus 5.5 committed 2026-09-24 21:31:37 +02:00
1 parent cce3e85e9d
commit 303bcdf0b4
2 files changed
+55 -49

No files matched your search

+52 -47
View File
@@ -44,6 +44,13 @@ BARS = 10
SEVERITY_ORDER = ("Critical", "High", "Medium", "Low")
# What the dependency check is called wherever the report names its source.
SCA = "CipherCell SCA"
# The order the sources are reported in, everywhere: the dependency check first, then the SARIF
# tools in this order. A tool not named here follows them, in the order its run appears.
TOOL_ORDER = ("CipherCell Lynkeus", "CipherCell SAST")
# A full git commit SHA.
_COMMIT = re.compile(r"[0-9a-f]{40}")
@@ -77,7 +84,8 @@ def build_context(
context: dict[str, Any] = {
"cover": _cover(sources, metadata, tools, sca, project=project, manufacturer=manufacturer),
"summary": _summary(tools, sca, documents.get("metrics")),
"sections": [_tool_section(name, report) for name, report in tools],
"sections": ([_sca_section(sca)] if sca is not None else [])
+ [_tool_section(name, report) for name, report in tools],
}
# Without the pipeline's metadata or cloc's count this page would be nothing but gaps.
@@ -89,9 +97,6 @@ def build_context(
if critical["table"]["rows"]:
context["critical"] = critical
if sca is not None:
context["sections"].insert(0, _sca_section(sca))
set_aside = _set_aside_section(tools)
if set_aside is not None:
@@ -109,7 +114,9 @@ def _tools(sarif: Any) -> list[tuple[str, SarifReport]]: # noqa: ANN401
runs = [run for run in sarif.get("runs") or [] if isinstance(run, dict)]
return [(_tool_name(run), _summarize(run)) for run in runs]
tools = [(_tool_name(run), _summarize(run)) for run in runs]
return sorted(tools, key=lambda pair: TOOL_ORDER.index(pair[0]) if pair[0] in TOOL_ORDER else len(TOOL_ORDER))
def _summarize(run: dict[str, Any]) -> SarifReport:
@@ -182,7 +189,7 @@ def _cover(
manufacturer: str | None,
) -> dict[str, Any]:
"""The first page."""
technologies = [name for name, _ in tools] + (["CipherCell SCA"] if sca is not None else [])
technologies = ([SCA] if sca is not None else []) + [name for name, _ in tools]
return {
"scan_id": str(sources.scan_id),
@@ -277,7 +284,7 @@ def _summary(
"""The counts, and where they concentrate."""
sca_total = sca.total if sca else 0
raw = sum(report.total for _, report in tools) + sca_total
true_issues = [f"{len(_kept(report))} {name}" for name, report in tools] + ([f"{sca_total} SCA"] if sca else [])
true_issues = ([f"{sca_total} {SCA}"] if sca else []) + [f"{len(_kept(report))} {name}" for name, report in tools]
summary = {
"id": "summary",
@@ -386,18 +393,30 @@ def _remedy(value: dict[str, Any]) -> str | None:
def _issue_types(tools: list[tuple[str, SarifReport]], sca: VdrReport | None) -> list[str]:
"""How many distinct kinds of true issue each tool, and the dependency check, found."""
counted = [f"{len({one.rule_id for one in _kept(report)})} {name} rules" for name, report in tools]
counted = [f"{sca.component_count} {SCA} affected components"] if sca is not None else []
if sca is not None:
counted.append(f"{sca.component_count} affected components")
return counted
return counted + [f"{len({one.rule_id for one in _kept(report)})} {name} rules" for name, report in tools]
def _distributions(tools: list[tuple[str, SarifReport]], sca: VdrReport | None) -> list[dict[str, Any]]:
"""Where the issues concentrate: by rule for each SARIF tool, by package for SCA."""
"""Where the issues concentrate: by package for SCA, by rule for each SARIF tool."""
blocks = []
if sca and sca.vulnerabilities:
blocks.append(
{
"title": f"Most affected components, true {SCA} issues ({sca.component_count})",
**_bars(
_group(
[(one, name) for one in sca.vulnerabilities for name in one.component_names],
lambda pair: pair[1],
lambda pair: _vdr_severity(pair[0]),
),
mono=True,
),
}
)
for name, report in tools:
findings = _kept(report)
@@ -409,21 +428,6 @@ def _distributions(tools: list[tuple[str, SarifReport]], sca: VdrReport | None)
}
)
if sca and sca.vulnerabilities:
blocks.append(
{
"title": f"Most affected components, true SCA issues ({sca.component_count})",
**_bars(
_group(
[(one, name) for one in sca.vulnerabilities for name in one.component_names],
lambda pair: pair[1],
lambda pair: _vdr_severity(pair[0]),
),
mono=True,
),
}
)
return blocks
@@ -498,6 +502,23 @@ def _critical(tools: list[tuple[str, SarifReport]], sca: VdrReport | None) -> di
"""The handful worth reading first, whichever tool found them."""
rows: list[tuple[str, dict[str, Any]]] = []
for vulnerability in sca.vulnerabilities if sca else []:
severity = _vdr_severity(vulnerability)
rows.append(
(
severity,
{
"cells": [
SCA,
{"value": severity, "kind": "severity"},
_stated(vulnerability.id),
{"value": _stated(", ".join(vulnerability.component_names)), "kind": "mono"},
_stated(vulnerability.description),
]
},
)
)
for name, report in tools:
for finding in _kept(report):
severity = _finding_severity(finding)
@@ -516,24 +537,8 @@ def _critical(tools: list[tuple[str, SarifReport]], sca: VdrReport | None) -> di
)
)
for vulnerability in sca.vulnerabilities if sca else []:
severity = _vdr_severity(vulnerability)
rows.append(
(
severity,
{
"cells": [
"SCA",
{"value": severity, "kind": "severity"},
_stated(vulnerability.id),
{"value": _stated(", ".join(vulnerability.component_names)), "kind": "mono"},
_stated(vulnerability.description),
]
},
)
)
# Sorted is stable, so within a severity each tool keeps its own order -- the rank, where it has one.
# Sorted is stable, so within a severity the sources keep the report's order, and each tool its own
# -- the rank, where it has one.
rows.sort(key=lambda pair: SEVERITY_ORDER.index(pair[0]))
return {
@@ -634,7 +639,7 @@ def _sca_section(sca: VdrReport) -> dict[str, Any]:
"""The dependency findings."""
return {
"id": "sca",
"title": "Dependency Vulnerabilities (SBOM / SCA)",
"title": f"{SCA} — Dependency Vulnerabilities",
"new_page": True,
"groups": [
{