From 8609b696c98d8d227fcbec3f4435e4b0c9d58ad2 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Fri, 26 Jun 2026 09:42:12 +0200 Subject: [PATCH] fix(ucca): CM-7 repo_scan is required evidence for attack_surface_minimization evidence_required lists only required:true rows; repo_scan was required:false so attack_surface_minimization surfaced config_export alone. An attack-surface scan IS required to evidence a minimized attack surface. Adds a test pinning the curated evidence_required set per NIST obligation (the table test only checked control count). Co-Authored-By: Claude Opus 4.7 --- .../evidence_requirements/nist_evidence.jsonl | 2 +- .../compliance_graph_handlers_test.go | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ai-compliance-sdk/data/evidence_requirements/nist_evidence.jsonl b/ai-compliance-sdk/data/evidence_requirements/nist_evidence.jsonl index b126bb69..7cd78773 100644 --- a/ai-compliance-sdk/data/evidence_requirements/nist_evidence.jsonl +++ b/ai-compliance-sdk/data/evidence_requirements/nist_evidence.jsonl @@ -7,4 +7,4 @@ {"framework": "NIST SP 800-53", "control": "SI-2", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Konfiguration des sicheren Update-/Patch-Mechanismus (signierte/automatische Updates) als technischer Nachweis.", "version": "2026-06-25"} {"framework": "NIST SP 800-53", "control": "SI-2", "evidence_type": "test_report", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "Update-/Patch-Verifikationstest (CI) belegt, dass Sicherheitsupdates greifen.", "version": "2026-06-25"} {"framework": "NIST SP 800-53", "control": "CM-7", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Konfiguration deaktivierter Ports/Dienste/Funktionen als Nachweis minimierter Angriffsflaeche.", "version": "2026-06-25"} -{"framework": "NIST SP 800-53", "control": "CM-7", "evidence_type": "repo_scan", "evidence_source": "scanner", "freshness_requirement": "quarterly", "required": false, "rationale": "Angriffsflaechen-Scan (offene Ports/Dienste) — vertiefend, nicht Pflicht je Release.", "version": "2026-06-25"} +{"framework": "NIST SP 800-53", "control": "CM-7", "evidence_type": "repo_scan", "evidence_source": "scanner", "freshness_requirement": "per_release", "required": true, "rationale": "Angriffsflaechen-Scan (offene Ports/Dienste) als Nachweis tatsaechlich minimierter Angriffsflaeche.", "version": "2026-06-25"} diff --git a/ai-compliance-sdk/internal/api/handlers/compliance_graph_handlers_test.go b/ai-compliance-sdk/internal/api/handlers/compliance_graph_handlers_test.go index e7f8591b..360d5990 100644 --- a/ai-compliance-sdk/internal/api/handlers/compliance_graph_handlers_test.go +++ b/ai-compliance-sdk/internal/api/handlers/compliance_graph_handlers_test.go @@ -95,3 +95,39 @@ func TestObligationStatus_NoFulfillmentClaim(t *testing.T) { } } } + +// Pin the curated evidence_required set per NIST obligation. A required:false row silently +// drops from evidence_required, which the table test above (control-count only) would miss. +func TestObligationStatus_NISTEvidenceTypes(t *testing.T) { + r := newComplianceGraphTestRouter(t) + want := map[string][]string{ + "attack_surface_minimization": {"config_export", "repo_scan"}, + "software_integrity_protection": {"sbom", "config_export"}, + "provide_security_updates": {"config_export", "test_report"}, + } + for ob, exp := range want { + _, resp := getObligationStatus(t, r, "?obligation_id="+ob) + if len(resp.Controls) != 1 { + t.Fatalf("%s: want 1 control, got %d", ob, len(resp.Controls)) + } + if got := resp.Controls[0].EvidenceRequired; !sameStringSet(got, exp) { + t.Errorf("%s evidence_required = %v, want %v", ob, got, exp) + } + } +} + +func sameStringSet(a, b []string) bool { + if len(a) != len(b) { + return false + } + m := make(map[string]bool, len(a)) + for _, x := range a { + m[x] = true + } + for _, x := range b { + if !m[x] { + return false + } + } + return true +}