Grade SARIF findings by score, then CWE, then level

A result's own security-severity score is banded like CVSS; without one,
the finding is graded by a hand-built CWE table; failing that, by its
SARIF level, one band lower than before. The scanners' levels said more
about the tool than about the risk: a container running as root came out
Critical and a hard-coded password Medium.

The summariser only reads security-severity off the rule, and Lynkeus
scores each result, so the runs are summarised here to keep that score.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
alexanderandClaude Opus 5.5 committed 2026-09-24 21:06:44 +02:00
1 parent 8629ba7bcf
commit 8f47303605
2 files changed
+155 -14

No files matched your search

+26 -14
View File
@@ -22,10 +22,11 @@ from collections import Counter
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from .vendor.cipherverifier.core.services.sarif import summarize_sarif
from .vendor.cipherverifier.core.services.sarif import _build_finding, _build_report, _rules_by_id
from .vendor.cipherverifier.core.services.vdr import summarize_vdr
from .layout import TITLE
from .severity import finding_severity
if TYPE_CHECKING:
from .vendor.cipherverifier.core.services.sarif import Finding, SarifReport
@@ -40,16 +41,6 @@ UNKNOWN = "<xxx>"
MOST_CRITICAL = 6
BARS = 10
# The severity a SARIF level is reported under. SARIF names four levels and the report bands five,
# so the mapping is written down rather than inferred from the spelling.
SEVERITY_OF_LEVEL = {
"error": "Critical",
"warning": "High",
"note": "Medium",
"none": "Low",
"info": "Low",
}
SEVERITY_ORDER = ("Critical", "High", "Medium", "Low")
# A full git commit SHA.
@@ -118,7 +109,28 @@ 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_sarif({"runs": [run]})) for run in runs]
return [(_tool_name(run), _summarize(run)) for run in runs]
def _summarize(run: dict[str, Any]) -> SarifReport:
"""Summarise one run, as :func:`summarize_sarif` would, keeping each result's own score.
The summariser reads ``security-severity`` off the rule only. Lynkeus scores each result instead,
and a result's own score is the more specific one, so it takes the rule's place.
"""
rules = _rules_by_id([run])
findings = []
for result in run.get("results") or []:
finding = _build_finding(result, run, rules)
stated = (result.get("properties") or {}).get("security-severity")
if stated is not None:
finding.rule_security_severity = str(stated)
findings.append(finding)
return _build_report(findings, ranked=any(finding.rank is not None for finding in findings))
def _tool_name(run: dict[str, Any]) -> str:
@@ -732,8 +744,8 @@ def _anchor(title: str) -> str:
def _finding_severity(finding: Finding) -> str:
"""The severity band a SARIF level falls in."""
return SEVERITY_OF_LEVEL.get(finding.level, "Low")
"""The severity band a SARIF finding falls in; see :mod:`severity`."""
return finding_severity(finding.rule_id, finding.level, finding.rule_security_severity)
def _vdr_severity(vulnerability: Vulnerability) -> str: