Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8609b696c9 | |||
| 207fc9cb56 | |||
| fdaf547b06 | |||
| fa536f9714 | |||
| cba066f49b | |||
| 75f7bd8de4 | |||
| f85fff4398 | |||
| 3bcffaf52c | |||
| 3a19affb67 | |||
| 2b985ad526 | |||
| 4e761c1363 | |||
| 9aef5ecf6c | |||
| f6c5f4e0a9 | |||
| c72fd3eb5a | |||
| b0435f9885 | |||
| 2341bda621 | |||
| 4634cc09d0 | |||
| d4df1e01df | |||
| ed31fdc0df | |||
| 5412bf0ba3 | |||
| 8a9d5e7c4d | |||
| 01956ee690 | |||
| e46e74ddbb | |||
| 63d65af41b | |||
| 8937f105ea | |||
| 1584b8fb2f | |||
| 2301fb2122 | |||
| 4aa6aa9812 | |||
| a53d67a35a | |||
| 3259984d1c | |||
| 5e3ed4071b | |||
| c090617afd | |||
| 417bcda68c | |||
| 86d1473a6a |
@@ -33,6 +33,14 @@ COPY migrations/ ./migrations/
|
||||
# Copy policy files (YAML rules)
|
||||
COPY policies/ ./policies/
|
||||
|
||||
# Copy Compliance Execution Graph data (file-backed: Registry join-key copy + accepted control
|
||||
# mappings + evidence requirements) consumed by GET /sdk/v1/compliance/obligation-status.
|
||||
# data/obligations/obligation_join_keys.json is a synced copy of the repo-root Registry contract
|
||||
# (the Obligation Registry owns the canonical file) — re-sync it when the Registry grows.
|
||||
COPY data/control_mappings/ ./data/control_mappings/
|
||||
COPY data/evidence_requirements/ ./data/evidence_requirements/
|
||||
COPY data/obligations/ ./data/obligations/
|
||||
|
||||
# Create non-root user
|
||||
RUN adduser -D -u 1000 appuser
|
||||
USER appuser
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Control-Mapping: CRA Annex I -> NIST SP 800-53 Rev. 5. Eine Zeile = ein Mapping (Schema: ControlMapping).
|
||||
// Reviewt 2026-06-25 (benjamin): 3 accepted, mapping_type=primary_implementation (kanonische Primaer-Control je Anforderung).
|
||||
// Heimat der OWASP-Rejects (2)(e)/(2)(l)/(2)(i): dort war OWASP nicht der Zielstandard ("Mapping ueber NIST/BSI erforderlich").
|
||||
// related-Controls (SC-3(3), RA-5, AC-6, SI-16, ...) folgen separat als mapping_type=supports — hier nur der kanonische Einstieg.
|
||||
// obligation_id (Registry-Handoff #4 adoptiert, #6 auf CORE re-pointet 2026-06-26): SI-7->software_integrity_protection (CORE (2)(f)), SI-2->provide_security_updates, CM-7->attack_surface_minimization (CORE (2)(j)). Join exakt. Die domaenen-scoped IDs (signed_update_integrity, remote_access_attack_surface_min) bleiben gueltige Obligations und zeigen per specializes->CORE auf diese Ziele.
|
||||
{"source_norm": "CRA Annex I Part I (2)(e) — Integritaet", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "SI-7", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST SI-7 = Software, Firmware, and Information Integrity — kanonische Integritaetskontrolle (Signaturpruefung, Manipulationserkennung).", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Primaere Implementierung der CRA-Integritaetsanforderung; OWASP war hier kein passender Treffer. Related (spaeter, supports): SA-10, CM-14.", "version": "2026-06-25", "obligation_id": "software_integrity_protection"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "SI-2", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST SI-2 = Flaw Remediation — kanonische Update-/Patch-Kontrolle.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Primaere Implementierung der CRA-Update-Anforderung. Related (spaeter, supports): RA-5, CM-3, SA-11.", "version": "2026-06-25", "obligation_id": "provide_security_updates"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "CM-7", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST CM-7 = Least Functionality — Deaktivierung nicht benoetigter Ports/Dienste/Funktionen.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "CM-7 als Primaer-Control fuer Angriffsflaeche (nicht SC-3(3)). Related (spaeter, supports): SC-3(3), AC-6, SI-16.", "version": "2026-06-25", "obligation_id": "attack_surface_minimization"}
|
||||
@@ -2,13 +2,13 @@
|
||||
// Reviewt 2026-06-25 (benjamin): 7 accepted, 13 rejected. accepted = Audit-Wahrheit (Advisor nutzt acceptedOnly).
|
||||
// rejected bleiben als Audit-Spur ("warum verworfen"). KEIN confidence — kuratiert = fachliche Feststellung.
|
||||
// Architekturbeweis: CRA -> OWASP fuer AppSec/Auth/Crypto/Logging; Ops/Update/Attack-Surface/Integritaet -> NIST/BSI.
|
||||
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.3.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.2.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11 = Cryptography.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11 = Cryptography, richtiger Bereich fuer Verschluesselung.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.7.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11.7 = Key Management.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11.7 = Key Management fuer Verschluesselung/Schluesselverwaltung.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.3", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.4", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.3.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25", "obligation_id": "user_authentication_required"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25", "obligation_id": "user_authentication_required"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.2.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11 = Cryptography.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11 = Cryptography, richtiger Bereich fuer Verschluesselung.", "version": "2026-06-25", "obligation_id": "credential_confidentiality_protection"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.7.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11.7 = Key Management.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11.7 = Key Management fuer Verschluesselung/Schluesselverwaltung.", "version": "2026-06-25", "obligation_id": "auth_key_management"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.3", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.4", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, kein Auth — verworfen.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, Crypto gehoert zu V11 — verworfen.", "version": "2026-06-25"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.3.2", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, Crypto gehoert zu V11 — verworfen.", "version": "2026-06-25"}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Evidence-Requirements je NIST-SP-800-53-Control (Schema: EvidenceRequirement). Eine Zeile = eine geforderte Evidenz.
|
||||
// WICHTIG: evidence_type ist FRAMEWORK-AGNOSTISCH (geteilter Katalog config_export/test_report/repo_scan/sbom/...) —
|
||||
// dieselben Typen tragen CRA, NIST, ISO 27001, IEC 62443, BSI. (framework, control) ist nur der Verweis, nicht der Typ.
|
||||
// Stand 2026-06-25, Basis: die 3 accepted CRA->NIST primary_implementation-Mappings (SI-7 Integritaet, SI-2 Updates, CM-7 Angriffsflaeche).
|
||||
{"framework": "NIST SP 800-53", "control": "SI-7", "evidence_type": "sbom", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "SBOM weist die Integritaet/Herkunft der Software-Bestandteile nach (bekannte, unmanipulierte Komponenten).", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "SI-7", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Secure-Boot-/Code-Signing-Konfiguration als Nachweis der Integritaetspruefung.", "version": "2026-06-25"}
|
||||
{"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": "per_release", "required": true, "rationale": "Angriffsflaechen-Scan (offene Ports/Dienste) als Nachweis tatsaechlich minimierter Angriffsflaeche.", "version": "2026-06-25"}
|
||||
@@ -0,0 +1,846 @@
|
||||
{
|
||||
"schema_version": "obligation_join_keys_v1",
|
||||
"contract": "obligation_id ist der stabile Join-Key. Legal Knowledge Graph haengt citation_spans an obligation_id; Compliance Execution Graph mappt control_mapping.source_norm -> obligation_id. Interim-Bruecke = citation_units. obligation_id NIE neu vergeben (re-link).",
|
||||
"count": 95,
|
||||
"obligation_ids": [
|
||||
{
|
||||
"obligation_id": "sbom_creation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_dependency_coverage",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 3(36) i.V.m. Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_format_standard",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_maintenance_update",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_completeness_verification",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_tooling_automation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_access_provision",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_authority_provision",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31 / Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_confidentiality",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31(4)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_supply_chain_contracts",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_technical_documentation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31 i.V.m. Annex VII"
|
||||
],
|
||||
"source_role": "EVIDENCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_identification_inventory",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_assessment_prioritization",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_remediation_patching",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (2) & (8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_handling_process",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Article 13(8) & Annex VII"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "coordinated_vulnerability_disclosure",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (5)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "exploited_vuln_reporting_authorities",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Article 14 & Article 16"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_info_dissemination_users",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (4) & (6)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "attack_surface_minimization",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(j)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "software_integrity_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "user_authentication_required",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "authentication_policy_documented",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "auth_exceptions_documented",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "mfa_required",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "step_up_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "privileged_op_reauth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "strong_crypto_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "credential_lifecycle_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "credential_confidentiality_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "password_policy",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "no_default_credentials",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "account_lockout_failed_attempts",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "server_side_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "session_binding_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reauth_after_inactivity",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "token_validation_lifecycle",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "mutual_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "revocation_check",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "encrypted_auth_channel",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "tls_certificate_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "service_to_service_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "auth_key_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "biometric_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "federated_auth_assertions",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "separate_authn_authz",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "supplier_access_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "personal_admin_accounts",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "firmware_software_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "event_logging_security_events",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "access_control_event_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "audit_trail_admin_actions",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_integrity_immutability",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_access_control_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_retention_archival",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "centralized_log_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_monitoring_alerting",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_data_minimization_privacy",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_format_standardization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_timestamp_synchronization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_availability_resilience",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_thread_safety_correctness",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_library_supply_chain",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_config_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_governance_roles",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "incident_response_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_transmission_security",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "network_traffic_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_control_least_privilege",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_confidentiality_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(b)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_session_management",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_mfa",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_encryption",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reject_insecure_remote_protocols",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_logging_audit",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(g)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_user_validation_ot",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_training",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_architecture_design",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_attack_surface_min",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_vuln_patch_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_threat_detection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_maintenance_governance",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "temporary_remote_access_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_data_export_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "component_remote_interface_security",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_fallback_concept",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "provide_security_updates",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)",
|
||||
"Art. 13"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "support_period_maintenance",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 13(8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "signed_update_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "trusted_update_source",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_testing_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_rollback",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "automatic_updates_optout",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_risk_assessment",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "secure_modification_control",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
|
||||
)
|
||||
|
||||
// ComplianceGraphHandlers serves the read-only Compliance Execution Graph
|
||||
// (Regulation -> Obligation -> Control -> Evidence) over the file-backed bridge artifacts.
|
||||
// It is intentionally SEPARATE from the DB-backed ObligationsHandlers: this is the curated
|
||||
// cross-session graph (Registry join keys + accepted control mappings + evidence requirements),
|
||||
// loaded once at startup. Fail-closed: if the graph could not load, every request answers 503.
|
||||
type ComplianceGraphHandlers struct {
|
||||
joins *ucca.ObligationJoinKeys
|
||||
mappings *ucca.ControlMappingSet
|
||||
evidence *ucca.EvidenceRequirementSet
|
||||
loadErr error
|
||||
}
|
||||
|
||||
// NewComplianceGraphHandlers loads the graph once. Construction never fails; a load error is
|
||||
// retained and surfaced as 503 per request (matches the codebase's load-warn-continue startup).
|
||||
func NewComplianceGraphHandlers() *ComplianceGraphHandlers {
|
||||
joins, mappings, evidence, err := ucca.LoadComplianceGraph()
|
||||
return &ComplianceGraphHandlers{joins: joins, mappings: mappings, evidence: evidence, loadErr: err}
|
||||
}
|
||||
|
||||
// LoadError exposes a startup load failure so the wiring can log a warning.
|
||||
func (h *ComplianceGraphHandlers) LoadError() error { return h.loadErr }
|
||||
|
||||
// RegisterRoutes mounts the compliance-graph routes under /compliance.
|
||||
func (h *ComplianceGraphHandlers) RegisterRoutes(r *gin.RouterGroup) {
|
||||
g := r.Group("/compliance")
|
||||
g.GET("/obligation-status", h.ObligationStatus)
|
||||
}
|
||||
|
||||
type cgControlDTO struct {
|
||||
Framework string `json:"framework"`
|
||||
Control string `json:"control"`
|
||||
MappingType string `json:"mapping_type"`
|
||||
EvidenceRequired []string `json:"evidence_required"`
|
||||
EvidenceStatus string `json:"evidence_status"` // missing | partial | present | none_required
|
||||
}
|
||||
|
||||
type cgStatusResponse struct {
|
||||
ObligationID string `json:"obligation_id"`
|
||||
OverallStatus string `json:"overall_status"` // unknown_obligation | unmapped | not_assessed | open | met
|
||||
LegalBasis []string `json:"legal_basis,omitempty"`
|
||||
CitationSpans string `json:"citation_spans"` // "pending" until the Legal-KG attaches spans
|
||||
Controls []cgControlDTO `json:"controls"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// ObligationStatus answers GET /sdk/v1/compliance/obligation-status?obligation_id=...
|
||||
//
|
||||
// It NEVER asserts fulfillment automatically. With no evidence collection wired (MVP), a mapped
|
||||
// obligation is "not_assessed" and every required evidence is "missing" — the honest picture is
|
||||
// "required vs present evidence", not "a document exists". Fail-closed otherwise:
|
||||
// - no obligation_id -> 400
|
||||
// - graph not loaded -> 503
|
||||
// - id not in the Registry -> 200 overall_status=unknown_obligation
|
||||
// - mapped but no control yet -> 200 overall_status=unmapped
|
||||
func (h *ComplianceGraphHandlers) ObligationStatus(c *gin.Context) {
|
||||
if h.loadErr != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "compliance graph unavailable", "detail": h.loadErr.Error()})
|
||||
return
|
||||
}
|
||||
obID := strings.TrimSpace(c.Query("obligation_id"))
|
||||
if obID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "obligation_id query parameter required"})
|
||||
return
|
||||
}
|
||||
resp := cgStatusResponse{ObligationID: obID, CitationSpans: "pending", Controls: []cgControlDTO{}}
|
||||
|
||||
if h.joins.FindObligation(obID) == nil {
|
||||
resp.OverallStatus = "unknown_obligation"
|
||||
resp.Note = "obligation_id not in the Registry join-key contract"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// MVP: hasEvidence=nil -> no collection wired -> all required evidence counts as missing.
|
||||
st := ucca.AssessObligationStatus(h.joins, h.mappings, h.evidence, obID, nil)
|
||||
resp.LegalBasis = st.LegalBasis
|
||||
|
||||
if len(st.Controls) == 0 {
|
||||
resp.OverallStatus = "unmapped"
|
||||
resp.Note = "no accepted control maps to this obligation yet"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cs := range st.Controls {
|
||||
types := make([]string, 0, len(cs.RequiredEvidence))
|
||||
for _, e := range cs.RequiredEvidence {
|
||||
types = append(types, e.EvidenceType)
|
||||
}
|
||||
resp.Controls = append(resp.Controls, cgControlDTO{
|
||||
Framework: cs.Framework,
|
||||
Control: cs.Control,
|
||||
MappingType: cs.MappingType,
|
||||
EvidenceRequired: types,
|
||||
EvidenceStatus: cgEvidenceStatus(len(cs.RequiredEvidence), len(cs.MissingEvidence)),
|
||||
})
|
||||
}
|
||||
// No fulfillment claim without real evidence collection.
|
||||
resp.OverallStatus = "not_assessed"
|
||||
resp.Note = "evidence collection not wired (MVP) — fulfillment not asserted"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func cgEvidenceStatus(required, missing int) string {
|
||||
switch {
|
||||
case required == 0:
|
||||
return "none_required"
|
||||
case missing == 0:
|
||||
return "present"
|
||||
case missing == required:
|
||||
return "missing"
|
||||
default:
|
||||
return "partial"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func newComplianceGraphTestRouter(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := NewComplianceGraphHandlers()
|
||||
if err := h.LoadError(); err != nil {
|
||||
t.Fatalf("compliance graph failed to load (candidate paths): %v", err)
|
||||
}
|
||||
r := gin.New()
|
||||
h.RegisterRoutes(r.Group("/sdk/v1"))
|
||||
return r
|
||||
}
|
||||
|
||||
func getObligationStatus(t *testing.T, r *gin.Engine, query string) (int, cgStatusResponse) {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/sdk/v1/compliance/obligation-status"+query, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
var resp cgStatusResponse
|
||||
if w.Code == http.StatusOK {
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode body %q: %v", w.Body.String(), err)
|
||||
}
|
||||
}
|
||||
return w.Code, resp
|
||||
}
|
||||
|
||||
func TestObligationStatus(t *testing.T) {
|
||||
r := newComplianceGraphTestRouter(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
wantHTTP int
|
||||
wantOverall string
|
||||
wantControls bool // expect >=1 control
|
||||
}{
|
||||
{"missing param -> 400", "", http.StatusBadRequest, "", false},
|
||||
{"unknown id -> unknown_obligation", "?obligation_id=does_not_exist", http.StatusOK, "unknown_obligation", false},
|
||||
{"mapped (OWASP V6) -> not_assessed", "?obligation_id=user_authentication_required", http.StatusOK, "not_assessed", true},
|
||||
{"NIST adopted (SI-2) -> not_assessed", "?obligation_id=provide_security_updates", http.StatusOK, "not_assessed", true},
|
||||
{"CORE attack_surface_minimization -> CM-7", "?obligation_id=attack_surface_minimization", http.StatusOK, "not_assessed", true},
|
||||
{"CORE software_integrity_protection -> SI-7", "?obligation_id=software_integrity_protection", http.StatusOK, "not_assessed", true},
|
||||
{"in registry, no control -> unmapped", "?obligation_id=sbom_creation", http.StatusOK, "unmapped", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
code, resp := getObligationStatus(t, r, tt.query)
|
||||
if code != tt.wantHTTP {
|
||||
t.Fatalf("http %d, want %d", code, tt.wantHTTP)
|
||||
}
|
||||
if tt.wantHTTP != http.StatusOK {
|
||||
return
|
||||
}
|
||||
if resp.OverallStatus != tt.wantOverall {
|
||||
t.Errorf("overall_status=%q, want %q", resp.OverallStatus, tt.wantOverall)
|
||||
}
|
||||
if tt.wantControls && len(resp.Controls) == 0 {
|
||||
t.Error("expected >=1 control")
|
||||
}
|
||||
if !tt.wantControls && len(resp.Controls) != 0 {
|
||||
t.Errorf("expected 0 controls, got %d", len(resp.Controls))
|
||||
}
|
||||
if resp.CitationSpans != "pending" {
|
||||
t.Errorf("citation_spans=%q, want pending", resp.CitationSpans)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The MVP must NEVER auto-assert fulfillment: with no evidence collection wired, every required
|
||||
// evidence is "missing" and the overall status stays "not_assessed".
|
||||
func TestObligationStatus_NoFulfillmentClaim(t *testing.T) {
|
||||
r := newComplianceGraphTestRouter(t)
|
||||
code, resp := getObligationStatus(t, r, "?obligation_id=user_authentication_required")
|
||||
if code != http.StatusOK {
|
||||
t.Fatalf("http %d", code)
|
||||
}
|
||||
if resp.OverallStatus == "met" || resp.OverallStatus == "erfuellt" {
|
||||
t.Fatalf("MVP must not assert fulfillment, got overall_status=%q", resp.OverallStatus)
|
||||
}
|
||||
for _, ctl := range resp.Controls {
|
||||
if len(ctl.EvidenceRequired) > 0 && ctl.EvidenceStatus != "missing" {
|
||||
t.Errorf("control %s/%s evidence_status=%q, want missing (no collection wired)", ctl.Framework, ctl.Control, ctl.EvidenceStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -153,6 +153,12 @@ func buildRouter(cfg *config.Config, pool *pgxpool.Pool) *gin.Engine {
|
||||
ragHandlers := handlers.NewRAGHandlers(corpusVersionStore)
|
||||
obligationsHandlers := handlers.NewObligationsHandlersWithStore(obligationsStore)
|
||||
|
||||
// Compliance Execution Graph (file-backed: Registry join keys + accepted control mappings + evidence)
|
||||
complianceGraphHandlers := handlers.NewComplianceGraphHandlers()
|
||||
if err := complianceGraphHandlers.LoadError(); err != nil {
|
||||
log.Printf("WARNING: compliance graph not loaded (obligation-status -> 503): %v", err)
|
||||
}
|
||||
|
||||
// Regulatory News
|
||||
allV2Regs, err := ucca.LoadAllV2Regulations()
|
||||
if err != nil {
|
||||
@@ -201,7 +207,8 @@ func buildRouter(cfg *config.Config, pool *pgxpool.Pool) *gin.Engine {
|
||||
uccaHandlers, escalationHandlers, obligationsHandlers, ragHandlers,
|
||||
roadmapHandlers, workshopHandlers, portfolioHandlers,
|
||||
academyHandlers, trainingHandlers, whistleblowerHandlers, iaceHandler,
|
||||
gapHandler, maximizerHandlers, regulatoryNewsHandlers, useCaseHandler)
|
||||
gapHandler, maximizerHandlers, regulatoryNewsHandlers, useCaseHandler,
|
||||
complianceGraphHandlers)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ func registerRoutes(
|
||||
maximizerHandlers *handlers.MaximizerHandlers,
|
||||
regulatoryNewsHandlers *handlers.RegulatoryNewsHandlers,
|
||||
useCaseHandler *handlers.UseCaseHandler,
|
||||
complianceGraphHandlers *handlers.ComplianceGraphHandlers,
|
||||
) {
|
||||
v1 := router.Group("/sdk/v1")
|
||||
{
|
||||
@@ -54,6 +55,7 @@ func registerRoutes(
|
||||
registerMaximizerRoutes(v1, maximizerHandlers)
|
||||
registerUseCaseRoutes(v1, useCaseHandler)
|
||||
v1.GET("/regulatory-news", regulatoryNewsHandlers.GetNews)
|
||||
complianceGraphHandlers.RegisterRoutes(v1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// graphCallerRel resolves a path relative to THIS source file (build-time location), so the
|
||||
// graph data is findable under `go test` (cwd = package dir) regardless of working directory.
|
||||
// In a built container the source is gone, so cwd-relative candidates carry the load instead.
|
||||
func graphCallerRel(rel string) string {
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(filepath.Dir(file), rel)
|
||||
}
|
||||
|
||||
// firstExisting returns the first candidate path that exists with the requested kind (dir vs
|
||||
// file). Empty candidates (e.g. unset env overrides) are skipped.
|
||||
func firstExisting(candidates []string, wantDir bool) string {
|
||||
for _, p := range candidates {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(p)
|
||||
if err != nil || info.IsDir() != wantDir {
|
||||
continue
|
||||
}
|
||||
return p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// LoadComplianceGraph loads the file-backed Compliance Execution Graph: the Registry join-key
|
||||
// contract (obligations/obligation_join_keys.json — owned by the Obligation session) + our
|
||||
// curated, accepted control mappings + evidence requirements. Locations are resolved across
|
||||
// three layouts: dev (cwd = ai-compliance-sdk/, canonical contract at ../obligations), container
|
||||
// (WORKDIR /app, data/ copied in incl. a synced data/obligations/ copy) and `go test`
|
||||
// (cwd = package dir, via graphCallerRel). Fail-closed: a missing/invalid source returns an
|
||||
// error so the handler serves 503 — never a half-built graph.
|
||||
//
|
||||
// NOTE: data/obligations/obligation_join_keys.json is a SYNCED COPY of the repo-root contract
|
||||
// (the canonical owner is the Obligation session). Re-sync it when the Registry grows; dev/test
|
||||
// prefer the canonical repo-root path, only the container falls back to the copy.
|
||||
func LoadComplianceGraph() (*ObligationJoinKeys, *ControlMappingSet, *EvidenceRequirementSet, error) {
|
||||
joinPath := firstExisting([]string{
|
||||
os.Getenv("BP_OBLIGATION_JOIN_KEYS"),
|
||||
"../obligations/obligation_join_keys.json",
|
||||
graphCallerRel("../../../obligations/obligation_join_keys.json"),
|
||||
"data/obligations/obligation_join_keys.json",
|
||||
graphCallerRel("../../data/obligations/obligation_join_keys.json"),
|
||||
}, false)
|
||||
if joinPath == "" {
|
||||
return nil, nil, nil, fmt.Errorf("obligation_join_keys.json not found in any candidate path")
|
||||
}
|
||||
mapDir := firstExisting([]string{
|
||||
os.Getenv("BP_CONTROL_MAPPINGS_DIR"),
|
||||
"data/control_mappings",
|
||||
graphCallerRel("../../data/control_mappings"),
|
||||
}, true)
|
||||
if mapDir == "" {
|
||||
return nil, nil, nil, fmt.Errorf("control_mappings dir not found in any candidate path")
|
||||
}
|
||||
evDir := firstExisting([]string{
|
||||
os.Getenv("BP_EVIDENCE_DIR"),
|
||||
"data/evidence_requirements",
|
||||
graphCallerRel("../../data/evidence_requirements"),
|
||||
}, true)
|
||||
if evDir == "" {
|
||||
return nil, nil, nil, fmt.Errorf("evidence_requirements dir not found in any candidate path")
|
||||
}
|
||||
|
||||
joins, err := LoadObligationJoinKeys(joinPath)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load join keys (%s): %w", joinPath, err)
|
||||
}
|
||||
mappings, err := LoadControlMappings(mapDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load control mappings (%s): %w", mapDir, err)
|
||||
}
|
||||
evidence, err := LoadEvidenceRequirements(evDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load evidence (%s): %w", evDir, err)
|
||||
}
|
||||
return joins, mappings, evidence, nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package ucca
|
||||
|
||||
// ObligationStatus is the Advisor's vertical slice over the compliance graph for ONE legal
|
||||
// obligation: which accepted controls satisfy it, what evidence they require, what's missing,
|
||||
// and the resulting status. The point is "the required evidence is (not) present", not "a
|
||||
// document exists". citation_spans is pending until the Legal-Knowledge-Graph session attaches
|
||||
// them to the obligation (the upper half of the bridge).
|
||||
type ObligationStatus struct {
|
||||
ObligationID string `json:"obligation_id"`
|
||||
LegalBasis []string `json:"legal_basis"` // the obligation's citation_units
|
||||
Status string `json:"status"` // erfuellt | offen | unklar
|
||||
Controls []ObligationControlStatus `json:"controls"`
|
||||
CitationSpans string `json:"citation_spans"` // "pending" until the registry fills them
|
||||
}
|
||||
|
||||
// ObligationControlStatus is one control under an obligation with its evidence picture.
|
||||
type ObligationControlStatus struct {
|
||||
Framework string `json:"framework"`
|
||||
Control string `json:"control"`
|
||||
MappingType string `json:"mapping_type"`
|
||||
RequiredEvidence []EvidenceRequirement `json:"required_evidence"`
|
||||
MissingEvidence []EvidenceRequirement `json:"missing_evidence"`
|
||||
}
|
||||
|
||||
// AssessObligationStatus traverses obligation_id -> (citation_unit) -> accepted Controls ->
|
||||
// required Evidence -> Status. hasEvidence reports whether a given (framework, control,
|
||||
// evidence_type) is already collected; pass nil in the MVP (no collection yet) -> everything
|
||||
// required is missing and the status is "offen". Unknown or unmapped obligation -> "unklar".
|
||||
func AssessObligationStatus(joins *ObligationJoinKeys, mappings *ControlMappingSet, evidence *EvidenceRequirementSet, obligationID string, hasEvidence func(framework, control, evidenceType string) bool) ObligationStatus {
|
||||
ob := joins.FindObligation(obligationID)
|
||||
if ob == nil {
|
||||
return ObligationStatus{ObligationID: obligationID, Status: "unklar", CitationSpans: "pending"}
|
||||
}
|
||||
st := ObligationStatus{
|
||||
ObligationID: obligationID,
|
||||
LegalBasis: ob.CitationUnits,
|
||||
CitationSpans: "pending",
|
||||
Controls: []ObligationControlStatus{},
|
||||
}
|
||||
ctrls := AcceptedControlsForObligation(*ob, mappings)
|
||||
if len(ctrls) == 0 {
|
||||
st.Status = "unklar" // no accepted control reaches it — we cannot assess
|
||||
return st
|
||||
}
|
||||
anyMissing := false
|
||||
for _, m := range ctrls {
|
||||
req := evidence.RequiredFor(m.TargetFramework, m.TargetControl)
|
||||
missing := make([]EvidenceRequirement, 0, len(req))
|
||||
for _, e := range req {
|
||||
if hasEvidence == nil || !hasEvidence(e.Framework, e.Control, e.EvidenceType) {
|
||||
missing = append(missing, e)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
anyMissing = true
|
||||
}
|
||||
st.Controls = append(st.Controls, ObligationControlStatus{
|
||||
Framework: m.TargetFramework,
|
||||
Control: m.TargetControl,
|
||||
MappingType: m.MappingType,
|
||||
RequiredEvidence: req,
|
||||
MissingEvidence: missing,
|
||||
})
|
||||
}
|
||||
if anyMissing {
|
||||
st.Status = "offen"
|
||||
} else {
|
||||
st.Status = "erfuellt"
|
||||
}
|
||||
return st
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package ucca
|
||||
|
||||
import "testing"
|
||||
|
||||
func loadGraph(t *testing.T) (*ObligationJoinKeys, *ControlMappingSet, *EvidenceRequirementSet) {
|
||||
t.Helper()
|
||||
joins, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
|
||||
if err != nil {
|
||||
t.Fatalf("join keys: %v", err)
|
||||
}
|
||||
maps, err := LoadControlMappings("../../data/control_mappings")
|
||||
if err != nil {
|
||||
t.Fatalf("mappings: %v", err)
|
||||
}
|
||||
ev, err := LoadEvidenceRequirements("../../data/evidence_requirements")
|
||||
if err != nil {
|
||||
t.Fatalf("evidence: %v", err)
|
||||
}
|
||||
return joins, maps, ev
|
||||
}
|
||||
|
||||
func TestAssessObligationStatus(t *testing.T) {
|
||||
joins, maps, ev := loadGraph(t)
|
||||
|
||||
// covered obligation, no evidence collected yet (MVP) -> offen
|
||||
st := AssessObligationStatus(joins, maps, ev, "user_authentication_required", nil)
|
||||
if st.Status != "offen" {
|
||||
t.Errorf("want offen, got %q", st.Status)
|
||||
}
|
||||
if len(st.Controls) == 0 {
|
||||
t.Fatal("expected controls for a covered obligation")
|
||||
}
|
||||
for _, c := range st.Controls {
|
||||
if len(c.MissingEvidence) != len(c.RequiredEvidence) {
|
||||
t.Error("MVP: all required evidence should be missing")
|
||||
}
|
||||
}
|
||||
t.Logf("DURCHSTICH user_authentication_required: status=%s legal_basis=%v citation_spans=%s",
|
||||
st.Status, st.LegalBasis, st.CitationSpans)
|
||||
for _, c := range st.Controls {
|
||||
t.Logf(" %s %s (%s): %d required evidence, %d missing", c.Framework, c.Control, c.MappingType, len(c.RequiredEvidence), len(c.MissingEvidence))
|
||||
}
|
||||
|
||||
// all evidence present -> erfuellt
|
||||
st2 := AssessObligationStatus(joins, maps, ev, "user_authentication_required", func(f, c, et string) bool { return true })
|
||||
if st2.Status != "erfuellt" {
|
||||
t.Errorf("want erfuellt with all evidence present, got %q", st2.Status)
|
||||
}
|
||||
|
||||
// uncovered obligation (no accepted control reaches it) -> unklar
|
||||
if st3 := AssessObligationStatus(joins, maps, ev, "sbom_creation", nil); st3.Status != "unklar" {
|
||||
t.Errorf("uncovered sbom_creation: want unklar, got %q", st3.Status)
|
||||
}
|
||||
|
||||
// unknown obligation_id -> unklar
|
||||
if st4 := AssessObligationStatus(joins, maps, ev, "does_not_exist", nil); st4.Status != "unklar" {
|
||||
t.Errorf("unknown obligation: want unklar, got %q", st4.Status)
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,14 @@ import (
|
||||
// professional statement, not an AI guess. The retriever's score lives only in the rationale
|
||||
// of a candidate, never as structured truth.
|
||||
type ControlMapping struct {
|
||||
SourceNorm string `json:"source_norm"` // e.g. "CRA Annex I Part I (2)(c)"
|
||||
SourceRole string `json:"source_role"` // source_role of the norm (operational_requirement, ...)
|
||||
TargetFramework string `json:"target_framework"` // e.g. "OWASP ASVS"
|
||||
TargetControl string `json:"target_control"` // e.g. "V6.3.1"
|
||||
MappingType string `json:"mapping_type"` // supports | partially_supports | implements | related | contradicts
|
||||
MappingStatus string `json:"mapping_status"` // candidate | accepted | rejected | superseded
|
||||
Provenance string `json:"provenance"` // retriever_candidate | human_curated | rule_based
|
||||
SourceNorm string `json:"source_norm"` // e.g. "CRA Annex I Part I (2)(c)"
|
||||
SourceRole string `json:"source_role"` // source_role of the norm (operational_requirement, ...)
|
||||
TargetFramework string `json:"target_framework"` // e.g. "OWASP ASVS"
|
||||
TargetControl string `json:"target_control"` // e.g. "V6.3.1"
|
||||
MappingType string `json:"mapping_type"` // primary_implementation | implements | supports | partially_supports | related | contradicts
|
||||
MappingStatus string `json:"mapping_status"` // candidate | accepted | rejected | superseded
|
||||
Provenance string `json:"provenance"` // retriever_candidate | human_curated | rule_based
|
||||
ObligationID string `json:"obligation_id,omitempty"` // stable cross-session join key (Obligation Registry); empty until adopted, citation_unit is the interim bridge
|
||||
Rationale string `json:"rationale"`
|
||||
ReviewedBy string `json:"reviewed_by,omitempty"` // who decided (human or rule id)
|
||||
ReviewDate string `json:"review_date,omitempty"` // YYYY-MM-DD
|
||||
@@ -35,7 +36,7 @@ type ControlMapping struct {
|
||||
|
||||
// Allowed enum values — the deterministic "rule" layer that keeps the curated store clean.
|
||||
var (
|
||||
mappingTypeValues = map[string]bool{"supports": true, "partially_supports": true, "implements": true, "related": true, "contradicts": true}
|
||||
mappingTypeValues = map[string]bool{"primary_implementation": true, "implements": true, "supports": true, "partially_supports": true, "related": true, "contradicts": true}
|
||||
mappingStatusValues = map[string]bool{"candidate": true, "accepted": true, "rejected": true, "superseded": true}
|
||||
provenanceValues = map[string]bool{"retriever_candidate": true, "human_curated": true, "rule_based": true}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ObligationKey is one entry of the Obligation Registry's cross-session contract
|
||||
// (obligations/obligation_join_keys.json). obligation_id is the STABLE join key — assigned
|
||||
// only by the Registry, never minted here. citation_units are the interim bridge until our
|
||||
// ControlMapping adopts obligation_id directly.
|
||||
type ObligationKey struct {
|
||||
ObligationID string `json:"obligation_id"`
|
||||
Regulation string `json:"regulation"`
|
||||
Family string `json:"family"`
|
||||
Tier string `json:"tier"`
|
||||
CitationUnits []string `json:"citation_units"`
|
||||
SourceRole string `json:"source_role"`
|
||||
}
|
||||
|
||||
// ObligationJoinKeys is the loaded contract + a citation-unit index for the interim join.
|
||||
type ObligationJoinKeys struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Count int `json:"count"`
|
||||
ObligationIDs []ObligationKey `json:"obligation_ids"`
|
||||
byCitationKey map[string][]string
|
||||
}
|
||||
|
||||
var citationRefRe = regexp.MustCompile(`\(([0-9a-zA-Z]+)\)`)
|
||||
|
||||
// citationUnitKey normalizes a CRA Annex I reference for the INTERIM citation_unit join, so
|
||||
// our "CRA Annex I Part I (2)(c)" and the Registry's "Annex I (2)(c)" collapse to the same
|
||||
// key ("i:2.c"). Interim only — superseded by the stable obligation_id once adopted.
|
||||
func citationUnitKey(cu string) string {
|
||||
low := strings.ToLower(cu)
|
||||
part := ""
|
||||
switch {
|
||||
case strings.Contains(low, "part ii"):
|
||||
part = "ii"
|
||||
case strings.Contains(low, "part i"), strings.Contains(low, "(2)"):
|
||||
part = "i" // CRA Annex I Part I = the (2)(x) essential requirements
|
||||
}
|
||||
var refs []string
|
||||
for _, m := range citationRefRe.FindAllStringSubmatch(cu, -1) {
|
||||
refs = append(refs, strings.ToLower(m[1]))
|
||||
}
|
||||
return part + ":" + strings.Join(refs, ".")
|
||||
}
|
||||
|
||||
// LoadObligationJoinKeys reads the Registry contract and indexes it by citation-unit key.
|
||||
func LoadObligationJoinKeys(path string) (*ObligationJoinKeys, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var o ObligationJoinKeys
|
||||
if err := json.Unmarshal(raw, &o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.byCitationKey = map[string][]string{}
|
||||
for _, ob := range o.ObligationIDs {
|
||||
for _, cu := range ob.CitationUnits {
|
||||
k := citationUnitKey(cu)
|
||||
o.byCitationKey[k] = append(o.byCitationKey[k], ob.ObligationID)
|
||||
}
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
// ObligationsForCitation returns the obligation_ids that join (interim) to a citation
|
||||
// reference such as a control_mapping.source_norm.
|
||||
func (o *ObligationJoinKeys) ObligationsForCitation(citationRef string) []string {
|
||||
return o.byCitationKey[citationUnitKey(citationRef)]
|
||||
}
|
||||
|
||||
// FindObligation returns the registry entry for an obligation_id (nil if unknown).
|
||||
func (o *ObligationJoinKeys) FindObligation(obligationID string) *ObligationKey {
|
||||
for i := range o.ObligationIDs {
|
||||
if o.ObligationIDs[i].ObligationID == obligationID {
|
||||
return &o.ObligationIDs[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mappingReaches reports whether a control mapping reaches an obligation — EXACT via the
|
||||
// adopted obligation_id (semantic, preferred), else via the interim citation_unit join (for
|
||||
// not-yet-adopted rows). Once obligation_id is set, the coarse citation_unit match is ignored:
|
||||
// that is how the semantic join replaces the structural one (e.g. V11.2.1 crypto no longer
|
||||
// rides (2)(d) into user_authentication_required — it goes to credential_confidentiality_protection).
|
||||
func mappingReaches(m ControlMapping, ob ObligationKey, citationKeys map[string]bool) bool {
|
||||
if m.ObligationID != "" {
|
||||
return m.ObligationID == ob.ObligationID
|
||||
}
|
||||
return citationKeys[citationUnitKey(m.SourceNorm)]
|
||||
}
|
||||
|
||||
// AcceptedControlsForObligation returns our accepted control mappings that reach an obligation
|
||||
// (deduped by target control), obligation_id-exact where adopted, citation_unit otherwise.
|
||||
func AcceptedControlsForObligation(ob ObligationKey, mappings *ControlMappingSet) []ControlMapping {
|
||||
keys := make(map[string]bool, len(ob.CitationUnits))
|
||||
for _, cu := range ob.CitationUnits {
|
||||
keys[citationUnitKey(cu)] = true
|
||||
}
|
||||
out := []ControlMapping{}
|
||||
seen := map[string]bool{}
|
||||
for _, m := range mappings.All {
|
||||
if !m.IsAccepted() || !mappingReaches(m, ob, keys) {
|
||||
continue
|
||||
}
|
||||
ck := m.TargetFramework + ":" + m.TargetControl
|
||||
if seen[ck] {
|
||||
continue
|
||||
}
|
||||
seen[ck] = true
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ObligationCoverage is one row of the cross-session coverage report.
|
||||
type ObligationCoverage struct {
|
||||
ObligationID string `json:"obligation_id"`
|
||||
Family string `json:"family"`
|
||||
Status string `json:"status"` // covered | mapped_rejected | uncovered
|
||||
AcceptedControls []string `json:"accepted_controls"`
|
||||
EvidenceCount int `json:"evidence_count"`
|
||||
}
|
||||
|
||||
// ComputeObligationCoverage joins the Registry obligations to our control mappings — exact via
|
||||
// obligation_id where adopted, else via the interim citation_unit join — and reports per
|
||||
// obligation: covered (>=1 accepted control reaches it), mapped_rejected (only rejected
|
||||
// mappings reach it), or uncovered. The signal back to the Obligation session.
|
||||
func ComputeObligationCoverage(joins *ObligationJoinKeys, mappings *ControlMappingSet, evidence *EvidenceRequirementSet) []ObligationCoverage {
|
||||
out := make([]ObligationCoverage, 0, len(joins.ObligationIDs))
|
||||
for _, ob := range joins.ObligationIDs {
|
||||
keys := make(map[string]bool, len(ob.CitationUnits))
|
||||
for _, cu := range ob.CitationUnits {
|
||||
keys[citationUnitKey(cu)] = true
|
||||
}
|
||||
cov := ObligationCoverage{ObligationID: ob.ObligationID, Family: ob.Family}
|
||||
seen := map[string]bool{}
|
||||
rejected := false
|
||||
for _, m := range mappings.All {
|
||||
if !mappingReaches(m, ob, keys) {
|
||||
continue
|
||||
}
|
||||
if m.IsAccepted() {
|
||||
ck := m.TargetFramework + ":" + m.TargetControl
|
||||
if !seen[ck] {
|
||||
seen[ck] = true
|
||||
cov.AcceptedControls = append(cov.AcceptedControls, ck)
|
||||
cov.EvidenceCount += len(evidence.RequiredFor(m.TargetFramework, m.TargetControl))
|
||||
}
|
||||
} else if m.MappingStatus == "rejected" {
|
||||
rejected = true
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(cov.AcceptedControls) > 0:
|
||||
cov.Status = "covered"
|
||||
case rejected:
|
||||
cov.Status = "mapped_rejected"
|
||||
default:
|
||||
cov.Status = "uncovered"
|
||||
}
|
||||
out = append(out, cov)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ucca
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCitationUnitKey_Join(t *testing.T) {
|
||||
// our source_norm and the registry citation_unit must collapse to the SAME key.
|
||||
if citationUnitKey("CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff") != citationUnitKey("Annex I (2)(c)") {
|
||||
t.Errorf("interim join broken: %q vs %q",
|
||||
citationUnitKey("CRA Annex I Part I (2)(c)"), citationUnitKey("Annex I (2)(c)"))
|
||||
}
|
||||
// Part II must NOT collide with Part I.
|
||||
if citationUnitKey("Annex I Part II (1)") == citationUnitKey("CRA Annex I Part I (2)(c)") {
|
||||
t.Error("Part II must not join to Part I")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadObligationJoinKeys(t *testing.T) {
|
||||
o, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if o.Count != len(o.ObligationIDs) {
|
||||
t.Errorf("count %d != len %d", o.Count, len(o.ObligationIDs))
|
||||
}
|
||||
if len(o.ObligationIDs) == 0 {
|
||||
t.Fatal("empty contract")
|
||||
}
|
||||
if got := o.ObligationsForCitation("CRA Annex I Part I (2)(c)"); len(got) == 0 {
|
||||
t.Error("expected an obligation joined to (2)(c)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestObligationCoverage_Report(t *testing.T) {
|
||||
joins, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
|
||||
if err != nil {
|
||||
t.Fatalf("join keys: %v", err)
|
||||
}
|
||||
maps, err := LoadControlMappings("../../data/control_mappings")
|
||||
if err != nil {
|
||||
t.Fatalf("mappings: %v", err)
|
||||
}
|
||||
ev, err := LoadEvidenceRequirements("../../data/evidence_requirements")
|
||||
if err != nil {
|
||||
t.Fatalf("evidence: %v", err)
|
||||
}
|
||||
cov := ComputeObligationCoverage(joins, maps, ev)
|
||||
if len(cov) == 0 {
|
||||
t.Fatal("no coverage computed")
|
||||
}
|
||||
byStatus := map[string]int{}
|
||||
for _, c := range cov {
|
||||
byStatus[c.Status]++
|
||||
}
|
||||
t.Logf("COVERAGE: %d Obligations | covered=%d mapped_rejected=%d uncovered=%d",
|
||||
len(cov), byStatus["covered"], byStatus["mapped_rejected"], byStatus["uncovered"])
|
||||
for _, c := range cov {
|
||||
if c.Status != "uncovered" {
|
||||
t.Logf(" %-15s %-36s controls=%v evidence=%d", c.Status, c.ObligationID, c.AcceptedControls, c.EvidenceCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
# Capability Model v1 — Objektarten & Beziehungstypen (Schema-Papier, NICHT materialisiert)
|
||||
|
||||
Status: **OFFEN / Entscheidung erforderlich (2026-06-26).** Dies ist Schritt **#5a** (Papier).
|
||||
Schritt **#5b** (Materialisierung: `capabilities.json`, Migration, Obligation→Capability-Links,
|
||||
Guidance-Mapping, Runtime) ist **GEGATED** auf die Annahme dieses Papiers. **Es wurde noch keine
|
||||
Zeile Daten verschoben.**
|
||||
|
||||
Baut auf [legal_obligation_layer_v1.md](legal_obligation_layer_v1.md),
|
||||
[obligation_registry_v1.md](obligation_registry_v1.md) und dem Cross-Domain-Review
|
||||
(`obligations/cross_domain_relationships.json`, Commit `ed31fdc0`).
|
||||
|
||||
---
|
||||
|
||||
## 0. Warum ein Papier statt `capabilities.json`
|
||||
|
||||
Die Plattform hat drei empirische Architektur-Sprünge gemacht:
|
||||
1. **Control ≠ Wissensobjekt** → Legal Obligation (sofort implementiert, datenbestätigt).
|
||||
2. **Procedure ist eigenständig** (implementiert: `cra_procedures.json`).
|
||||
3. **Capabilities tauchen domänenübergreifend wieder auf** (Cross-Domain-Review).
|
||||
|
||||
(1) und (2) waren breit datenbelegt → sofort umgesetzt. Bei (3) ist die **Objektart selbst noch
|
||||
nicht definiert.** Wir wissen NICHT genau, was eine Capability ist. Materialisieren wir jetzt,
|
||||
riskieren wir, in drei Wochen festzustellen: „Attack Surface war gar keine Capability" → Umbau.
|
||||
|
||||
---
|
||||
|
||||
## 1. Der Auslöser: die 8 „Capabilities" sind NICHT eine Objektart
|
||||
|
||||
Der Cross-Domain-Review fand 16 `SHARED_CAPABILITY`-Paare → 8 Cluster. Bei Inspektion zerfallen
|
||||
sie in **zwei verschiedene Objektarten**:
|
||||
|
||||
| Cluster (Opus-Benennung) | Art | Begründung |
|
||||
|---|---|---|
|
||||
| `mfa` | **Capability** | implementierbar als Funktion |
|
||||
| `session_management` | **Capability** | implementierbar |
|
||||
| `transport_encryption` (tls/mutual_tls/cert) | **Capability** | implementierbar (vom Klassifikator fein gesplittet → 1 Capability) |
|
||||
| `code_signing` | **Capability** | implementierbar |
|
||||
| `anomaly_detection` | **Capability** | implementierbar |
|
||||
| `access_control` | **Ziel** (schwach) | abstraktes Ziel, kein Baustein — eher OVERLAP (siehe Konsolidierung) |
|
||||
|
||||
Dazu die **zwei Gap-„Obligations" aus Handoff #4** (NIST SI-7/CM-7 waren breiter als jeder
|
||||
einzelne Treffer):
|
||||
|
||||
| Kandidat | Art | Begründung |
|
||||
|---|---|---|
|
||||
| `software_integrity_protection` (SI-7) | **Sicherheitsziel** | wird NICHT direkt gebaut; erreicht durch code_signing + hash_verification + secure_boot |
|
||||
| `attack_surface_minimization` (CM-7) | **Sicherheitsziel** | erreicht durch least_functionality + Port-Deaktivierung + Interface-Reduktion |
|
||||
|
||||
**Kernbeobachtung (User):** Es gibt **Typ 1 — technische Fähigkeiten** (implementierbar) und
|
||||
**Typ 2 — Sicherheitsziele** (nicht direkt implementierbar, durch mehrere Capabilities erreicht).
|
||||
Sie in eine `capabilities.json` zu werfen wäre der Fehler.
|
||||
|
||||
```
|
||||
Integrity Protection (Ziel) Access Protection (Ziel)
|
||||
↑ erreicht durch ↑ erreicht durch
|
||||
code_signing · hash_verification · mfa · session_management ·
|
||||
secure_boot (Capabilities) credential_storage (Capabilities)
|
||||
```
|
||||
|
||||
Das erklärt rückwirkend auch das **systematische Synth-Über-Tiering** (Auth 14→6, Remote 14→5):
|
||||
das LLM mischte ziel-nahe Obligations mit fähigkeits-nahen Mechanismen, weil die Modellsprache
|
||||
die Ebenen nicht trennte.
|
||||
|
||||
---
|
||||
|
||||
## 2. Kandidat-Objektarten
|
||||
|
||||
| Objektart | Definition | Diskriminator-Test |
|
||||
|---|---|---|
|
||||
| **Regulation** | Rechtsakt (CRA, NIS2, DSGVO, MaschVO) | „Ist es ein Gesetz/VO?" |
|
||||
| **Legal Obligation** | rechtlich verankerte Pflicht. **CORE** (abstrakt, oft = Sicherheitsziel) ⊇ **DOMAIN** (spezialisiert) — die CORE/DOMAIN-Achse existiert bereits (portability). | „Steht das so (sinngemäß) im Recht? Kann ein Prüfer FEHLT/ERFÜLLT sagen?" |
|
||||
| **Capability** *(NEU)* | implementierbare, **regulierungs-agnostische** technische Funktion, als Einheit baubar & testbar | „Kann ein Hersteller GENAU DAS bauen/konfigurieren?" → ja |
|
||||
| **Procedure** | wiederholbarer operativer Prozess, der eine Capability ausbringt/erhält (bereits modelliert) | „Ist es eine Tätigkeit/ein Ablauf?" |
|
||||
| **Control** | testbare Prüfanweisung | „Kann man es prüfen (pass/fail)?" |
|
||||
| **Evidence** | Nachweis-Artefakt (Audit-Log, SBOM, Release Notes) | „Ist es ein Beleg-Dokument/Datum?" |
|
||||
| **Guidance** *(quer)* | externe Empfehlung WIE (NIST/OWASP/ENISA/BSI). **Nicht-bindend.** | „Beschreibt es eine empfohlene Umsetzung, kein Primärrecht?" |
|
||||
|
||||
---
|
||||
|
||||
## 3. DER ZENTRALE KNACKPUNKT: Ist „Security Objective" eine eigene Klasse?
|
||||
|
||||
### Modell A — flach (Objektive = Obligations)
|
||||
```
|
||||
Regulation → Legal Obligation → Capability → Procedure → Control → Evidence
|
||||
```
|
||||
Sicherheitsziele sind einfach **CORE Legal Obligations**; domänen-scoped Pflichten sind DOMAIN-
|
||||
Obligations, die per `specializes` an die CORE hängen.
|
||||
|
||||
### Modell B — mit eigener Security-Objective-Klasse
|
||||
```
|
||||
Regulation → Legal Obligation → Security Objective → Capability → Procedure → Control → Evidence
|
||||
```
|
||||
|
||||
### Modell C — hybrid (Capability als einzige neue Klasse) ← **EMPFEHLUNG**
|
||||
```
|
||||
Regulation → Legal Obligation (CORE ⊇ DOMAIN) --realized_by--> Capability → Procedure → Control → Evidence
|
||||
▲ ▲
|
||||
└── specializes (DOMAIN→CORE) └── described_by ── Guidance (NIST/OWASP/…)
|
||||
```
|
||||
|
||||
**Empfehlung: Modell C.** Begründung aus den Daten:
|
||||
- Die „Sicherheitsziele" (`software_integrity_protection`, `attack_surface_minimization`, CIA,
|
||||
access-protection) **SIND im CRA bindende Pflichten** (Annex I (2)(a–m) ist Primärrecht). Ein
|
||||
Sicherheitsziel ist also eine **CORE Legal Obligation**, kein neuer Objekttyp.
|
||||
- Die **CORE/DOMAIN-Achse existiert schon** (portability_core ⊇ health/data_act). `attack_surface_
|
||||
minimization` (CORE) ⊇ `remote_access_attack_surface_min` (DOMAIN) ist exakt dasselbe Muster.
|
||||
→ keine neue Klasse, nur konsequente Nutzung des Vorhandenen.
|
||||
- **Genau EINE** wirklich neue Klasse (**Capability**) ist sparsam und niedrig-risiko.
|
||||
- Modell B verdoppelt die normative Ebene (Obligation vs Objective), die im CRA 1:1 zusammenfällt
|
||||
→ Klasse, die niemand sauber befüllt.
|
||||
|
||||
**Konsequenz für die #4-Gap:** `software_integrity_protection` + `attack_surface_minimization`
|
||||
werden als **CORE Legal Obligations** angelegt (nicht als Capabilities), und die domänen-scoped
|
||||
Treffer (`signed_update_integrity`, `remote_access_attack_surface_min`) `specializes` → CORE.
|
||||
NIST SI-7/CM-7 mappen dann `primary_implementation` auf die CORE.
|
||||
|
||||
**Offen für den User:** Modell C akzeptieren? Oder ist die regulierungs-AGNOSTISCHE Vereinheitlichung
|
||||
(eine „confidentiality" über CRA+NIS2+ISO) so wertvoll, dass „Security Objective" doch eine eigene
|
||||
Klasse verdient (Modell B)? Das ist die einzige wirklich offene Architekturentscheidung.
|
||||
|
||||
---
|
||||
|
||||
## 4. Beziehungstypen — das Modell ist ein GRAPH, keine flache Ebene
|
||||
|
||||
Der Review fand **vier distinkte Cross-Domain-Strukturrelationen** (nicht eine):
|
||||
`SUPPORTED_BY` 23 · `SHARED_CAPABILITY` 16 · `SHARED_EVIDENCE` 7 · `SHARED_PROCEDURE` 5 (+ 1 Merge).
|
||||
Das ist kein Baum. Vorgeschlagenes gerichtetes Kanten-Vokabular:
|
||||
|
||||
| Kante | von → nach | aus Review-Relation |
|
||||
|---|---|---|
|
||||
| `specializes` | DOMAIN-Obligation → CORE-Obligation | (SUPPORTED_BY, Spezialfall) |
|
||||
| `contributes_to` | Obligation → Obligation | (SUPPORTED_BY, Beitrag) |
|
||||
| `realized_by` | Obligation → Capability | (SHARED_CAPABILITY ⇒ 2 Obl. teilen 1 Capability) |
|
||||
| `deployed_via` | Capability → Procedure | (SHARED_PROCEDURE) |
|
||||
| `verified_by` | Procedure/Capability → Control | — |
|
||||
| `produces` | Procedure → Evidence | (SHARED_EVIDENCE ⇒ 2 Obl. teilen 1 Nachweis) |
|
||||
| `described_by` | Capability → Guidance | (guidance_basis) |
|
||||
| `same_as` | Obligation ↔ Obligation | (SAME_OBLIGATION, Merge) |
|
||||
|
||||
`SHARED_CAPABILITY`/`SHARED_EVIDENCE`/`SHARED_PROCEDURE` sind also **keine Obligation-Obligation-
|
||||
Kanten**, sondern Belege, dass zwei Obligations **denselben Knoten einer tieferen Ebene** teilen
|
||||
(Capability / Evidence / Procedure). Genau das ist der Mehrwert gegenüber „sieht ähnlich aus".
|
||||
|
||||
---
|
||||
|
||||
## 5. Die 8 offenen Fragen (Antwort + Tradeoff)
|
||||
|
||||
1. **Was ist eine Capability?** Eine implementierbare, regulierungs-agnostische technische Funktion,
|
||||
als Einheit baubar/konfigurierbar/testbar (MFA, TLS, Code Signing, Session-Mgmt, Anomaly-Detection).
|
||||
2. **Unterschied zur Obligation?** Obligation = rechtliche Pflicht (WAS das Recht verlangt, regulierungs-
|
||||
verankert, normativ). Capability = technisches Mittel (WIE man sie erfüllt, agnostisch). n:m.
|
||||
3. **Unterschied zum Security Objective?** Ziel = erwünschter Sicherheitszustand (CIA, attack-surface-min);
|
||||
Capability = Mittel dorthin. **Empfehlung (Modell C):** das Ziel ist eine CORE Obligation, kein
|
||||
eigener Typ → Unterschied reduziert sich auf Obligation(abstrakt) vs Capability(Mittel).
|
||||
4. **Wann Guidance?** Wenn es eine **nicht-bindende externe Empfehlung zur Umsetzung** ist (NIST AC-12,
|
||||
OWASP ASVS V6). Hängt an der **Capability** (meist) bzw. Procedure — NIE als `legal_basis` einer
|
||||
LEGAL_MINIMUM-Obligation (Primärrecht-Regel bleibt).
|
||||
5. **Wann Procedure?** Wenn es ein **wiederholbarer operativer Ablauf** ist, der eine Capability
|
||||
ausbringt/erhält (MFA konfigurieren, Schlüssel rotieren, Patch-Zyklus fahren).
|
||||
6. **Capability → mehrere Obligations?** **JA, belegt:** `mfa` erfüllt 6 Obligations (auth+remote),
|
||||
`code_signing` 2 (auth+updates). n:m.
|
||||
7. **Obligation → mehrere Capabilities?** **JA, belegt:** access-protection ← mfa + session_management
|
||||
+ credential_storage. n:m.
|
||||
8. **Wo hängen NIST/OWASP/ENISA/BSI?** Primär an der **Capability** (sie beschreiben deren Umsetzung),
|
||||
teils an Procedure. **Das erklärt, warum die über-getierten BP-Obligations `guidance_basis` trugen:
|
||||
sie waren in Wahrheit Capabilities.** Sauberer Sitz von `guidance_basis` = Capability.
|
||||
|
||||
---
|
||||
|
||||
## 6. Worked Examples (4 Domänen, echte IDs)
|
||||
|
||||
**Authentication** — `user_authentication_required` (Obl, CORE: access-protection)
|
||||
`--realized_by-->` { `mfa`, `session_management`, `credential_storage` } (Capabilities)
|
||||
`--described_by-->` NIST IA-2 / OWASP ASVS V6 (Guidance).
|
||||
|
||||
**Updates** — `provide_security_updates` (Obl, LEGAL_MINIMUM) `--realized_by-->`
|
||||
{ `code_signing` (= signed_update_integrity-Capability), `automatic_update_delivery`, `rollback` }
|
||||
— exakt die `capability_candidate`-Marker aus `cra_updates.json`.
|
||||
|
||||
**Remote Access** — CORE `attack_surface_minimization` (NEU, = CM-7-Ziel) `⊇ specializes ⊇`
|
||||
`remote_access_attack_surface_min` (DOMAIN) `--realized_by-->` { `least_functionality`, `port_disabling` }.
|
||||
|
||||
**SBOM** — Sonderfall: die SBOM-Familie ist im Cross-Review der **Evidence-/Procedure-Input** für
|
||||
`vuln_identification_inventory` (5× SUPPORTED_BY-Hub), weniger Capability. → bestätigt, dass nicht
|
||||
jede Domäne primär Capabilities beisteuert; manche liefern **Evidence**. Stützt den Graph-Charakter.
|
||||
|
||||
---
|
||||
|
||||
## 7. Entscheidung, die ich vom User brauche (vor #5b)
|
||||
|
||||
1. **Modell C** (Capability = einzige neue Klasse; Sicherheitsziele = CORE-Obligations) — akzeptiert?
|
||||
Oder Modell B (Security Objective als eigene Klasse für regulierungs-agnostische Vereinheitlichung)?
|
||||
2. **Kanten-Vokabular** aus §4 — so einfrieren?
|
||||
3. **`guidance_basis` wandert konzeptionell an die Capability** — einverstanden? (Bricht nichts sofort;
|
||||
die Obligations behalten den Verweis bis #5b.)
|
||||
4. Erst danach **#5b**: `capabilities.json` (capability_id, fulfills_obligations[] via `realized_by`,
|
||||
guidance_basis hochgezogen), die 2 CORE-Gap-Obligations, der Merge (`vuln_remediation_patching` ≈
|
||||
`provide_security_updates`), und die 2 Remote-Grenzfälle final tiern.
|
||||
|
||||
## 8. Bewusst NICHT in #5a (gegated)
|
||||
|
||||
Keine `capabilities.json`, keine Migration, kein Obligation-Rewrite, kein Guidance-Move, kein Runtime.
|
||||
Erst Modell-Annahme, dann Daten. „Erst das Schema, dann verschieben."
|
||||
@@ -0,0 +1,108 @@
|
||||
# Compliance Operating System — Meta Model v1.0 (FROZEN)
|
||||
|
||||
> **STATUS: EINGEFROREN (2026-06-26). ARCHITEKTUR-FREEZE IN KRAFT.**
|
||||
> Ab v1.0 dürfen neue Regulierungen das Modell **nicht mehr verändern** — sie müssen sich
|
||||
> **einfügen**. Das Modell wird nur wieder geöffnet, wenn eine Regulierung **nachweislich
|
||||
> scheitert** (eine Anforderung lässt sich ohne neue Objektklasse nicht abbilden).
|
||||
> Validiert gegen 5 Regulierungsarten: DSGVO · CRA · MaschVO · Data Act · NIS2.
|
||||
|
||||
Konsolidiert + friert ein: [legal_obligation_layer_v1.md](legal_obligation_layer_v1.md),
|
||||
[capability_model_v1.md](capability_model_v1.md) (Modell C), [meta_model_validation_v1.md](meta_model_validation_v1.md).
|
||||
Was hier eingefroren wird, ist **ausschließlich die Meta-Semantik** — NICHT die Registry, NICHT die
|
||||
Capabilities-Liste, NICHT die Procedures (diese wachsen als Daten weiter).
|
||||
|
||||
## 1. Objektklassen (6 + Guidance) — eingefroren
|
||||
|
||||
| Klasse | Was | Regulierungs-Bindung |
|
||||
|---|---|---|
|
||||
| **Regulation** | Rechtsakt | — |
|
||||
| **Legal Obligation** | rechtlich verankerte Pflicht; **CORE ⊇ DOMAIN** | regulierungs-anchored |
|
||||
| **Capability** | implementierbare technische Faehigkeit (OPTIONAL für eine Obligation) | **agnostisch** (n:m über Regulierungen) |
|
||||
| **Procedure** | wiederholbarer operativer Prozess | agnostisch |
|
||||
| **Control** | testbare Prüfanweisung | agnostisch |
|
||||
| **Evidence** | Nachweis-Artefakt | agnostisch |
|
||||
| **Guidance** *(quer)* | externe nicht-bindende Empfehlung (NIST/OWASP/ISO/BSI) — hängt an der **Capability** | agnostisch |
|
||||
|
||||
## 2. Die Kette + kanonisches Kanten-Vokabular — eingefroren
|
||||
|
||||
```
|
||||
Regulation
|
||||
↓ definiert
|
||||
Legal Obligation (CORE ⊇ DOMAIN)
|
||||
↓ realized_by (OPTIONAL — rein prozessuale/dokumentarische Obligations überspringen Capability)
|
||||
Capability
|
||||
↓ deployed_via (alias: operationalized_by)
|
||||
Procedure
|
||||
↓ verified_by
|
||||
Control
|
||||
↓ produces (alias: produces_evidence_for)
|
||||
Evidence
|
||||
→ Produktstatus
|
||||
```
|
||||
|
||||
Kanten (gerichtet, eingefroren):
|
||||
`specializes` (DOMAIN→CORE) · `realized_by` (Obligation→Capability) · `deployed_via` (Capability→Procedure) ·
|
||||
`verified_by` (Procedure/Capability→Control) · `produces` (Procedure→Evidence) · `described_by` (Capability→Guidance) ·
|
||||
`supports` / `depends_on` / `contributes_to` (Obligation↔Obligation) · `same_as` (Merge/Alias).
|
||||
**Das Modell ist ein GRAPH, kein Baum** (n:m an realized_by, supports, produces).
|
||||
|
||||
## 3. Attribute (KEINE Klassen) — eingefroren
|
||||
|
||||
`applicability` · `tier` (LEGAL_MINIMUM/BEST_PRACTICE) · `legal_basis` (Primärrecht) ·
|
||||
`guidance_basis` (NIST/OWASP/…, kanonisch an der Capability) · `objective_tags`
|
||||
(integrity/confidentiality/attack_surface/… — Vorwärts-Kompat zu einer späteren Security-Objective-
|
||||
Klasse) · `risk_level` · `deadline` · **`hazard` (Attribut, KEINE Klasse)**.
|
||||
|
||||
**Watch-Point (bewusste Nicht-Klasse):** `Hazard/Threat` bleibt ein Risiko-Treiber-Attribut. Es wird
|
||||
*erst dann* eine eigene Klasse, wenn quantitatives Risiko (FMEA: Hazard→Risiko→Maßnahme) als
|
||||
First-Class-Graph-Knoten modelliert werden soll — das ist die einzige bekannte künftige Öffnungs-Ursache.
|
||||
|
||||
## 4. Architektur-Freeze-Policy
|
||||
|
||||
1. **Neue Regulierung = Daten, nicht Architektur.** Sie läuft durch `Parser → Discovery-Pipeline →
|
||||
Review → Registry` und fügt Obligations/Capabilities/Procedures/Evidence hinzu.
|
||||
2. **Eine neue Objektklasse ist eine Architektur-Änderung** und erfordert explizite Wieder-Öffnung +
|
||||
Begründung (nachgewiesenes Scheitern der Abbildung). Default-Erwartung: **0 neue Klassen.**
|
||||
3. Verfeinerungen an Attributen (neues `*_tag`, neues risk-Attribut) sind erlaubt, solange keine
|
||||
neue Klasse entsteht.
|
||||
|
||||
## 5. Reuse-Metrik (KPI je neuer Regulierung) — der Wissens-Akkumulations-Beweis
|
||||
|
||||
Für jede neue Regulierung gemessen (Baseline = der jeweils vorhandene Bestand):
|
||||
|
||||
| Kennzahl | Soll/Bedeutung |
|
||||
|---|---|
|
||||
| **Neue Objektklassen** | **= 0** (Invariante; sonst Freeze gebrochen) |
|
||||
| Neue Capabilities | additiv (z.B. +8) |
|
||||
| **Wiederverwendete Capabilities %** | Kern-KPI (z.B. NIS2 ~70–80 % erwartet) |
|
||||
| Wiederverwendete Procedures % | (z.B. 58 %) |
|
||||
| Wiederverwendete Evidence % | (z.B. 81 %) |
|
||||
| Neue Obligations | additiv (z.B. +42) |
|
||||
|
||||
Zielaussage: *„Beim AI Act: 0 neue Objektklassen, 12 neue Capabilities, 41 neue Obligations,
|
||||
78 % der vorhandenen Capabilities wiederverwendet."* → belegt, dass das System **Wissen akkumuliert**,
|
||||
statt je Regulierung neu gebaut zu werden. (Tool zur Berechnung folgt mit dem ersten Live-Durchlauf.)
|
||||
|
||||
## 6. Der Burggraben (warum das mehr ist als ein Advisor / RAG)
|
||||
|
||||
Der Kunde denkt nicht in Artikeln, sondern: *„Wir haben Remote-Updates / signierte Firmware / einen
|
||||
Vuln-Prozess."* Über die Capability-Schicht bildet das System diese Aussagen auf **alle betroffenen
|
||||
Obligations mehrerer Regulierungen** ab und beantwortet die eigentliche Frage aus dem Kundengespräch:
|
||||
> **„Habe ich das Gesetz richtig verstanden, und reicht das, was wir umgesetzt haben?"**
|
||||
|
||||
Das ist regel-/wissensgestütztes Reasoning über ein gemeinsames Modell — keine RAG-Aufgabe.
|
||||
(Die Reasoning-Session hält dabei die Welt-Grenze: `ClaimCoverage` „potenziell relevant" ⊥
|
||||
`ComplianceStatus` „erfüllt aus Nachweisen".)
|
||||
|
||||
## 7. Was NICHT eingefroren ist (wächst weiter als Daten)
|
||||
|
||||
Registry-Inhalte (Obligations je Regulierung), die Capabilities-Liste, Procedures, Evidence-Typen,
|
||||
Applicability-Prädikate, Citation-Spans. Diese Schicht ist **Wissensaufbau** — explizit erwünschtes
|
||||
Wachstum gegen das eingefrorene Modell.
|
||||
|
||||
## 8. Erster Live-Durchlauf (User-Priorität nach Informationswert)
|
||||
|
||||
1. **MaschVO** ⭐⭐⭐⭐⭐ — beweist „Compliance-OS ≠ Cybersecurity" (physische Safety, CE, Restgefahren).
|
||||
2. **NIS2** ⭐⭐⭐⭐ — misst maximalen Capability-Reuse (erwartet 70–80 %).
|
||||
3. **AI Act** ⭐⭐⭐⭐ — Risikoklassifizierung/Governance, vermutlich 0 neue Klassen.
|
||||
4. **Data Act** ⭐⭐⭐ — bestätigt „Capability optional".
|
||||
@@ -0,0 +1,159 @@
|
||||
# Meta-Model Validation v1 — Ist das Modell regulierungsunabhängig?
|
||||
|
||||
Status: **Phase 6 — Meta-Validierung (2026-06-26). KEIN neues Coding, KEINE Regulierung ingestiert.**
|
||||
Dieses Dokument ist der Stresstest VOR der nächsten Regulierung. Baut auf
|
||||
[capability_model_v1.md](capability_model_v1.md) (Modell C, #5b materialisiert) +
|
||||
[legal_obligation_layer_v1.md](legal_obligation_layer_v1.md).
|
||||
|
||||
## Die eigentliche Frage
|
||||
|
||||
Nicht „welche Regulierung kommt als nächstes?", sondern:
|
||||
|
||||
> **Kann eine völlig neue Regulierung in dieses Modell eingeordnet werden, OHNE eine neue
|
||||
> Objektklasse einzuführen?**
|
||||
|
||||
Wenn ja für MaschVO + Data Act + AI Act + NIS2 → das ist kein CRA-Graph mehr, sondern ein
|
||||
**Compliance Meta Model**. Ab dann bringt jede Regulierung primär *Daten*, nicht *Architektur*.
|
||||
|
||||
## Das zu testende Modell (6 Klassen + Attribute, KEINE weitere Klasse erlaubt)
|
||||
|
||||
```
|
||||
Regulation
|
||||
↓ definiert
|
||||
Legal Obligation (CORE ⊇ DOMAIN; tier=LEGAL_MINIMUM/BEST_PRACTICE; objective_tags[]; applicability)
|
||||
↓ realized_by (OPTIONAL)
|
||||
Capability (regulierungs-agnostische technische Faehigkeit; guidance_basis hier)
|
||||
↓ deployed_via
|
||||
Procedure
|
||||
↓ verified_by
|
||||
Control
|
||||
↓ produces
|
||||
Evidence
|
||||
```
|
||||
Quer: **Guidance** (NIST/OWASP/ISO/BSI) hängt an der Capability. **Attribute** (keine Klassen):
|
||||
`tier`, `objective_tags`, `applicability`, später `deadline`/`risk_level`/`severity`.
|
||||
Kanten: realized_by · specializes · contributes_to · deployed_via · verified_by · produces · described_by · same_as.
|
||||
|
||||
---
|
||||
|
||||
## Test 1 — Maschinenverordnung (EU) 2023/1230
|
||||
|
||||
| Modell-Klasse | MaschVO-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation (CORE) | `hazard_minimization` (Sicherheits-Analogon zu attack_surface_minimization), `safe_control_systems`, `machine_risk_assessment`, `ce_conformity`, `instructions_for_use` — exakt die `machine_*`-Obligations, die die Reasoning-Session bereits unabhängig geprägt hat. |
|
||||
| Capability | **physische Sicherheitsfunktionen**: `emergency_stop`, `safety_interlock`, `two_hand_control`, `guarding`, `safe_torque_off`. → die **Capability-Klasse generalisiert von Cyber auf physische Safety** (gleiche Klasse, andere Domäne). |
|
||||
| Procedure | Risikobeurteilung (ISO 12100), CE-Konformitätsbewertung. |
|
||||
| Evidence | Technische Unterlagen, Risikobeurteilungsbericht, Konformitätserklärung. |
|
||||
|
||||
**Stress-Punkt:** „**Hazard**" (mechanisch/elektrisch/thermisch) = Schadensquelle — weder Obligation
|
||||
noch Capability. Kandidat für eine neue Klasse? → **Nein, für die Repräsentation:** ein Hazard ist
|
||||
ein *Risiko-Treiber* (Attribut/Applicability der Risikobeurteilungs-Procedure); eine Capability
|
||||
*mitigiert* einen Hazard, genau wie eine Cyber-Capability eine (implizite) Bedrohung kontert. `PL/SIL`
|
||||
= Attribut (wie `tier`). **Hazard wird erst dann eine Klasse, wenn ihr quantitatives FMEA-Risiko als
|
||||
First-Class-Graph-Knoten wollt** (vgl. [[project-fmea-safety-direction]]) — nicht für Compliance-Abbildung.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** (Stärkstes Ergebnis: die Capability-Klasse trägt von Cyber zu Safety.)
|
||||
|
||||
---
|
||||
|
||||
## Test 2 — Data Act (EU) 2023/2854
|
||||
|
||||
| Modell-Klasse | Data-Act-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `data_act_data_access_by_design`, `data_act_user_data_access`, `data_portability_switching`, `fair_contract_terms` (FRAND), `interoperability` — deckt sich mit den `data_act_*`-Obligations der Reasoning-Session. |
|
||||
| Capability | `data_export_api`, `interoperability_interface`, `access_control` (**Reuse**). ABER: `fair_contract_terms` hat **KEINE technische Capability**. |
|
||||
| Procedure | FRAND-Klauseln entwerfen; Switching-Prozess. |
|
||||
| Evidence | Vertrag/Klauselwerk, API-Doku. |
|
||||
|
||||
**Stress-Punkt:** **vertraglich-rechtliche Pflichten** (FRAND, Verbot unfairer Klauseln) haben kein
|
||||
technisches Mittel. → Beleg, dass **`realized_by Capability` OPTIONAL ist**: manche Obligations werden
|
||||
rein über **Procedure (Entwurf) + Evidence (Vertrag)** erfüllt. Das ist KEINE neue Klasse — wir haben
|
||||
es schon gesehen (SBOM-Familie war Evidence-/Procedure-lastig, kaum Capability).
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Verfeinerung: Capability ist optional (Obligation → Procedure → Evidence
|
||||
ohne Capability ist gültig).
|
||||
|
||||
---
|
||||
|
||||
## Test 3 — AI Act (EU) 2024/1689
|
||||
|
||||
| Modell-Klasse | AI-Act-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `ai_risk_management_system`, `ai_data_governance`, `ai_technical_documentation`, `ai_transparency_disclosure`, `human_oversight`, `accuracy_robustness`, `fundamental_rights_assessment`. |
|
||||
| Capability | `event_logging` (**Reuse**!), `bias_detection`, `accuracy_testing`, `human_oversight_mechanism`, `ai_transparency_notice`. |
|
||||
| Procedure | Risikomanagement-Prozess; FRIA-Durchführung; Human-Oversight-Prozess. |
|
||||
| Evidence | Technische Dokumentation, FRIA-Bericht, Logs. |
|
||||
|
||||
**Stress-Punkt:** **Risiko-Klassifikation** (unacceptable/high/limited/minimal) bestimmt, WELCHE
|
||||
Obligations gelten. → das ist **Applicability** (existiert bereits; analog zu CRA-Produktklasse).
|
||||
`human_oversight` = Procedure + Capability (Oversight-UI). `transparency` = Disclosure (Capability/Evidence,
|
||||
wie Cookie/DSE-Offenlegung). `FRIA` = Procedure + Evidence.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Verfeinerung: Risiko-Tier = Applicability-Attribut (vorhanden).
|
||||
|
||||
---
|
||||
|
||||
## Test 4 — NIS2 (EU) 2022/2555
|
||||
|
||||
| Modell-Klasse | NIS2-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `nis2_risk_management_measures`, `nis2_incident_reporting`, `supply_chain_security`, `governance_accountability`, `business_continuity`. |
|
||||
| Capability | **MFA, transport_encryption, security_monitoring_alerting, patch/update, backup** — **dieselben Capabilities wie CRA**. Das ist die Auszahlung: NIS2-Obligations `realized_by` die bereits gebaute Capability-Schicht. |
|
||||
| Procedure | Incident-Response-Prozess; Lieferketten-Audit; Governance-Prozess. |
|
||||
| Evidence | Incident-Reports, Audit-Logs, Vorstandsprotokolle. |
|
||||
|
||||
**Stress-Punkt:** **Meldefristen** (24h/72h/1 Monat) = zeitgebundene Procedure → `deadline` = Attribut.
|
||||
`governance_accountability` (Management-Haftung) = organisatorische Obligation → Procedure + Evidence.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Stärkster Reuse-Fall (teilt die CRA-Capability-Schicht vollständig).
|
||||
|
||||
---
|
||||
|
||||
## Ergebnis: 4 × NEIN → das Metamodell steht
|
||||
|
||||
Alle vier Regulierungen passen in die 6 Klassen **ohne neue Objektklasse** — unter zwei
|
||||
Verfeinerungen, die der Test selbst aufdeckt (beide sind KEINE neuen Klassen):
|
||||
|
||||
1. **`realized_by Capability` ist OPTIONAL.** Vertraglich/dokumentarisch/prozessuale Obligations
|
||||
(Data-Act-FRAND, NIS2-Governance, AI-Act-FRIA) werden rein über Procedure + Evidence erfüllt.
|
||||
2. **Risiko-Niveau / Frist / Hazard-Schwere / Risiko-Tier sind ATTRIBUTE**, keine Klassen
|
||||
(`tier`-Muster: `deadline`, `risk_level`, `severity`, `risk_tier`).
|
||||
|
||||
**Der einzige Watch-Point:** **Hazard / Threat.** Heute implizit (Obligations existieren, um sie zu
|
||||
kontern). Eine eigene Klasse wird *erst* nötig, wenn ihr **quantitatives Risiko first-class** modelliert
|
||||
(FMEA: Hazard→Risiko→Maßnahme als Graph-Knoten). Für die reine Compliance-Abbildung: nicht nötig.
|
||||
→ Das ist die präzise Antwort auf „wo wäre erstmals eine neue Klasse nötig?".
|
||||
|
||||
## Empirische Stütze (nicht nur Theorie)
|
||||
|
||||
Die 3. Session (Reasoning Engine) hat **unabhängig** `proposed=True`-Obligations für MaschVO
|
||||
(`machine_*`) und Data Act (`data_act_*`) geprägt — und brauchte dafür **keine neue Objektklasse**,
|
||||
nur Obligation-IDs. Zwei Sessions kommen unabhängig zum selben Schluss.
|
||||
|
||||
## Konsequenz für die Reasoning-Schicht (Produktvision)
|
||||
|
||||
Heute: `Product → Applicable Regulations → Applicable Obligations`.
|
||||
Mit der Capability-Schicht wird daraus:
|
||||
```
|
||||
Applicable Capabilities → Required Procedures → Expected Evidence
|
||||
```
|
||||
Antwort auf die Kundenaussage „Ich habe X umgesetzt" ist dann nicht „CRA Artikel …", sondern:
|
||||
```
|
||||
✓ Capability A ✓ Capability B ✗ Capability C
|
||||
↓
|
||||
erfüllt CRA, MaschVO, NIS2 (teilweise)
|
||||
```
|
||||
Eine Capability erfüllt Obligations über *mehrere Regulierungen* (n:m) → eine Umsetzung wird gegen
|
||||
das gesamte Regelwerk bewertet. Das ist qualitativ ein anderes Produkt als RAG.
|
||||
|
||||
## Entscheidung / nächster Schritt
|
||||
|
||||
Wenn dieses Dokument akzeptiert ist („keine weitere Klasse nötig"), verschiebt sich die Arbeit von
|
||||
**Architektur** zu **Wissensaufbau**: jede neue Regulierung läuft durch
|
||||
`Parser → Discovery-Pipeline → Review → Registry` (vorhandene Tooling), statt das Modell zu ändern.
|
||||
Offen für den User: (a) Metamodell als stabil einfrieren? (b) den Hazard/Threat-Watch-Point als
|
||||
bewusste Nicht-Klasse dokumentieren (bis FMEA-Quantifizierung)? (c) dann erste Regulierung als DATEN.
|
||||
|
||||
## Bewusst NICHT in diesem Schritt
|
||||
|
||||
Kein Code, keine Regulierung ingestiert, keine neue Klasse angelegt. Reiner Modell-Stresstest.
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"schema_version": "capability_layer_v1",
|
||||
"model": "Modell C (docs-src/development/capability_model_v1.md)",
|
||||
"note": "Capability = technische Faehigkeit (regulierungs-agnostisch). realized_by = Obligations, die sie erfuellt (n:m). guidance_basis hier KANONISCH hochgezogen aus den realisierten Obligations (die Obligation-Kopien bleiben vorerst als Legacy; Strip = Folge-Cleanup). Sicherheitsziele sind KEINE Capabilities -> cra_core.json.",
|
||||
"dropped": {
|
||||
"access_control": "OVERLAP (credential_confidentiality <-> sbom_confidentiality), nicht materialisiert"
|
||||
},
|
||||
"candidate_capabilities_followup": [
|
||||
"automatic_update_delivery",
|
||||
"update_rollback",
|
||||
"trusted_update_source",
|
||||
"hash_verification",
|
||||
"secure_boot",
|
||||
"least_functionality",
|
||||
"credential_storage"
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"capability_id": "multi_factor_authentication",
|
||||
"name": "Multi-Factor Authentication",
|
||||
"description": "Mehrfaktor-Authentisierung als technische Faehigkeit (Besitz/Wissen/Inhaerenz).",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"mfa_required",
|
||||
"privileged_op_reauth",
|
||||
"remote_access_authentication",
|
||||
"remote_access_mfa",
|
||||
"remote_access_user_validation_ot",
|
||||
"supplier_access_auth"
|
||||
],
|
||||
"realizes_count": 6,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-63B",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "Out-of-Band-Authentifizierung",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "out_of_band_authentication"
|
||||
},
|
||||
{
|
||||
"source": "Hardware-basierte Authentifizierung (AAL3)",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "hardware_authenticators"
|
||||
},
|
||||
{
|
||||
"source": "E-Mail-Authentifizierungsmechanismen (SPF/DKIM/DMARC)",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "email_authentication"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-02",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-02(1)",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AC-17",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-53 IA-2",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "ICS Security Kompendium",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "ISO",
|
||||
"anchor": "ISO 27001 A.5.19",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "session_management",
|
||||
"name": "Session Management",
|
||||
"description": "Sichere Sitzungsverwaltung: Timeouts, Bindung, Re-Auth, Beendigung.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"reauth_after_inactivity",
|
||||
"remote_session_management",
|
||||
"session_binding_management",
|
||||
"temporary_remote_access_mgmt"
|
||||
],
|
||||
"realizes_count": 4,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-63B 4.3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-53 AC-12",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "OWASP",
|
||||
"anchor": "ASVS V3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AC-2(5)",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "transport_encryption",
|
||||
"name": "Transport Encryption",
|
||||
"description": "Verschluesselter Transport (TLS, mutual-TLS, Zertifikats-Auth, VPN/Tunnel).",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"encrypted_auth_channel",
|
||||
"mutual_authentication",
|
||||
"reject_insecure_remote_protocols",
|
||||
"remote_access_confidentiality_integrity",
|
||||
"remote_access_encryption",
|
||||
"service_to_service_auth",
|
||||
"tls_certificate_auth"
|
||||
],
|
||||
"realizes_count": 7,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "TR-02102-2",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-03",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SC-8",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "IT-Grundschutz NET.3.3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "OWASP",
|
||||
"anchor": "API Security Top 10",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-05(2)",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "code_signing",
|
||||
"name": "Code & Update Signing",
|
||||
"description": "Digitale Signatur + Integritaets-/Authentizitaetspruefung von Firmware/Software/Updates.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"firmware_software_authentication",
|
||||
"signed_update_integrity"
|
||||
],
|
||||
"realizes_count": 2,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SI-07",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-147 BIOS Protection",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"updates"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "security_monitoring_alerting",
|
||||
"name": "Security Monitoring & Alerting",
|
||||
"description": "Anomalie-/Bedrohungserkennung und Alarmierung aus Logs/Telemetrie.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"log_monitoring_alerting",
|
||||
"remote_access_threat_detection"
|
||||
],
|
||||
"realizes_count": 2,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AU-6/SI-4",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-94",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"logging",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,71 +1,92 @@
|
||||
{
|
||||
"schema_version": "controls_for_obligation_mapping_v1",
|
||||
"purpose": "Accepted CRA->OWASP controls (Compliance Execution Graph) for the Obligation Registry to propose the SEMANTIC control->obligation_id, replacing the coarse citation_unit interim join. Fill proposed_obligation_id per control, then we adopt it into control_mapping.obligation_id.",
|
||||
"source": "ai-compliance-sdk control_mappings, mapping_status=accepted, reviewed_by=benjamin 2026-06-25",
|
||||
"count": 7,
|
||||
"purpose": "Accepted CRA->Framework controls (Compliance Execution Graph) for the Obligation Registry to propose the SEMANTIC control->obligation_id, replacing the coarse citation_unit interim join. Fill proposed_obligation_id per control, then we adopt it into control_mapping.obligation_id.",
|
||||
"source": "ai-compliance-sdk control_mappings, mapping_status=accepted, reviewed_by=benjamin 2026-06-25. OWASP ASVS (7, gefuellt) + NIST SP 800-53 (3, pending).",
|
||||
"filled_by": "obligation-registry-session 2026-06-25. OWASP 7/7 (4 auth/crypto + 3 logging). NIST 3/3 GEFUELLT (Obligation-Session): SI-2->provide_security_updates (stark, (2)(c)/Art.13) · SI-7->signed_update_integrity (update-scoped; SI-7 breiter) · CM-7->remote_access_attack_surface_min (remote-scoped; CM-7 breiter). GAP-BEFUND (Cross-Domain-Review): generische Parent-Obligations software_integrity_protection + attack_surface_minimization FEHLEN — SI-7/CM-7 sind breiter als die domaenen-scoped Treffer. Kandidaten fuer neue generische Obligations (User-Entscheidung). Damit 10/10 proposed_obligation_id gefuellt.",
|
||||
"join_principle": "SEMANTISCH via obligation_id, NICHT via citation_unit/legal_basis-Anker. Die CRA-Anker sind im Registry teils approximativ (siehe anchor_quality_note) — daher ist obligation_id der stabile Primaerschluessel, nicht der Anker.",
|
||||
"anchor_quality_note": "Registry-legal_basis-Anker sind teils CRA-Part-I-fehlzugeordnet (Opus-Synthese): user_authentication_required steht auf (2)(d) statt (2)(c); Crypto-Obligations auf (2)(e) statt (2)(d). CRA Annex I Part I: (2)(c)=Zugriffsschutz, (2)(d)=Vertraulichkeit, (2)(e)=Integritaet. Korrektur kommt mit dem zitierfaehigen Re-Ingest (span-genau). Deshalb: NICHT auf Anker joinen. ABER: der Logging-Cut (V16.*) ist korrekt auf (2)(k) verankert (echte Logging-Subsektion, kein Fehl-Anker).",
|
||||
"mapping_type_note": "NEU: mapping_type=primary_implementation = die kanonische Primaer-Control einer Anforderung (genau eine), staerker als implements/supports. related-Controls (SC-3(3), RA-5, AC-6, SI-16, SA-10, ...) folgen separat als supports. Eine Obligation kann mehrere Controls haben, aber genau einen primary_implementation-Einstieg.",
|
||||
"count": 10,
|
||||
"controls": [
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V6.3.1",
|
||||
"framework": "OWASP ASVS", "control": "V6.3.1",
|
||||
"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff",
|
||||
"citation_unit": "Annex I (2)(c)",
|
||||
"family": "auth",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(c)", "family": "auth", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "user_authentication_required",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "Zugriffsschutz/Authentisierung-vor-Zugriff = Nutzer-Auth (NICHT firmware, trotz strukturellem (2)(c)-Join)"
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V6.1.1",
|
||||
"framework": "OWASP ASVS", "control": "V6.1.1",
|
||||
"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff",
|
||||
"citation_unit": "Annex I (2)(c)",
|
||||
"family": "auth",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(c)", "family": "auth", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "user_authentication_required",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "wie V6.3.1"
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V11.2.1",
|
||||
"framework": "OWASP ASVS", "control": "V11.2.1",
|
||||
"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung",
|
||||
"citation_unit": "Annex I (2)(d)",
|
||||
"family": "crypto",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(d)", "family": "crypto", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "credential_confidentiality_protection",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "Vertraulichkeit von Auth-Daten. ALT: encrypted_auth_channel, falls V11.2.1 transit-/kanal-spezifisch ist — bitte aus eurem Control-Text bestaetigen."
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V11.7.1",
|
||||
"framework": "OWASP ASVS", "control": "V11.7.1",
|
||||
"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung",
|
||||
"citation_unit": "Annex I (2)(d)",
|
||||
"family": "crypto",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(d)", "family": "crypto", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "auth_key_management",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "Key Management = Schluessel erzeugen/speichern/HSM"
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V16.3.3",
|
||||
"framework": "OWASP ASVS", "control": "V16.3.3",
|
||||
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
|
||||
"citation_unit": "Annex I (2)(k)",
|
||||
"family": "logging",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "event_logging_security_events",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "Umbrella-LM 'Produkt protokolliert sicherheitsrelevante Ereignisse' (CRA (2)(k)). ALT bei access-decision-spezifischem Control-Text: access_control_event_logging — bitte aus eurem ASVS-V16.3-Text bestaetigen."
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V16.3.4",
|
||||
"framework": "OWASP ASVS", "control": "V16.3.4",
|
||||
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
|
||||
"citation_unit": "Annex I (2)(k)",
|
||||
"family": "logging",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "event_logging_security_events",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "Umbrella-LM (CRA (2)(k)). ALT bei admin-/privileg-spezifischem Control-Text: audit_trail_admin_actions — bitte aus eurem ASVS-V16.3-Text bestaetigen."
|
||||
},
|
||||
{
|
||||
"framework": "OWASP ASVS",
|
||||
"control": "V16.1.1",
|
||||
"framework": "OWASP ASVS", "control": "V16.1.1",
|
||||
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
|
||||
"citation_unit": "Annex I (2)(k)",
|
||||
"family": "logging",
|
||||
"mapping_type": "supports",
|
||||
"proposed_obligation_id": ""
|
||||
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
|
||||
"proposed_obligation_id": "event_logging_security_events",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "V16.1 = allgemeine Logging-Anforderung -> Umbrella-LM event_logging_security_events. Hohe Konfidenz."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "SI-7",
|
||||
"source_norm": "CRA Annex I Part I (2)(e) — Integritaet",
|
||||
"citation_unit": "Annex I (2)(e)", "family": "integrity", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "software_integrity_protection",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST SI-7 = Software/Firmware/Information Integrity (gesamte Produkt-Integritaet). #6 ADOPTIERT (2026-06-26) auf CORE software_integrity_protection (Annex I (2)(f)) — die in #5b materialisierte generische Integritaets-Obligation. Die domaenen-scoped signed_update_integrity (Update-Signatur, (1)(3)(f)) bleibt gueltig als DOMAIN, specializes->CORE. NICHT log_integrity_immutability (Audit-Log-Schutz, andere Ebene)."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "SI-2",
|
||||
"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates",
|
||||
"citation_unit": "Annex I (2)(l)", "family": "update", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "provide_security_updates",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST SI-2 = Flaw Remediation. STARKER Treffer in eurer NEUEN updates-Familie (93-Stand): provide_security_updates (LEGAL_MINIMUM, Annex I (2)(c) + Art. 13) = DAS sichere-Update-LM. -> SI-2 primary_implementation = provide_security_updates. Verwandt (supports): vuln_remediation_patching (Part II Remediations-PROZESS), support_period_maintenance, update_testing_validation, update_rollback. Mein source_norm-Anker (2)(l) ist approximativ -> bitte (2)(c)/Art.13 via provide_security_updates nutzen."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "CM-7",
|
||||
"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren",
|
||||
"citation_unit": "Annex I (2)(i)", "family": "attack_surface", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "attack_surface_minimization",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST CM-7 = Least Functionality (deaktivierte Ports/Dienste/Funktionen, GESAMTE Angriffsflaeche). #6 ADOPTIERT (2026-06-26) auf CORE attack_surface_minimization (Annex I (2)(j)) — die in #5b materialisierte generische Obligation. Die domaenen-scoped remote_access_attack_surface_min (nur Remote-Access-Flaeche) bleibt gueltig als DOMAIN, specializes->CORE. related (supports): SC-3(3)/AC-6/SI-16."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -966,7 +966,10 @@
|
||||
"relationships": [],
|
||||
"citation_anchor_ids": [],
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "draft"
|
||||
"review_status": "draft",
|
||||
"merged_into": "provide_security_updates",
|
||||
"status": "deprecated_alias",
|
||||
"merge_note": "SAME_OBLIGATION (Cross-Domain-Review). Kanonisch: provide_security_updates ((2)(c)/Art.13). ID bleibt als Alias aufloesbar; downstream provide_security_updates nutzen."
|
||||
},
|
||||
{
|
||||
"id": "vuln_handling_process",
|
||||
|
||||
@@ -10281,7 +10281,11 @@
|
||||
"cluster_size": 4,
|
||||
"llm_model": "claude-opus-4-8",
|
||||
"synthesis_version": "v1"
|
||||
}
|
||||
},
|
||||
"specializes": "software_integrity_protection",
|
||||
"objective_tags": [
|
||||
"integrity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"schema_version": "obligation_registry_v1",
|
||||
"regulation": "CRA",
|
||||
"regulation_code": "CRA",
|
||||
"family": "core",
|
||||
"theme": "CORE Security Objectives (CRA Annex I als regulierungs-agnostische Sicherheitsziele)",
|
||||
"generated_by": "materialize_capabilities.py (#5b, Modell C)",
|
||||
"note": "CORE Legal Obligations = Sicherheitsziele (Modell C: KEINE eigene SecurityObjective-Klasse). DOMAIN-Obligations specializes-en hierauf. objective_tags = Vorwaerts-Kompat zu Modell B.",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"obligations": [
|
||||
{
|
||||
"id": "attack_surface_minimization",
|
||||
"name": "Minimierung der Angriffsflaeche",
|
||||
"family": "core",
|
||||
"description": "Das Produkt minimiert seine Angriffsflaeche: unnoetige Funktionen/Ports/Dienste/Schnittstellen sind deaktiviert (Least Functionality).",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"source_role": "LEGAL_BASIS",
|
||||
"applicability": "universal",
|
||||
"objective_tags": [
|
||||
"attack_surface"
|
||||
],
|
||||
"legal_basis": [
|
||||
{
|
||||
"source": "CRA",
|
||||
"anchor": "Annex I Part I (2)(j)",
|
||||
"citation": "limit attack surfaces, including external interfaces"
|
||||
}
|
||||
],
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "CM-7 Least Functionality",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"specialized_by": [
|
||||
"remote_access_attack_surface_min",
|
||||
"component_remote_interface_security"
|
||||
],
|
||||
"primary_implementation": "NIST CM-7",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"
|
||||
},
|
||||
{
|
||||
"id": "software_integrity_protection",
|
||||
"name": "Schutz der Software-/Firmware-Integritaet",
|
||||
"family": "core",
|
||||
"description": "Das Produkt schuetzt Integritaet und Authentizitaet von Software/Firmware (Manipulationserkennung, Secure Boot, Signaturpruefung, Runtime-Integritaet).",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"source_role": "LEGAL_BASIS",
|
||||
"applicability": "universal",
|
||||
"objective_tags": [
|
||||
"integrity"
|
||||
],
|
||||
"legal_basis": [
|
||||
{
|
||||
"source": "CRA",
|
||||
"anchor": "Annex I Part I (2)(f)",
|
||||
"citation": "protect the integrity of stored, transmitted or processed data, software and configuration"
|
||||
}
|
||||
],
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SI-7 Software, Firmware, and Information Integrity",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"specialized_by": [
|
||||
"signed_update_integrity",
|
||||
"firmware_software_authentication"
|
||||
],
|
||||
"realized_by_capabilities": [
|
||||
"code_signing"
|
||||
],
|
||||
"primary_implementation": "NIST SI-7",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"
|
||||
}
|
||||
],
|
||||
"relationships": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "obligation_join_keys_v1",
|
||||
"contract": "obligation_id ist der stabile Join-Key. Legal Knowledge Graph haengt citation_spans an obligation_id; Compliance Execution Graph mappt control_mapping.source_norm -> obligation_id. Interim-Bruecke = citation_units. obligation_id NIE neu vergeben (re-link).",
|
||||
"count": 47,
|
||||
"count": 95,
|
||||
"obligation_ids": [
|
||||
{
|
||||
"obligation_id": "sbom_creation",
|
||||
@@ -175,6 +175,26 @@
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "attack_surface_minimization",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(j)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "software_integrity_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "user_authentication_required",
|
||||
"regulation": "CRA",
|
||||
@@ -418,6 +438,409 @@
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "event_logging_security_events",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "access_control_event_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "audit_trail_admin_actions",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_integrity_immutability",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_access_control_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_retention_archival",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "centralized_log_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_monitoring_alerting",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_data_minimization_privacy",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_format_standardization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_timestamp_synchronization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_availability_resilience",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_thread_safety_correctness",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_library_supply_chain",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_config_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_governance_roles",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "incident_response_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_transmission_security",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "network_traffic_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_control_least_privilege",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_confidentiality_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(b)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_session_management",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_mfa",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_encryption",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reject_insecure_remote_protocols",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_logging_audit",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(g)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_user_validation_ot",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_training",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_architecture_design",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_attack_surface_min",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_vuln_patch_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_threat_detection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_maintenance_governance",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "temporary_remote_access_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_data_export_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "component_remote_interface_security",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_fallback_concept",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "provide_security_updates",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)",
|
||||
"Art. 13"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "support_period_maintenance",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 13(8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "signed_update_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "trusted_update_source",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_testing_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_rollback",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "automatic_updates_optout",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_risk_assessment",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "secure_modification_control",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Cross-Domain Relationship Discovery — Stufe 2: Opus klassifiziert jede Kandidaten-Beziehung
|
||||
in GENAU EINE Kategorie. Liefert das Rohmaterial der Compliance-Ontologie (insb. SHARED_CAPABILITY
|
||||
= Capability-Schicht). ANTHROPIC_API_KEY aus ENV (nie hartcodiert). Streaming.
|
||||
|
||||
ANTHROPIC_API_KEY=… python3 classify_relationships.py --pairs /tmp/cd_pairs.json \
|
||||
--only-cross-family --out /tmp/cd_classified.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
SYS = """Du bist Compliance-Ontologe. Gegeben Paare von Legal Obligations (CRA), bestimme fuer
|
||||
JEDES Paar GENAU EINE Beziehung. Ziel ist NICHT Aehnlichkeit, sondern die STRUKTURELLE Beziehung.
|
||||
|
||||
Kategorien (genau EINE; bei Mehrdeutigkeit gilt diese Prioritaet):
|
||||
1 SAME_OBLIGATION — dieselbe rechtliche Pflicht, nur pro Domaene anders formuliert -> MERGE-Kandidat.
|
||||
2 SUPPORTED_BY — A ist domaenenspezifische Auspraegung/Teilfall von B ODER A traegt zur Erfuellung von B bei. RICHTUNG angeben.
|
||||
3 SHARED_CAPABILITY — beide werden durch DIESELBE technische Faehigkeit erfuellt (z.B. MFA, TLS-Verschluesselung, digitale Signatur, Session-Management, Patch-Management, Logging-Pipeline). capability_name (snake_case) angeben.
|
||||
4 SHARED_PROCEDURE — beide ueber denselben operativen Prozess erfuellt, ohne gemeinsames technisches Artefakt.
|
||||
5 SHARED_EVIDENCE — beide erzeugen/nutzen denselben Nachweis (Audit-Log, SBOM, Release Notes). evidence_name angeben.
|
||||
6 SHARED_GUIDANCE — beide berufen sich auf denselben externen Standard (NIST/OWASP/ISO), sonst distinkt.
|
||||
7 OVERLAP_ONLY — nur oberflaechliche Wort-/Themenueberlappung, keine echte strukturelle Beziehung.
|
||||
8 UNRELATED — Falsch-Positiv der Embedding-Naehe.
|
||||
|
||||
Gib AUSSCHLIESSLICH JSON aus:
|
||||
{"results":[{"i":0,"relation":"SHARED_CAPABILITY","direction":"a->b|b->a|none","capability_name":"","evidence_name":"","reason":"max 18 Woerter"}]}
|
||||
Regeln: relation = genau eine der 8 Strings. direction nur bei SUPPORTED_BY, sonst "none".
|
||||
capability_name NUR bei SHARED_CAPABILITY (sonst ""), evidence_name NUR bei SHARED_EVIDENCE (sonst "").
|
||||
Sei streng: SHARED_GUIDANCE/OVERLAP_ONLY/UNRELATED grosszuegig nutzen; SAME_OBLIGATION nur bei echter Deckungsgleichheit.
|
||||
Gib fuer JEDES Paar (per Index i) genau ein Ergebnis."""
|
||||
|
||||
|
||||
def build_user(pairs: list[dict]) -> str:
|
||||
lines = []
|
||||
for i, p in enumerate(pairs):
|
||||
lines.append(f'[{i}] A={p["a"]} ({p["fa"]}/{p["ta"]}): {p["da"]}\n'
|
||||
f' B={p["b"]} ({p["fb"]}/{p["tb"]}): {p["db"]} [sim={p["sim"]}]')
|
||||
return "Paare:\n" + "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--pairs", required=True)
|
||||
ap.add_argument("--only-cross-family", action="store_true")
|
||||
ap.add_argument("--min-sim", type=float, default=0.0)
|
||||
ap.add_argument("--model", default="claude-opus-4-8")
|
||||
ap.add_argument("--out", required=True)
|
||||
a = ap.parse_args()
|
||||
d = json.load(open(a.pairs, encoding="utf-8"))
|
||||
pairs = [p for p in d["pairs"]
|
||||
if (not a.only_cross_family or p["cross_family"]) and p["sim"] >= a.min_sim]
|
||||
|
||||
import anthropic
|
||||
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
|
||||
with client.messages.stream(model=a.model, max_tokens=24000, system=SYS,
|
||||
messages=[{"role": "user", "content": build_user(pairs)}]) as st:
|
||||
msg = st.get_final_message()
|
||||
txt = msg.content[0].text
|
||||
m = re.search(r"\{.*\}", txt, re.DOTALL)
|
||||
data = json.loads(m.group(0) if m else txt)
|
||||
|
||||
res = []
|
||||
for r in data.get("results", []):
|
||||
i = r.get("i")
|
||||
if not isinstance(i, int) or i < 0 or i >= len(pairs):
|
||||
continue
|
||||
p = pairs[i]
|
||||
res.append({"a": p["a"], "fa": p["fa"], "b": p["b"], "fb": p["fb"], "sim": p["sim"],
|
||||
"relation": r.get("relation", "?"), "direction": r.get("direction", "none"),
|
||||
"capability_name": r.get("capability_name", ""),
|
||||
"evidence_name": r.get("evidence_name", ""), "reason": r.get("reason", "")})
|
||||
dist = Counter(r["relation"] for r in res)
|
||||
out = {"n_pairs": len(pairs), "n_classified": len(res), "distribution": dict(dist),
|
||||
"model": a.model, "results": res}
|
||||
json.dump(out, open(a.out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(f"classified {len(res)}/{len(pairs)} | {dict(dist)}")
|
||||
print("written:", a.out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Cross-Domain Relationship Discovery — Stufe 1 (key-frei, im bp-compliance-backend-Container).
|
||||
Alle Obligations mehrerer Registries -> BGE-M3-Embedding -> je Obligation Top-K Nachbarn ->
|
||||
Kandidaten-Paare (cross- UND same-family) >= min-sim. KEIN Urteil hier — nur Kandidaten.
|
||||
Stufe 2 (classify_relationships.py) klassifiziert die Beziehung per Opus.
|
||||
|
||||
python3 cross_domain_pairs.py /tmp/reg/cra.json /tmp/reg/cra_authentication.json ... \
|
||||
--top-k 8 --min-sim 0.60 --out /tmp/cd_pairs.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from _core import cosine
|
||||
|
||||
|
||||
async def run(paths: list[str], top_k: int, min_sim: float, out: str) -> None:
|
||||
from compliance.services.mc_embedding_matcher import _embed_texts
|
||||
|
||||
obls: list[dict] = []
|
||||
for p in paths:
|
||||
reg = json.load(open(p, encoding="utf-8"))
|
||||
fam = reg.get("family", "")
|
||||
for o in reg.get("obligations", []):
|
||||
obls.append({"id": o["id"], "family": o.get("family", "") or fam,
|
||||
"tier": o.get("tier", ""), "name": o.get("name", ""),
|
||||
"desc": o.get("description", "")})
|
||||
vecs = await _embed_texts([f'{o["name"]}. {o["desc"]}' for o in obls])
|
||||
n = len(obls)
|
||||
print(f"obligations={n}")
|
||||
|
||||
best: dict[tuple[int, int], float] = {}
|
||||
for i in range(n):
|
||||
nbrs = sorted(((cosine(vecs[i], vecs[j]), j) for j in range(n) if j != i), reverse=True)[:top_k]
|
||||
for s, j in nbrs:
|
||||
if s < min_sim:
|
||||
continue
|
||||
a, b = sorted((i, j))
|
||||
if (a, b) not in best or s > best[(a, b)]:
|
||||
best[(a, b)] = s
|
||||
|
||||
pairs = []
|
||||
for (a, b), s in sorted(best.items(), key=lambda x: -x[1]):
|
||||
pairs.append({
|
||||
"a": obls[a]["id"], "fa": obls[a]["family"], "ta": obls[a]["tier"], "da": obls[a]["desc"][:220],
|
||||
"b": obls[b]["id"], "fb": obls[b]["family"], "tb": obls[b]["tier"], "db": obls[b]["desc"][:220],
|
||||
"sim": round(s, 3), "cross_family": obls[a]["family"] != obls[b]["family"]})
|
||||
cf = sum(1 for p in pairs if p["cross_family"])
|
||||
json.dump({"n_obligations": n, "n_pairs": len(pairs), "cross_family": cf, "pairs": pairs},
|
||||
open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(f"pairs={len(pairs)} (cross-family={cf}, same-family={len(pairs) - cf}) written: {out}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("registries", nargs="+")
|
||||
ap.add_argument("--top-k", type=int, default=8)
|
||||
ap.add_argument("--min-sim", type=float, default=0.60)
|
||||
ap.add_argument("--out", default="/tmp/cd_pairs.json")
|
||||
a = ap.parse_args()
|
||||
asyncio.run(run(a.registries, a.top_k, a.min_sim, a.out))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,170 @@
|
||||
"""#5b — Materialisierung der Capability-Schicht (Modell C, User-Entscheidung 2026-06-26).
|
||||
|
||||
Aus `cross_domain_relationships.json` (SHARED_CAPABILITY) + den 6 CRA-P1-Registries:
|
||||
- `obligations/capabilities.json` — Capability-Knoten: realized_by (n:m) + guidance_basis hochgezogen.
|
||||
- `obligations/cra_core.json` — 2 CORE-Obligations (Sicherheitsziele): attack_surface_minimization,
|
||||
software_integrity_protection (Modell C: KEINE eigene SecurityObjective-Klasse; das Ziel IST eine
|
||||
abstrakte CORE-Pflicht).
|
||||
- patcht DOMAIN-Obligations in ihren Registries: `specializes` (→CORE) + `objective_tags` (Vorwärts-
|
||||
Kompat zu Modell B: Tags, keine Klasse).
|
||||
- markiert `vuln_remediation_patching` als deprecated_alias von `provide_security_updates` (Merge).
|
||||
- `remote_access_data_export_protection` bleibt BEST_PRACTICE (Notiz: pending Data-Act-Scope).
|
||||
|
||||
Deterministisch. Lokal lauffähig (nur json). Danach export_join_keys neu (inkl. cra_core).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
REG_FILES = ["obligations/cra.json", "obligations/cra_authentication.json",
|
||||
"obligations/cra_logging.json", "obligations/cra_remote_access.json",
|
||||
"obligations/cra_updates.json"]
|
||||
|
||||
# Cluster-capability_name -> kanonische capability_id (access_control absichtlich gedroppt: zu schwach)
|
||||
CONSOLIDATE = {"mfa": "multi_factor_authentication", "session_management": "session_management",
|
||||
"tls_encryption": "transport_encryption", "mutual_tls": "transport_encryption",
|
||||
"tls_certificate_auth": "transport_encryption", "code_signing": "code_signing",
|
||||
"anomaly_detection": "security_monitoring_alerting"}
|
||||
CAP_META = {
|
||||
"multi_factor_authentication": ("Multi-Factor Authentication",
|
||||
"Mehrfaktor-Authentisierung als technische Faehigkeit (Besitz/Wissen/Inhaerenz)."),
|
||||
"session_management": ("Session Management",
|
||||
"Sichere Sitzungsverwaltung: Timeouts, Bindung, Re-Auth, Beendigung."),
|
||||
"transport_encryption": ("Transport Encryption",
|
||||
"Verschluesselter Transport (TLS, mutual-TLS, Zertifikats-Auth, VPN/Tunnel)."),
|
||||
"code_signing": ("Code & Update Signing",
|
||||
"Digitale Signatur + Integritaets-/Authentizitaetspruefung von Firmware/Software/Updates."),
|
||||
"security_monitoring_alerting": ("Security Monitoring & Alerting",
|
||||
"Anomalie-/Bedrohungserkennung und Alarmierung aus Logs/Telemetrie."),
|
||||
}
|
||||
CAP_ORDER = ["multi_factor_authentication", "session_management", "transport_encryption",
|
||||
"code_signing", "security_monitoring_alerting"]
|
||||
|
||||
# DOMAIN-Obligation -> (CORE-Ziel, objective_tags)
|
||||
SPECIALIZES = {
|
||||
"remote_access_attack_surface_min": ("attack_surface_minimization", ["attack_surface"]),
|
||||
"component_remote_interface_security": ("attack_surface_minimization", ["attack_surface"]),
|
||||
"signed_update_integrity": ("software_integrity_protection", ["integrity"]),
|
||||
"firmware_software_authentication": ("software_integrity_protection", ["integrity"]),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
regs = {f: json.load(open(f, encoding="utf-8")) for f in REG_FILES}
|
||||
idx = {}
|
||||
for f, r in regs.items():
|
||||
for o in r.get("obligations", []):
|
||||
idx[o["id"]] = (f, o)
|
||||
|
||||
# realized_by aus dem Artefakt
|
||||
art = json.load(open("obligations/cross_domain_relationships.json", encoding="utf-8"))
|
||||
realized = {cid: set() for cid in CONSOLIDATE.values()}
|
||||
for rr in art["raw_results"]:
|
||||
if rr["relation"] == "SHARED_CAPABILITY" and rr.get("capability_name") in CONSOLIDATE:
|
||||
cid = CONSOLIDATE[rr["capability_name"]]
|
||||
realized[cid].update([rr["a"], rr["b"]])
|
||||
|
||||
def guidance_for(ids):
|
||||
seen, out = set(), []
|
||||
for oid in ids:
|
||||
if oid in idx:
|
||||
for g in idx[oid][1].get("guidance_basis", []):
|
||||
k = (g.get("source", ""), g.get("anchor", ""))
|
||||
if k not in seen:
|
||||
seen.add(k)
|
||||
out.append(g)
|
||||
return out
|
||||
|
||||
caps = []
|
||||
for cid in CAP_ORDER:
|
||||
obls = sorted(realized[cid])
|
||||
name, desc = CAP_META[cid]
|
||||
caps.append({"capability_id": cid, "name": name, "description": desc,
|
||||
"type": "technical_capability", "realized_by": obls, "realizes_count": len(obls),
|
||||
"guidance_basis": guidance_for(obls),
|
||||
"domains": sorted({idx[o][1].get("family", "") for o in obls if o in idx}),
|
||||
"provenance": {"source": "cross_domain_relationships.json SHARED_CAPABILITY"}})
|
||||
|
||||
capabilities = {
|
||||
"schema_version": "capability_layer_v1", "model": "Modell C (docs-src/development/capability_model_v1.md)",
|
||||
"note": "Capability = technische Faehigkeit (regulierungs-agnostisch). realized_by = Obligations, "
|
||||
"die sie erfuellt (n:m). guidance_basis hier KANONISCH hochgezogen aus den realisierten "
|
||||
"Obligations (die Obligation-Kopien bleiben vorerst als Legacy; Strip = Folge-Cleanup). "
|
||||
"Sicherheitsziele sind KEINE Capabilities -> cra_core.json.",
|
||||
"dropped": {"access_control": "OVERLAP (credential_confidentiality <-> sbom_confidentiality), nicht materialisiert"},
|
||||
"candidate_capabilities_followup": ["automatic_update_delivery", "update_rollback",
|
||||
"trusted_update_source", "hash_verification", "secure_boot", "least_functionality",
|
||||
"credential_storage"],
|
||||
"capabilities": caps}
|
||||
json.dump(capabilities, open("obligations/capabilities.json", "w", encoding="utf-8"),
|
||||
ensure_ascii=False, indent=1)
|
||||
|
||||
core = [
|
||||
{"id": "attack_surface_minimization", "name": "Minimierung der Angriffsflaeche", "family": "core",
|
||||
"description": "Das Produkt minimiert seine Angriffsflaeche: unnoetige Funktionen/Ports/Dienste/"
|
||||
"Schnittstellen sind deaktiviert (Least Functionality).",
|
||||
"tier": "LEGAL_MINIMUM", "source_role": "LEGAL_BASIS", "applicability": "universal",
|
||||
"objective_tags": ["attack_surface"],
|
||||
"legal_basis": [{"source": "CRA", "anchor": "Annex I Part I (2)(j)",
|
||||
"citation": "limit attack surfaces, including external interfaces"}],
|
||||
"guidance_basis": [{"source": "NIST", "anchor": "CM-7 Least Functionality", "role": "best_practice"}],
|
||||
"specialized_by": ["remote_access_attack_surface_min", "component_remote_interface_security"],
|
||||
"primary_implementation": "NIST CM-7", "citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"},
|
||||
{"id": "software_integrity_protection", "name": "Schutz der Software-/Firmware-Integritaet", "family": "core",
|
||||
"description": "Das Produkt schuetzt Integritaet und Authentizitaet von Software/Firmware "
|
||||
"(Manipulationserkennung, Secure Boot, Signaturpruefung, Runtime-Integritaet).",
|
||||
"tier": "LEGAL_MINIMUM", "source_role": "LEGAL_BASIS", "applicability": "universal",
|
||||
"objective_tags": ["integrity"],
|
||||
"legal_basis": [{"source": "CRA", "anchor": "Annex I Part I (2)(f)",
|
||||
"citation": "protect the integrity of stored, transmitted or processed data, software and configuration"}],
|
||||
"guidance_basis": [{"source": "NIST", "anchor": "SI-7 Software, Firmware, and Information Integrity", "role": "best_practice"}],
|
||||
"specialized_by": ["signed_update_integrity", "firmware_software_authentication"],
|
||||
"realized_by_capabilities": ["code_signing"],
|
||||
"primary_implementation": "NIST SI-7", "citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"},
|
||||
]
|
||||
json.dump({"schema_version": "obligation_registry_v1", "regulation": "CRA", "regulation_code": "CRA",
|
||||
"family": "core",
|
||||
"theme": "CORE Security Objectives (CRA Annex I als regulierungs-agnostische Sicherheitsziele)",
|
||||
"generated_by": "materialize_capabilities.py (#5b, Modell C)",
|
||||
"note": "CORE Legal Obligations = Sicherheitsziele (Modell C: KEINE eigene SecurityObjective-Klasse). "
|
||||
"DOMAIN-Obligations specializes-en hierauf. objective_tags = Vorwaerts-Kompat zu Modell B.",
|
||||
"citation_status": "pending_span_anchor", "obligations": core, "relationships": []},
|
||||
open("obligations/cra_core.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
|
||||
dirty = set()
|
||||
patched = []
|
||||
for oid, (coreid, tags) in SPECIALIZES.items():
|
||||
if oid in idx:
|
||||
f, o = idx[oid]
|
||||
o["specializes"] = coreid
|
||||
o["objective_tags"] = tags
|
||||
dirty.add(f)
|
||||
patched.append(oid)
|
||||
if "vuln_remediation_patching" in idx:
|
||||
f, o = idx["vuln_remediation_patching"]
|
||||
o["merged_into"] = "provide_security_updates"
|
||||
o["status"] = "deprecated_alias"
|
||||
o["merge_note"] = ("SAME_OBLIGATION (Cross-Domain-Review). Kanonisch: provide_security_updates "
|
||||
"((2)(c)/Art.13). ID bleibt als Alias aufloesbar; downstream provide_security_updates nutzen.")
|
||||
dirty.add(f)
|
||||
if "remote_access_data_export_protection" in idx:
|
||||
f, o = idx["remote_access_data_export_protection"]
|
||||
o["tier_note"] = ("Bleibt BEST_PRACTICE (NICHT LM) bis Data-Act/Export-Scope sauber ist (User #5b.6). "
|
||||
"Evtl. Capability-or-Procedure statt Obligation.")
|
||||
dirty.add(f)
|
||||
for f in dirty:
|
||||
json.dump(regs[f], open(f, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
|
||||
print("capabilities.json:", len(caps), "Capabilities")
|
||||
for c in caps:
|
||||
print(f" {c['capability_id']:30s} realizes {c['realizes_count']:2d} | guidance {len(c['guidance_basis'])} | {c['domains']}")
|
||||
print("cra_core.json: 2 CORE (attack_surface_minimization, software_integrity_protection)")
|
||||
print("specializes gepatcht:", patched)
|
||||
print("alias: vuln_remediation_patching -> provide_security_updates")
|
||||
print("dirty registries:", sorted(dirty))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -19,6 +19,20 @@ SCOPES = {
|
||||
"vuln": ["%schwachstellenbehandl%", "%schwachstellenmanagement%", "%vulnerability handling%",
|
||||
"%coordinated vulnerab%", "%vulnerability disclosure%", "%cvd-konzept%"],
|
||||
"auth": ["%authentisierung%", "%authentifizierung%", "%authentication%"],
|
||||
"logging": ["%logging%", "%protokollierung%", "%audit-log%", "%audit-trail%",
|
||||
"%ereignisprotokoll%", "%sicherheitsprotokoll%", "%audit-protokoll%",
|
||||
"%log-management%", "%sicherheitsereignis%protokoll%", "%audit-trail%"],
|
||||
"remote_access": ["%fernwartung%", "%fernzugriff%", "%fernzugang%", "%fernwartungs%",
|
||||
"%remote access%", "%remote maintenance%", "%remote management%",
|
||||
"%remote-wartung%", "%remote-zugriff%", "%remote-zugang%",
|
||||
"%sichere fernwartung%", "%fernsteuerung%"],
|
||||
"updates": ["%sicherheitsupdate%", "%security update%", "%sicherheits-update%",
|
||||
"%security patch%", "%sicherheitspatch%", "%patch-management%",
|
||||
"%patchmanagement%", "%patch management%", "%firmware-update%",
|
||||
"%firmware update%", "%software-update%", "%software update%",
|
||||
"%automatische aktualisierung%", "%update-mechanismus%",
|
||||
"%update-bereitstellung%", "%bereitstellung von updates%",
|
||||
"%sichere aktualisierung%", "%signierte update%", "%update-paket%"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user