feat(cra): standalone CRA finding->Annex I risk mapper + MCP interface
Deterministic mapper (no DB/LLM): repo-scanner findings -> the CRA Annex I essential requirement(s) they violate -> risk level -> remediation measures + coverage. Reuses the existing Annex I spine (cra_annex_i_data). The MCP server (compliance/mcp/server.py, stdio) is the thin transport the external scanner queries; all logic lives in the fully-tested mapper. Works standalone (no project/FMEA required). No DB migrations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Tests for the deterministic CRA finding -> Annex I requirement mapper."""
|
||||
from compliance.services.cra_finding_mapper import (
|
||||
ScannerFinding, map_finding, assess_findings, assess_findings_payload,
|
||||
)
|
||||
|
||||
|
||||
def test_hardcoded_credentials_cwe_maps_to_credential_requirement():
|
||||
m = map_finding(ScannerFinding(id="f1", title="Hardcoded API key", cwe="CWE-798", severity="high"))
|
||||
assert m.primary_requirement == "CRA-AI-9"
|
||||
assert "CRA-AI-8" in m.requirement_ids
|
||||
assert not m.unmapped
|
||||
assert m.annex_anchor # spine carries the Annex anchor
|
||||
|
||||
|
||||
def test_default_password_is_critical_and_carries_measure_M542():
|
||||
m = map_finding(ScannerFinding(id="f2", title="Universal default password", cwe="CWE-259", severity="critical"))
|
||||
assert m.primary_requirement == "CRA-AI-8"
|
||||
assert m.risk_level == "CRITICAL"
|
||||
assert "M542" in m.measures # default-password change measure from the spine
|
||||
|
||||
|
||||
def test_weak_tls_via_keyword_maps_to_transport_crypto():
|
||||
m = map_finding(ScannerFinding(id="f3", title="TLS 1.0 enabled", description="weak protocol", severity="high"))
|
||||
assert m.primary_requirement == "CRA-AI-15"
|
||||
assert not m.unmapped
|
||||
|
||||
|
||||
def test_dependency_cve_without_cwe_maps_to_dependency_monitoring():
|
||||
m = map_finding(ScannerFinding(id="f4", title="lodash 4.17.4 has known CVE", category="dependency", severity="high"))
|
||||
assert m.primary_requirement == "CRA-AI-22"
|
||||
|
||||
|
||||
def test_severity_derived_from_cvss_when_missing():
|
||||
m = map_finding(ScannerFinding(id="f5", title="cleartext transmission", cwe="CWE-319", cvss=9.4))
|
||||
# finding sev (CRITICAL from cvss) escalates over requirement sev
|
||||
assert m.risk_level == "CRITICAL"
|
||||
|
||||
|
||||
def test_risk_is_max_of_finding_and_requirement_severity():
|
||||
# low-severity finding but the requirement (CRA-AI-8, CRITICAL) dominates
|
||||
m = map_finding(ScannerFinding(id="f6", title="default password", severity="low"))
|
||||
assert m.primary_requirement == "CRA-AI-8"
|
||||
assert m.risk_level == "CRITICAL"
|
||||
|
||||
|
||||
def test_unmapped_finding_is_flagged_not_invented():
|
||||
m = map_finding(ScannerFinding(id="f7", title="zzz unrelated note", severity="low"))
|
||||
assert m.unmapped
|
||||
assert m.requirement_ids == []
|
||||
|
||||
|
||||
def test_assessment_aggregates_and_coverage():
|
||||
findings = [
|
||||
ScannerFinding(id="a", cwe="CWE-259", severity="critical"), # CRA-AI-8
|
||||
ScannerFinding(id="b", title="TLS 1.0", severity="high"), # CRA-AI-15
|
||||
ScannerFinding(id="c", title="zzz nothing", severity="low"), # unmapped
|
||||
]
|
||||
a = assess_findings(findings)
|
||||
assert a.findings_total == 3
|
||||
assert sum(a.by_risk.values()) == 3
|
||||
assert "CRA-AI-8" in a.requirements_touched
|
||||
assert "c" in a.unmapped_findings
|
||||
assert a.coverage_pct == round(100.0 * 2 / 3, 1)
|
||||
assert any(meas["id"] == "M542" for meas in a.open_measures)
|
||||
assert all("description" in meas for meas in a.open_measures)
|
||||
|
||||
|
||||
def test_payload_entry_is_json_serializable_and_deterministic():
|
||||
payload = {"findings": [
|
||||
{"id": "x", "cwe": "CWE-798", "severity": "high"},
|
||||
{"id": "y", "type": "dependency", "name": "openssl CVE-2024-x", "severity": "critical"},
|
||||
]}
|
||||
r1 = assess_findings_payload(payload)
|
||||
r2 = assess_findings_payload(payload)
|
||||
assert r1 == r2 # deterministic
|
||||
assert r1["findings_total"] == 2
|
||||
assert isinstance(r1["mapped"], list) and isinstance(r1["mapped"][0], dict)
|
||||
assert r1["mapped"][0]["primary_requirement"] == "CRA-AI-9"
|
||||
|
||||
|
||||
def test_empty_payload_is_safe():
|
||||
r = assess_findings_payload({})
|
||||
assert r["findings_total"] == 0
|
||||
assert r["coverage_pct"] == 0.0
|
||||
Reference in New Issue
Block a user