feat(cra): SBOM- + DAST-Findings aus dem Scanner-MCP konsumieren
CI / detect-changes (push) Successful in 8s
CI / branch-name (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 6s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Successful in 1m4s
CI / iace-gt-coverage (push) Successful in 15s
CI / test-python-backend (push) Successful in 24s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped

Sharangs compliance-scanner-agent exponiert SBOM (sbom_vuln_report) + DAST
(list_dast_findings) als eigene MCP-Tools (nicht via list_findings). Neuer
fetch_all_findings(repo_id) zieht list_findings + SBOM + DAST in EINER
MCP-Session und normalisiert ins Finding-Schema:
- SBOM: ein Finding pro verwundbarem Paket (nicht pro CVE), cwe=CWE-1395
  -> deterministisch CRA-AI-22 (robust gegen Paketnamen wie "sqlite").
- DAST: cwe/endpoint/vuln_type uebernommen -> Mapping via cwe/keywords.
assess-from-scanner nutzt fetch_all_findings + liefert source.breakdown
(code/sbom/dast). DAST hat im MCP keinen repo_id-Filter -> dast_repo_scoped:false
(deployment-weit, transparent geflaggt).

Echte MCP-Daten: Kitchenasty 58 code + 35 sbom + 81 dast -> 174 gemappt
(Coverage 94,3%, alle 35 SBOM -> CRA-AI-22).

Enthaelt zusaetzlich das Qdrant->Prod-Kopierскript (#42, verbatim macmini->prod).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Bönisch
2026-06-18 12:05:05 +02:00
parent 8f21650d74
commit 43e02f794a
4 changed files with 298 additions and 3 deletions
@@ -0,0 +1,77 @@
"""SBOM/DAST normalization from the scanner MCP -> CRA finding shape + mapping.
Shapes pinned from the live MCP (sbom_vuln_report / list_dast_findings, 2026-06-18).
"""
import json
from compliance.services.scanner_mcp_client import normalize_sbom_report, normalize_dast
from compliance.services.cra_finding_mapper import ScannerFinding, map_finding
SBOM = json.dumps({
"repo_id": "r1",
"vulnerable_packages_count": 1,
"total_vulnerabilities": 3,
"packages": [
{"name": "dompurify", "version": "3.3.3", "package_manager": "npm",
"license": "MIT", "vulnerabilities": [
{"id": "GHSA-39q2", "source": "osv", "severity": None},
{"id": "GHSA-39q2", "source": "osv", "severity": None}, # dup
{"id": "GHSA-76mc", "source": "osv", "severity": "high"}]},
{"name": "clean-pkg", "version": "1.0", "package_manager": "npm",
"vulnerabilities": []}, # no vulns -> skipped
],
})
DAST = json.dumps([
{"_id": {"$oid": "abc123"}, "vuln_type": "security_misconfiguration",
"title": "SQL backup exposure: /backup.sql", "description": "Sensitive resource accessible.",
"severity": "high", "cwe": "CWE-16", "endpoint": "https://demo.x/backup.sql",
"method": "GET", "exploitable": True},
])
class TestSbom:
def test_one_finding_per_vulnerable_package(self):
out = normalize_sbom_report(SBOM)
assert len(out) == 1 # clean-pkg skipped
f = out[0]
assert f["scan_type"] == "dependency"
assert f["cwe"] == "CWE-1395"
assert f["location"] == "npm:dompurify@3.3.3"
assert f["severity"] == "high" # escalated from the one graded vuln
assert "GHSA-39q2" in f["description"] and "GHSA-76mc" in f["description"]
def test_maps_to_dependency_requirement_even_with_keyword_in_name(self):
# CWE path dominates → CRA-AI-22, not CRA-AI-20 from a "sql"-like name
out = normalize_sbom_report(json.dumps({
"repo_id": "r", "packages": [
{"name": "sqlite3", "version": "5.0", "package_manager": "npm",
"vulnerabilities": [{"id": "CVE-x", "severity": "medium"}]}]}))
m = map_finding(ScannerFinding.from_dict(out[0]))
assert m.primary_requirement == "CRA-AI-22"
def test_bad_json(self):
assert normalize_sbom_report("not json") == []
assert normalize_sbom_report("{}") == []
class TestDast:
def test_normalizes_dast_finding(self):
out = normalize_dast(DAST)
assert len(out) == 1
f = out[0]
assert f["scan_type"] == "dast"
assert f["cwe"] == "CWE-16"
assert f["location"] == "https://demo.x/backup.sql"
assert f["exploited"] is True
assert "security_misconfiguration" in f["description"]
def test_dast_maps_via_cwe(self):
out = normalize_dast(DAST)
m = map_finding(ScannerFinding.from_dict(out[0]))
assert m.primary_requirement == "CRA-AI-1" # CWE-16 -> secure config
assert m.finding_id == "abc123" # _id.$oid extracted
def test_empty(self):
assert normalize_dast("[]") == []
assert normalize_dast("not json") == []