From c7845f67d6842e187d14ee8784cd783be7e01e30 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 14 Jun 2026 10:45:21 +0200 Subject: [PATCH] feat(cra): attach network_security regulatory breadth (shared Controls-API) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Semantic breadth (2): each finding's CRA-AI is mapped to a network_security sub_topic and enriched with atom-grain, framework-traceable obligations from the shared Controls-API (compliance.atom_classification) — at the endpoint/view layer (SessionLocal), NOT in the pure mapper. CRA-AI anchor + curated measure + NIST/OWASP crosswalk stay the lead; this is breadth + source evidence. Only network_security is queried (atom-grain), scoped by sub_topic + limit. Frontend renders it under the collapsible best-practice depth (control_id · title · source). Co-Authored-By: Claude Opus 4.7 --- .../cra/_components/CRACyberView.tsx | 15 ++++ .../sdk/iace/[projectId]/cra/_hooks/useCRA.ts | 2 + .../iace/[projectId]/cra/_hooks/useCRADemo.ts | 3 + .../compliance/api/cra_assess_routes.py | 20 +++++- .../services/cra_use_case_controls.py | 69 +++++++++++++++++++ .../tests/test_cra_use_case_controls.py | 16 +++++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 backend-compliance/compliance/services/cra_use_case_controls.py create mode 100644 backend-compliance/tests/test_cra_use_case_controls.py diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx index 211add54..27c23749 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx @@ -127,6 +127,21 @@ function FindingsTable({ findings }: { findings: CRAFinding[] }) { )} + {f.regulatory_breadth && f.regulatory_breadth.length > 0 && ( +
+

+ Regulatorische Breite{f.sub_topic ? ` — ${f.sub_topic}` : ''} (NIST/ENISA/ISO-Quellen) +

+
    + {f.regulatory_breadth.map((c) => ( +
  • + {c.control_id} {c.title} + · {c.source_regulation} +
  • + ))} +
+
+ )} )} diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts index 272f6297..040c4a83 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts @@ -57,6 +57,8 @@ function merge(live: any): CRADemo { risk_level: m.risk_level || (base ? base.risk_level : 'LOW'), measures: m.measures || [], evidence_type: m.evidence_type, + sub_topic: m.sub_topic, + regulatory_breadth: m.regulatory_breadth || [], priority_tier: m.priority_tier, priority_score: m.priority_score, quick_win: m.quick_win, diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts index 67219211..25cc685f 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts @@ -28,6 +28,9 @@ export interface CRAFinding { risk_level: string measures: string[] evidence_type?: string // code | process | hybrid | document — drives the remediation-class badge + // network_security regulatory breadth (atom-grain shared Controls-API), live only + sub_topic?: string + regulatory_breadth?: { control_id: string; title: string; source_regulation: string; severity?: string }[] // priority layer (set live by the backend prioritizer; optional in the static fallback) priority_tier?: string priority_score?: number diff --git a/backend-compliance/compliance/api/cra_assess_routes.py b/backend-compliance/compliance/api/cra_assess_routes.py index 1b2a8ff8..53f83c35 100644 --- a/backend-compliance/compliance/api/cra_assess_routes.py +++ b/backend-compliance/compliance/api/cra_assess_routes.py @@ -16,6 +16,8 @@ from pydantic import BaseModel from compliance.services.cra_finding_mapper import assess_findings_payload from compliance.services.cra_snapshot_store import save_snapshot, list_snapshots, get_snapshot +from compliance.services.cra_use_case_controls import enrich_findings_with_breadth +from database import SessionLocal from .tenant_utils import get_tenant_id router = APIRouter(prefix="/v1/cra", tags=["cra"]) @@ -59,15 +61,29 @@ def _payload(body: AssessRequest) -> dict: } +def _assess_enriched(body: AssessRequest) -> dict: + """Assessment + the network_security regulatory breadth (atom-grain). + + Breadth is attached at this view layer (db here), never in the pure mapper. + """ + result = assess_findings_payload(_payload(body)) + db = SessionLocal() + try: + enrich_findings_with_breadth(result.get("mapped", []), db) + finally: + db.close() + return result + + @router.post("/assess") async def assess(body: AssessRequest): - return assess_findings_payload(_payload(body)) + return _assess_enriched(body) @router.post("/projects/{project_id}/assess-snapshot") async def assess_snapshot(project_id: str, body: AssessRequest, tenant_id: str = Depends(get_tenant_id)): """Run the assessment and persist it as a versioned snapshot (running system).""" - assessment = assess_findings_payload(_payload(body)) + assessment = _assess_enriched(body) snap = save_snapshot(project_id, tenant_id, assessment) return {"snapshot": snap, "assessment": assessment} diff --git a/backend-compliance/compliance/services/cra_use_case_controls.py b/backend-compliance/compliance/services/cra_use_case_controls.py new file mode 100644 index 00000000..29cf1a78 --- /dev/null +++ b/backend-compliance/compliance/services/cra_use_case_controls.py @@ -0,0 +1,69 @@ +"""Attach the atom-grain network_security regulatory breadth to CRA findings. + +This is the "semantic breadth (2)" from the handoff: the shared Controls-API +(compliance.atom_classification, use_case=network_security, ~11k precise, +framework-traceable obligations). It runs at the ENDPOINT/VIEW layer — NOT in +the pure cra_finding_mapper, which stays deterministic. The CRA Annex I anchor + +the curated measure + the NIST/OWASP golden-set crosswalk remain the lead; this +is breadth + source evidence, not a replacement. + +Only network_security is atom-grain — we query only that, always scoped by +sub_topic + limit (per the caveats). +""" +from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS +from compliance.services.use_case_controls import UseCaseControlsService + +# CRA-AI requirement -> network_security sub_topic (via the NIST families per +# CRA-AI). Exact sub_topic keys verified against the live atom_classification. +_REQ_TO_SUBTOPIC = { + "CRA-AI-1": "secure_development", "CRA-AI-2": "network_segmentation", + "CRA-AI-3": "network_segmentation", "CRA-AI-4": "access_control", + "CRA-AI-5": "secure_development", "CRA-AI-6": "secure_development", + "CRA-AI-7": "authentication", "CRA-AI-8": "authentication", "CRA-AI-9": "authentication", + "CRA-AI-10": "access_control", "CRA-AI-11": "authentication", "CRA-AI-12": "access_control", + "CRA-AI-13": "cryptography", "CRA-AI-14": "cryptography", "CRA-AI-15": "cryptography", + "CRA-AI-16": "cryptography", "CRA-AI-17": "data_protection", + "CRA-AI-18": "secure_development", "CRA-AI-19": "secure_development", "CRA-AI-20": "secure_development", + "CRA-AI-21": "supply_chain_security", "CRA-AI-22": "vulnerability_management", + "CRA-AI-23": "supply_chain_security", + "CRA-AI-24": "logging_monitoring", "CRA-AI-25": "logging_monitoring", + "CRA-AI-26": "logging_monitoring", "CRA-AI-27": "logging_monitoring", + "CRA-AI-28": "vulnerability_management", "CRA-AI-29": "vulnerability_management", + "CRA-AI-30": "vulnerability_management", "CRA-AI-31": "vulnerability_management", + "CRA-AI-32": "vulnerability_management", "CRA-AI-33": "vulnerability_management", + "CRA-AI-34": "vulnerability_management", + "CRA-AI-35": "incident_response", "CRA-AI-36": "incident_response", + "CRA-AI-37": "incident_response", "CRA-AI-38": "incident_response", + "CRA-AI-39": "vulnerability_management", "CRA-AI-40": "incident_response", +} + + +def subtopic_for(req_id: str): + return _REQ_TO_SUBTOPIC.get(req_id) + + +def enrich_findings_with_breadth(mapped: list, db, limit: int = 5) -> None: + """Attach `sub_topic` + `regulatory_breadth` (atom controls) to each finding. + + Queries network_security once per distinct sub_topic (cached). Best-effort: + on any error a finding just gets an empty breadth — never breaks the assessment. + """ + svc = UseCaseControlsService(db) + cache: dict = {} + for m in mapped: + st = _REQ_TO_SUBTOPIC.get(m.get("primary_requirement")) + m["sub_topic"] = st + if not st: + m["regulatory_breadth"] = [] + continue + if st not in cache: + try: + res = svc.controls_for_use_case("network_security", sub_topic=st, limit=limit) + cache[st] = [ + {"control_id": c.get("control_id"), "title": c.get("title"), + "source_regulation": c.get("source_regulation"), "severity": c.get("severity")} + for c in res.get("controls", []) + ] + except Exception: + cache[st] = [] + m["regulatory_breadth"] = cache[st] diff --git a/backend-compliance/tests/test_cra_use_case_controls.py b/backend-compliance/tests/test_cra_use_case_controls.py new file mode 100644 index 00000000..31ec396d --- /dev/null +++ b/backend-compliance/tests/test_cra_use_case_controls.py @@ -0,0 +1,16 @@ +"""Pin the CRA-AI -> network_security sub_topic map (DB enrichment verified live).""" +from compliance.services.cra_use_case_controls import subtopic_for +from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS + +# Exact atom-grain sub_topic keys (verified against the live atom_classification). +_VALID = { + "access_control", "authentication", "cryptography", "network_segmentation", + "logging_monitoring", "supply_chain_security", "vulnerability_management", + "incident_response", "secure_development", "data_protection", +} + + +def test_every_requirement_maps_to_a_valid_subtopic(): + for req in ANNEX_I_REQUIREMENTS: + st = subtopic_for(req["req_id"]) + assert st in _VALID, "{} -> {}".format(req["req_id"], st)