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:
1 parent
8629ba7bcf
commit
8f47303605
2 files changed
+155
-14
No files matched your search
+26
-14
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user