feat: Framework Decomposition Engine + Composite Detection for Pass 0b

Adds a routing layer between Pass 0a and Pass 0b that classifies obligations
into atomic/compound/framework_container. Framework-container obligations
(e.g. "CCM-Praktiken fuer AIS") are decomposed into concrete sub-obligations
via an internal framework registry before Pass 0b composition.

- New: framework_decomposition.py with routing, matching, decomposition
- New: Framework registry (NIST SP 800-53, OWASP ASVS, CSA CCM) as JSON
- New: Composite detection flags on atomic controls (is_composite, atomicity)
- New: gen_meta fields: framework_ref, framework_domain, decomposition_source
- Integration: _route_and_compose() in run_pass0b() deterministic path
- 248 tests (198 decomposition + 50 framework), all passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-23 12:11:55 +01:00
parent 1a63f5857b
commit 48ca0a6bef
8 changed files with 2744 additions and 18 deletions

View File

@@ -0,0 +1,443 @@
{
"framework_id": "CSA_CCM",
"display_name": "Cloud Security Alliance CCM v4",
"license": {
"type": "restricted",
"rag_allowed": false,
"use_as_metadata": true,
"note": "Abstrahierte Struktur — keine Originaltexte uebernommen"
},
"domains": [
{
"domain_id": "AIS",
"title": "Application and Interface Security",
"aliases": ["ais", "application and interface security", "anwendungssicherheit", "schnittstellensicherheit"],
"keywords": ["application", "anwendung", "interface", "schnittstelle", "api", "web", "eingabevalidierung"],
"subcontrols": [
{
"subcontrol_id": "AIS-01",
"title": "Application Security Policy",
"statement": "Sicherheitsrichtlinien fuer Anwendungsentwicklung und Schnittstellenmanagement muessen definiert und angewendet werden.",
"keywords": ["policy", "richtlinie", "entwicklung"],
"action_hint": "document",
"object_hint": "Anwendungssicherheitsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "AIS-02",
"title": "Application Security Design",
"statement": "Sicherheitsanforderungen muessen in den Entwurf jeder Anwendung integriert werden.",
"keywords": ["design", "entwurf", "security by design"],
"action_hint": "implement",
"object_hint": "Sicherheitsanforderungen im Anwendungsentwurf",
"object_class": "process"
},
{
"subcontrol_id": "AIS-03",
"title": "Application Security Testing",
"statement": "Anwendungen muessen vor dem Deployment und regelmaessig auf Sicherheitsschwachstellen getestet werden.",
"keywords": ["testing", "test", "sast", "dast", "penetration"],
"action_hint": "test",
"object_hint": "Anwendungssicherheitstests",
"object_class": "process"
},
{
"subcontrol_id": "AIS-04",
"title": "Secure Development Practices",
"statement": "Sichere Entwicklungspraktiken (Code Review, Pair Programming, SAST) muessen fuer alle Entwicklungsprojekte gelten.",
"keywords": ["development", "entwicklung", "code review", "sast", "praktiken"],
"action_hint": "implement",
"object_hint": "Sichere Entwicklungspraktiken",
"object_class": "process"
},
{
"subcontrol_id": "AIS-05",
"title": "API Security",
"statement": "APIs muessen authentifiziert, autorisiert und gegen Missbrauch geschuetzt werden.",
"keywords": ["api", "schnittstelle", "authentifizierung", "rate limiting"],
"action_hint": "implement",
"object_hint": "API-Sicherheitskontrollen",
"object_class": "interface"
},
{
"subcontrol_id": "AIS-06",
"title": "Automated Application Security Testing",
"statement": "Automatisierte Sicherheitstests muessen in die CI/CD-Pipeline integriert werden.",
"keywords": ["automatisiert", "ci/cd", "pipeline", "sast", "dast"],
"action_hint": "configure",
"object_hint": "Automatisierte Sicherheitstests in CI/CD",
"object_class": "configuration"
}
]
},
{
"domain_id": "BCR",
"title": "Business Continuity and Resilience",
"aliases": ["bcr", "business continuity", "resilience", "geschaeftskontinuitaet", "resilienz"],
"keywords": ["continuity", "kontinuitaet", "resilience", "resilienz", "disaster", "recovery", "backup"],
"subcontrols": [
{
"subcontrol_id": "BCR-01",
"title": "Business Continuity Planning",
"statement": "Ein Geschaeftskontinuitaetsplan muss erstellt, dokumentiert und regelmaessig getestet werden.",
"keywords": ["plan", "kontinuitaet", "geschaeft"],
"action_hint": "document",
"object_hint": "Geschaeftskontinuitaetsplan",
"object_class": "policy"
},
{
"subcontrol_id": "BCR-02",
"title": "Risk Assessment for BCM",
"statement": "Risikobewertungen muessen fuer geschaeftskritische Prozesse durchgefuehrt werden.",
"keywords": ["risiko", "bewertung", "kritisch"],
"action_hint": "assess",
"object_hint": "BCM-Risikobewertung",
"object_class": "risk_artifact"
},
{
"subcontrol_id": "BCR-03",
"title": "Backup and Recovery",
"statement": "Datensicherungen muessen regelmaessig erstellt und Wiederherstellungstests durchgefuehrt werden.",
"keywords": ["backup", "sicherung", "wiederherstellung", "recovery"],
"action_hint": "maintain",
"object_hint": "Datensicherung und Wiederherstellung",
"object_class": "technical_control"
},
{
"subcontrol_id": "BCR-04",
"title": "Disaster Recovery Planning",
"statement": "Ein Disaster-Recovery-Plan muss dokumentiert und jaehrlich getestet werden.",
"keywords": ["disaster", "recovery", "katastrophe"],
"action_hint": "document",
"object_hint": "Disaster-Recovery-Plan",
"object_class": "policy"
}
]
},
{
"domain_id": "CCC",
"title": "Change Control and Configuration Management",
"aliases": ["ccc", "change control", "configuration management", "aenderungsmanagement", "konfigurationsmanagement"],
"keywords": ["change", "aenderung", "konfiguration", "configuration", "release", "deployment"],
"subcontrols": [
{
"subcontrol_id": "CCC-01",
"title": "Change Management Policy",
"statement": "Ein Aenderungsmanagement-Prozess muss definiert und fuer alle Aenderungen angewendet werden.",
"keywords": ["policy", "richtlinie", "aenderung"],
"action_hint": "document",
"object_hint": "Aenderungsmanagement-Richtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "CCC-02",
"title": "Change Testing",
"statement": "Aenderungen muessen vor der Produktivsetzung getestet und genehmigt werden.",
"keywords": ["test", "genehmigung", "approval"],
"action_hint": "test",
"object_hint": "Aenderungstests",
"object_class": "process"
},
{
"subcontrol_id": "CCC-03",
"title": "Configuration Baseline",
"statement": "Basiskonfigurationen fuer alle Systeme muessen definiert und dokumentiert werden.",
"keywords": ["baseline", "basis", "standard"],
"action_hint": "define",
"object_hint": "Konfigurationsbaseline",
"object_class": "configuration"
}
]
},
{
"domain_id": "CEK",
"title": "Cryptography, Encryption and Key Management",
"aliases": ["cek", "cryptography", "encryption", "key management", "kryptographie", "verschluesselung", "schluesselverwaltung"],
"keywords": ["kryptographie", "verschluesselung", "schluessel", "key", "encryption", "certificate", "zertifikat"],
"subcontrols": [
{
"subcontrol_id": "CEK-01",
"title": "Encryption Policy",
"statement": "Verschluesselungsrichtlinien muessen definiert werden, die Algorithmen, Schluessellaengen und Einsatzbereiche festlegen.",
"keywords": ["policy", "richtlinie", "algorithmus"],
"action_hint": "document",
"object_hint": "Verschluesselungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "CEK-02",
"title": "Key Management",
"statement": "Kryptographische Schluessel muessen ueber ihren Lebenszyklus sicher verwaltet werden.",
"keywords": ["key", "schluessel", "management", "lebenszyklus"],
"action_hint": "maintain",
"object_hint": "Schluesselverwaltung",
"object_class": "cryptographic_control"
},
{
"subcontrol_id": "CEK-03",
"title": "Data Encryption",
"statement": "Sensible Daten muessen bei Speicherung und Uebertragung verschluesselt werden.",
"keywords": ["data", "daten", "speicherung", "uebertragung"],
"action_hint": "encrypt",
"object_hint": "Datenverschluesselung",
"object_class": "cryptographic_control"
}
]
},
{
"domain_id": "DSP",
"title": "Data Security and Privacy",
"aliases": ["dsp", "data security", "privacy", "datensicherheit", "datenschutz"],
"keywords": ["datenschutz", "datensicherheit", "privacy", "data security", "pii", "personenbezogen", "dsgvo"],
"subcontrols": [
{
"subcontrol_id": "DSP-01",
"title": "Data Classification",
"statement": "Daten muessen nach Sensibilitaet klassifiziert und entsprechend geschuetzt werden.",
"keywords": ["klassifizierung", "sensibilitaet", "classification"],
"action_hint": "define",
"object_hint": "Datenklassifizierung",
"object_class": "data"
},
{
"subcontrol_id": "DSP-02",
"title": "Data Inventory",
"statement": "Ein Dateninventar muss gefuehrt werden, das alle Verarbeitungen personenbezogener Daten dokumentiert.",
"keywords": ["inventar", "verzeichnis", "verarbeitung", "vvt"],
"action_hint": "maintain",
"object_hint": "Dateninventar",
"object_class": "register"
},
{
"subcontrol_id": "DSP-03",
"title": "Data Retention and Deletion",
"statement": "Aufbewahrungsfristen muessen definiert und Daten nach Ablauf sicher geloescht werden.",
"keywords": ["retention", "aufbewahrung", "loeschung", "frist"],
"action_hint": "delete",
"object_hint": "Datenloeschung nach Frist",
"object_class": "data"
},
{
"subcontrol_id": "DSP-04",
"title": "Privacy Impact Assessment",
"statement": "Datenschutz-Folgenabschaetzungen muessen fuer risikoreiche Verarbeitungen durchgefuehrt werden.",
"keywords": ["dsfa", "pia", "folgenabschaetzung", "impact"],
"action_hint": "assess",
"object_hint": "Datenschutz-Folgenabschaetzung",
"object_class": "risk_artifact"
},
{
"subcontrol_id": "DSP-05",
"title": "Data Subject Rights",
"statement": "Verfahren zur Bearbeitung von Betroffenenrechten muessen implementiert werden.",
"keywords": ["betroffenenrechte", "auskunft", "loeschung", "data subject"],
"action_hint": "implement",
"object_hint": "Betroffenenrechte-Verfahren",
"object_class": "process"
}
]
},
{
"domain_id": "GRC",
"title": "Governance, Risk and Compliance",
"aliases": ["grc", "governance", "risk", "compliance", "risikomanagement"],
"keywords": ["governance", "risiko", "compliance", "management", "policy", "richtlinie"],
"subcontrols": [
{
"subcontrol_id": "GRC-01",
"title": "Information Security Program",
"statement": "Ein umfassendes Informationssicherheitsprogramm muss etabliert und aufrechterhalten werden.",
"keywords": ["programm", "sicherheit", "information"],
"action_hint": "maintain",
"object_hint": "Informationssicherheitsprogramm",
"object_class": "policy"
},
{
"subcontrol_id": "GRC-02",
"title": "Risk Management Program",
"statement": "Ein Risikomanagement-Programm muss implementiert werden, das Identifikation, Bewertung und Behandlung umfasst.",
"keywords": ["risiko", "management", "bewertung", "behandlung"],
"action_hint": "implement",
"object_hint": "Risikomanagement-Programm",
"object_class": "process"
},
{
"subcontrol_id": "GRC-03",
"title": "Compliance Monitoring",
"statement": "Die Einhaltung regulatorischer und vertraglicher Anforderungen muss ueberwacht werden.",
"keywords": ["compliance", "einhaltung", "regulatorisch", "ueberwachung"],
"action_hint": "monitor",
"object_hint": "Compliance-Ueberwachung",
"object_class": "process"
}
]
},
{
"domain_id": "IAM",
"title": "Identity and Access Management",
"aliases": ["iam", "identity", "access management", "identitaetsmanagement", "zugriffsverwaltung"],
"keywords": ["identitaet", "zugriff", "identity", "access", "authentifizierung", "autorisierung", "sso"],
"subcontrols": [
{
"subcontrol_id": "IAM-01",
"title": "Identity and Access Policy",
"statement": "Identitaets- und Zugriffsmanagement-Richtlinien muessen definiert werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "IAM-Richtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "IAM-02",
"title": "Strong Authentication",
"statement": "Starke Authentifizierung (MFA) muss fuer administrative und sicherheitskritische Zugriffe gefordert werden.",
"keywords": ["mfa", "stark", "authentifizierung", "admin"],
"action_hint": "implement",
"object_hint": "Starke Authentifizierung",
"object_class": "technical_control"
},
{
"subcontrol_id": "IAM-03",
"title": "Identity Lifecycle Management",
"statement": "Identitaeten muessen ueber ihren gesamten Lebenszyklus verwaltet werden.",
"keywords": ["lifecycle", "lebenszyklus", "onboarding", "offboarding"],
"action_hint": "maintain",
"object_hint": "Identitaets-Lebenszyklus",
"object_class": "account"
},
{
"subcontrol_id": "IAM-04",
"title": "Access Review",
"statement": "Zugriffsrechte muessen regelmaessig ueberprueft und ueberschuessige Rechte entzogen werden.",
"keywords": ["review", "ueberpruefen", "rechte", "rezertifizierung"],
"action_hint": "review",
"object_hint": "Zugriffsrechte-Review",
"object_class": "access_control"
}
]
},
{
"domain_id": "LOG",
"title": "Logging and Monitoring",
"aliases": ["log", "logging", "monitoring", "protokollierung", "ueberwachung"],
"keywords": ["logging", "monitoring", "protokollierung", "ueberwachung", "siem", "alarm"],
"subcontrols": [
{
"subcontrol_id": "LOG-01",
"title": "Logging Policy",
"statement": "Protokollierungs-Richtlinien muessen definiert werden, die Umfang und Aufbewahrung festlegen.",
"keywords": ["policy", "richtlinie", "umfang", "aufbewahrung"],
"action_hint": "document",
"object_hint": "Protokollierungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "LOG-02",
"title": "Security Event Logging",
"statement": "Sicherheitsrelevante Ereignisse muessen erfasst und zentral gespeichert werden.",
"keywords": ["event", "ereignis", "sicherheit", "zentral"],
"action_hint": "configure",
"object_hint": "Sicherheits-Event-Logging",
"object_class": "configuration"
},
{
"subcontrol_id": "LOG-03",
"title": "Monitoring and Alerting",
"statement": "Sicherheitsrelevante Logs muessen ueberwacht und bei Anomalien Alarme ausgeloest werden.",
"keywords": ["monitoring", "alerting", "alarm", "anomalie"],
"action_hint": "monitor",
"object_hint": "Log-Ueberwachung und Alarmierung",
"object_class": "technical_control"
}
]
},
{
"domain_id": "SEF",
"title": "Security Incident Management",
"aliases": ["sef", "security incident", "incident management", "vorfallmanagement", "sicherheitsvorfall"],
"keywords": ["vorfall", "incident", "sicherheitsvorfall", "reaktion", "response", "meldung"],
"subcontrols": [
{
"subcontrol_id": "SEF-01",
"title": "Incident Management Policy",
"statement": "Ein Vorfallmanagement-Prozess muss definiert, dokumentiert und getestet werden.",
"keywords": ["policy", "richtlinie", "prozess"],
"action_hint": "document",
"object_hint": "Vorfallmanagement-Richtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "SEF-02",
"title": "Incident Response Team",
"statement": "Ein Incident-Response-Team muss benannt und geschult werden.",
"keywords": ["team", "response", "schulung"],
"action_hint": "define",
"object_hint": "Incident-Response-Team",
"object_class": "role"
},
{
"subcontrol_id": "SEF-03",
"title": "Incident Reporting",
"statement": "Sicherheitsvorfaelle muessen innerhalb definierter Fristen an zustaendige Stellen gemeldet werden.",
"keywords": ["reporting", "meldung", "frist", "behoerde"],
"action_hint": "report",
"object_hint": "Vorfallmeldung",
"object_class": "incident"
},
{
"subcontrol_id": "SEF-04",
"title": "Incident Lessons Learned",
"statement": "Nach jedem Vorfall muss eine Nachbereitung mit Lessons Learned durchgefuehrt werden.",
"keywords": ["lessons learned", "nachbereitung", "verbesserung"],
"action_hint": "review",
"object_hint": "Vorfall-Nachbereitung",
"object_class": "record"
}
]
},
{
"domain_id": "TVM",
"title": "Threat and Vulnerability Management",
"aliases": ["tvm", "threat", "vulnerability", "schwachstelle", "bedrohung", "schwachstellenmanagement"],
"keywords": ["schwachstelle", "vulnerability", "threat", "bedrohung", "patch", "scan"],
"subcontrols": [
{
"subcontrol_id": "TVM-01",
"title": "Vulnerability Management Policy",
"statement": "Schwachstellenmanagement-Richtlinien muessen definiert und umgesetzt werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Schwachstellenmanagement-Richtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "TVM-02",
"title": "Vulnerability Scanning",
"statement": "Systeme muessen regelmaessig auf Schwachstellen gescannt werden.",
"keywords": ["scan", "scanning", "regelmaessig"],
"action_hint": "test",
"object_hint": "Schwachstellenscan",
"object_class": "system"
},
{
"subcontrol_id": "TVM-03",
"title": "Vulnerability Remediation",
"statement": "Erkannte Schwachstellen muessen priorisiert und innerhalb definierter Fristen behoben werden.",
"keywords": ["remediation", "behebung", "frist", "priorisierung"],
"action_hint": "remediate",
"object_hint": "Schwachstellenbehebung",
"object_class": "system"
},
{
"subcontrol_id": "TVM-04",
"title": "Penetration Testing",
"statement": "Regelmaessige Penetrationstests muessen durchgefuehrt werden.",
"keywords": ["penetration", "pentest", "test"],
"action_hint": "test",
"object_hint": "Penetrationstest",
"object_class": "system"
}
]
}
]
}

View File

@@ -0,0 +1,514 @@
{
"framework_id": "NIST_SP800_53",
"display_name": "NIST SP 800-53 Rev. 5",
"license": {
"type": "public_domain",
"rag_allowed": true,
"use_as_metadata": true
},
"domains": [
{
"domain_id": "AC",
"title": "Access Control",
"aliases": ["access control", "zugriffskontrolle", "zugriffssteuerung"],
"keywords": ["access", "zugriff", "berechtigung", "authorization", "autorisierung"],
"subcontrols": [
{
"subcontrol_id": "AC-1",
"title": "Access Control Policy and Procedures",
"statement": "Zugriffskontrollrichtlinien und -verfahren muessen definiert, dokumentiert und regelmaessig ueberprueft werden.",
"keywords": ["policy", "richtlinie", "verfahren", "procedures"],
"action_hint": "document",
"object_hint": "Zugriffskontrollrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "AC-2",
"title": "Account Management",
"statement": "Benutzerkonten muessen ueber ihren gesamten Lebenszyklus verwaltet werden: Erstellung, Aktivierung, Aenderung, Deaktivierung und Loeschung.",
"keywords": ["account", "konto", "benutzer", "lifecycle", "lebenszyklus"],
"action_hint": "maintain",
"object_hint": "Benutzerkontenverwaltung",
"object_class": "account"
},
{
"subcontrol_id": "AC-3",
"title": "Access Enforcement",
"statement": "Der Zugriff auf Systemressourcen muss gemaess der definierten Zugriffskontrollrichtlinie durchgesetzt werden.",
"keywords": ["enforcement", "durchsetzung", "ressourcen", "system"],
"action_hint": "restrict_access",
"object_hint": "Zugriffsdurchsetzung",
"object_class": "access_control"
},
{
"subcontrol_id": "AC-5",
"title": "Separation of Duties",
"statement": "Aufgabentrennung muss definiert und durchgesetzt werden, um Interessenkonflikte und Missbrauch zu verhindern.",
"keywords": ["separation", "trennung", "duties", "aufgaben", "funktionstrennung"],
"action_hint": "define",
"object_hint": "Aufgabentrennung",
"object_class": "role"
},
{
"subcontrol_id": "AC-6",
"title": "Least Privilege",
"statement": "Zugriffsrechte muessen nach dem Prinzip der minimalen Rechte vergeben werden.",
"keywords": ["least privilege", "minimal", "rechte", "privileg"],
"action_hint": "restrict_access",
"object_hint": "Minimale Rechtevergabe",
"object_class": "access_control"
},
{
"subcontrol_id": "AC-7",
"title": "Unsuccessful Logon Attempts",
"statement": "Fehlgeschlagene Anmeldeversuche muessen begrenzt und ueberwacht werden.",
"keywords": ["logon", "anmeldung", "fehlgeschlagen", "sperre", "lockout"],
"action_hint": "monitor",
"object_hint": "Anmeldeversuchsueberwachung",
"object_class": "technical_control"
},
{
"subcontrol_id": "AC-17",
"title": "Remote Access",
"statement": "Fernzugriff muss autorisiert, ueberwacht und verschluesselt werden.",
"keywords": ["remote", "fern", "vpn", "fernzugriff"],
"action_hint": "configure",
"object_hint": "Fernzugriffskonfiguration",
"object_class": "technical_control"
}
]
},
{
"domain_id": "AU",
"title": "Audit and Accountability",
"aliases": ["audit", "protokollierung", "accountability", "rechenschaftspflicht"],
"keywords": ["audit", "log", "protokoll", "nachvollziehbarkeit", "logging"],
"subcontrols": [
{
"subcontrol_id": "AU-1",
"title": "Audit Policy and Procedures",
"statement": "Audit- und Protokollierungsrichtlinien muessen definiert und regelmaessig ueberprueft werden.",
"keywords": ["policy", "richtlinie", "audit"],
"action_hint": "document",
"object_hint": "Auditrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "AU-2",
"title": "Event Logging",
"statement": "Sicherheitsrelevante Ereignisse muessen identifiziert und protokolliert werden.",
"keywords": ["event", "ereignis", "logging", "protokollierung"],
"action_hint": "configure",
"object_hint": "Ereignisprotokollierung",
"object_class": "configuration"
},
{
"subcontrol_id": "AU-3",
"title": "Content of Audit Records",
"statement": "Audit-Eintraege muessen ausreichende Informationen enthalten: Zeitstempel, Quelle, Ergebnis, Identitaet.",
"keywords": ["content", "inhalt", "record", "eintrag"],
"action_hint": "define",
"object_hint": "Audit-Eintragsformat",
"object_class": "record"
},
{
"subcontrol_id": "AU-6",
"title": "Audit Record Review and Reporting",
"statement": "Audit-Eintraege muessen regelmaessig ueberprueft und bei Anomalien berichtet werden.",
"keywords": ["review", "ueberpruefen", "reporting", "anomalie"],
"action_hint": "review",
"object_hint": "Audit-Ueberpruefung",
"object_class": "record"
},
{
"subcontrol_id": "AU-9",
"title": "Protection of Audit Information",
"statement": "Audit-Daten muessen vor unbefugtem Zugriff, Aenderung und Loeschung geschuetzt werden.",
"keywords": ["schutz", "protection", "integritaet", "integrity"],
"action_hint": "implement",
"object_hint": "Audit-Datenschutz",
"object_class": "technical_control"
}
]
},
{
"domain_id": "AT",
"title": "Awareness and Training",
"aliases": ["awareness", "training", "schulung", "sensibilisierung"],
"keywords": ["training", "schulung", "awareness", "sensibilisierung", "weiterbildung"],
"subcontrols": [
{
"subcontrol_id": "AT-1",
"title": "Policy and Procedures",
"statement": "Schulungs- und Sensibilisierungsrichtlinien muessen definiert und regelmaessig aktualisiert werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Schulungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "AT-2",
"title": "Literacy Training and Awareness",
"statement": "Alle Mitarbeiter muessen regelmaessig Sicherheitsschulungen erhalten.",
"keywords": ["mitarbeiter", "schulung", "sicherheit"],
"action_hint": "train",
"object_hint": "Sicherheitsschulung",
"object_class": "training"
},
{
"subcontrol_id": "AT-3",
"title": "Role-Based Training",
"statement": "Rollenbasierte Sicherheitsschulungen muessen fuer Mitarbeiter mit besonderen Sicherheitsaufgaben durchgefuehrt werden.",
"keywords": ["rollenbasiert", "role-based", "speziell"],
"action_hint": "train",
"object_hint": "Rollenbasierte Sicherheitsschulung",
"object_class": "training"
}
]
},
{
"domain_id": "CM",
"title": "Configuration Management",
"aliases": ["configuration management", "konfigurationsmanagement", "konfiguration"],
"keywords": ["konfiguration", "configuration", "baseline", "haertung", "hardening"],
"subcontrols": [
{
"subcontrol_id": "CM-1",
"title": "Policy and Procedures",
"statement": "Konfigurationsmanagement-Richtlinien muessen dokumentiert und gepflegt werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Konfigurationsmanagement-Richtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "CM-2",
"title": "Baseline Configuration",
"statement": "Basiskonfigurationen fuer Systeme muessen definiert, dokumentiert und gepflegt werden.",
"keywords": ["baseline", "basis", "standard"],
"action_hint": "define",
"object_hint": "Basiskonfiguration",
"object_class": "configuration"
},
{
"subcontrol_id": "CM-6",
"title": "Configuration Settings",
"statement": "Sicherheitsrelevante Konfigurationseinstellungen muessen definiert und durchgesetzt werden.",
"keywords": ["settings", "einstellungen", "sicherheit"],
"action_hint": "configure",
"object_hint": "Sicherheitskonfiguration",
"object_class": "configuration"
},
{
"subcontrol_id": "CM-7",
"title": "Least Functionality",
"statement": "Systeme muessen so konfiguriert werden, dass nur notwendige Funktionen aktiv sind.",
"keywords": ["least functionality", "minimal", "dienste", "ports"],
"action_hint": "configure",
"object_hint": "Minimalkonfiguration",
"object_class": "configuration"
},
{
"subcontrol_id": "CM-8",
"title": "System Component Inventory",
"statement": "Ein Inventar aller Systemkomponenten muss gefuehrt und aktuell gehalten werden.",
"keywords": ["inventar", "inventory", "komponenten", "assets"],
"action_hint": "maintain",
"object_hint": "Systemkomponenten-Inventar",
"object_class": "register"
}
]
},
{
"domain_id": "IA",
"title": "Identification and Authentication",
"aliases": ["identification", "authentication", "identifikation", "authentifizierung"],
"keywords": ["authentifizierung", "identifikation", "identity", "passwort", "mfa", "credential"],
"subcontrols": [
{
"subcontrol_id": "IA-1",
"title": "Policy and Procedures",
"statement": "Identifikations- und Authentifizierungsrichtlinien muessen dokumentiert und regelmaessig ueberprueft werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Authentifizierungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "IA-2",
"title": "Identification and Authentication",
"statement": "Benutzer und Geraete muessen eindeutig identifiziert und authentifiziert werden.",
"keywords": ["benutzer", "geraete", "identifizierung"],
"action_hint": "implement",
"object_hint": "Benutzerauthentifizierung",
"object_class": "technical_control"
},
{
"subcontrol_id": "IA-2(1)",
"title": "Multi-Factor Authentication",
"statement": "Multi-Faktor-Authentifizierung muss fuer privilegierte Konten implementiert werden.",
"keywords": ["mfa", "multi-faktor", "zwei-faktor", "2fa"],
"action_hint": "implement",
"object_hint": "Multi-Faktor-Authentifizierung",
"object_class": "technical_control"
},
{
"subcontrol_id": "IA-5",
"title": "Authenticator Management",
"statement": "Authentifizierungsmittel (Passwoerter, Token, Zertifikate) muessen sicher verwaltet werden.",
"keywords": ["passwort", "token", "zertifikat", "credential"],
"action_hint": "maintain",
"object_hint": "Authentifizierungsmittel-Verwaltung",
"object_class": "technical_control"
}
]
},
{
"domain_id": "IR",
"title": "Incident Response",
"aliases": ["incident response", "vorfallbehandlung", "vorfallreaktion", "incident management"],
"keywords": ["vorfall", "incident", "reaktion", "response", "breach", "sicherheitsvorfall"],
"subcontrols": [
{
"subcontrol_id": "IR-1",
"title": "Policy and Procedures",
"statement": "Vorfallreaktionsrichtlinien und -verfahren muessen definiert und regelmaessig aktualisiert werden.",
"keywords": ["policy", "richtlinie", "verfahren"],
"action_hint": "document",
"object_hint": "Vorfallreaktionsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "IR-2",
"title": "Incident Response Training",
"statement": "Mitarbeiter muessen regelmaessig in der Vorfallreaktion geschult werden.",
"keywords": ["training", "schulung"],
"action_hint": "train",
"object_hint": "Vorfallreaktionsschulung",
"object_class": "training"
},
{
"subcontrol_id": "IR-4",
"title": "Incident Handling",
"statement": "Ein strukturierter Prozess fuer die Vorfallbehandlung muss implementiert werden: Erkennung, Analyse, Eindaemmung, Behebung.",
"keywords": ["handling", "behandlung", "erkennung", "eindaemmung"],
"action_hint": "implement",
"object_hint": "Vorfallbehandlungsprozess",
"object_class": "process"
},
{
"subcontrol_id": "IR-5",
"title": "Incident Monitoring",
"statement": "Sicherheitsvorfaelle muessen kontinuierlich ueberwacht und verfolgt werden.",
"keywords": ["monitoring", "ueberwachung", "tracking"],
"action_hint": "monitor",
"object_hint": "Vorfallsueberwachung",
"object_class": "incident"
},
{
"subcontrol_id": "IR-6",
"title": "Incident Reporting",
"statement": "Sicherheitsvorfaelle muessen innerhalb definierter Fristen an die zustaendigen Stellen gemeldet werden.",
"keywords": ["reporting", "meldung", "melden", "frist"],
"action_hint": "report",
"object_hint": "Vorfallmeldung",
"object_class": "incident"
},
{
"subcontrol_id": "IR-8",
"title": "Incident Response Plan",
"statement": "Ein Vorfallreaktionsplan muss dokumentiert und regelmaessig getestet werden.",
"keywords": ["plan", "dokumentation", "test"],
"action_hint": "document",
"object_hint": "Vorfallreaktionsplan",
"object_class": "policy"
}
]
},
{
"domain_id": "RA",
"title": "Risk Assessment",
"aliases": ["risk assessment", "risikobewertung", "risikoanalyse"],
"keywords": ["risiko", "risk", "bewertung", "assessment", "analyse", "bedrohung", "threat"],
"subcontrols": [
{
"subcontrol_id": "RA-1",
"title": "Policy and Procedures",
"statement": "Risikobewertungsrichtlinien muessen dokumentiert und regelmaessig aktualisiert werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Risikobewertungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "RA-3",
"title": "Risk Assessment",
"statement": "Regelmaessige Risikobewertungen muessen durchgefuehrt und dokumentiert werden.",
"keywords": ["bewertung", "assessment", "regelmaessig"],
"action_hint": "assess",
"object_hint": "Risikobewertung",
"object_class": "risk_artifact"
},
{
"subcontrol_id": "RA-5",
"title": "Vulnerability Monitoring and Scanning",
"statement": "Systeme muessen regelmaessig auf Schwachstellen gescannt und ueberwacht werden.",
"keywords": ["vulnerability", "schwachstelle", "scan", "monitoring"],
"action_hint": "monitor",
"object_hint": "Schwachstellenueberwachung",
"object_class": "system"
}
]
},
{
"domain_id": "SC",
"title": "System and Communications Protection",
"aliases": ["system protection", "communications protection", "kommunikationsschutz", "systemschutz"],
"keywords": ["verschluesselung", "encryption", "tls", "netzwerk", "network", "kommunikation", "firewall"],
"subcontrols": [
{
"subcontrol_id": "SC-1",
"title": "Policy and Procedures",
"statement": "System- und Kommunikationsschutzrichtlinien muessen dokumentiert und aktuell gehalten werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Kommunikationsschutzrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "SC-7",
"title": "Boundary Protection",
"statement": "Netzwerkgrenzen muessen durch Firewall-Regeln und Zugangskontrollen geschuetzt werden.",
"keywords": ["boundary", "grenze", "firewall", "netzwerk"],
"action_hint": "implement",
"object_hint": "Netzwerkgrenzschutz",
"object_class": "technical_control"
},
{
"subcontrol_id": "SC-8",
"title": "Transmission Confidentiality and Integrity",
"statement": "Daten muessen bei der Uebertragung durch Verschluesselung geschuetzt werden.",
"keywords": ["transmission", "uebertragung", "verschluesselung", "tls"],
"action_hint": "encrypt",
"object_hint": "Uebertragungsverschluesselung",
"object_class": "cryptographic_control"
},
{
"subcontrol_id": "SC-12",
"title": "Cryptographic Key Establishment and Management",
"statement": "Kryptographische Schluessel muessen sicher erzeugt, verteilt, gespeichert und widerrufen werden.",
"keywords": ["key", "schluessel", "kryptographie", "management"],
"action_hint": "maintain",
"object_hint": "Schluesselverwaltung",
"object_class": "cryptographic_control"
},
{
"subcontrol_id": "SC-13",
"title": "Cryptographic Protection",
"statement": "Kryptographische Mechanismen muessen gemaess anerkannten Standards implementiert werden.",
"keywords": ["kryptographie", "verschluesselung", "standard"],
"action_hint": "implement",
"object_hint": "Kryptographischer Schutz",
"object_class": "cryptographic_control"
}
]
},
{
"domain_id": "SI",
"title": "System and Information Integrity",
"aliases": ["system integrity", "information integrity", "systemintegritaet", "informationsintegritaet"],
"keywords": ["integritaet", "integrity", "malware", "patch", "flaw", "schwachstelle"],
"subcontrols": [
{
"subcontrol_id": "SI-1",
"title": "Policy and Procedures",
"statement": "System- und Informationsintegritaetsrichtlinien muessen dokumentiert und regelmaessig ueberprueft werden.",
"keywords": ["policy", "richtlinie"],
"action_hint": "document",
"object_hint": "Integritaetsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "SI-2",
"title": "Flaw Remediation",
"statement": "Bekannte Schwachstellen muessen innerhalb definierter Fristen behoben werden.",
"keywords": ["flaw", "schwachstelle", "patch", "behebung", "remediation"],
"action_hint": "remediate",
"object_hint": "Schwachstellenbehebung",
"object_class": "system"
},
{
"subcontrol_id": "SI-3",
"title": "Malicious Code Protection",
"statement": "Systeme muessen vor Schadsoftware geschuetzt werden durch Erkennung und Abwehrmechanismen.",
"keywords": ["malware", "schadsoftware", "antivirus", "erkennung"],
"action_hint": "implement",
"object_hint": "Schadsoftwareschutz",
"object_class": "technical_control"
},
{
"subcontrol_id": "SI-4",
"title": "System Monitoring",
"statement": "Systeme muessen kontinuierlich auf Sicherheitsereignisse und Anomalien ueberwacht werden.",
"keywords": ["monitoring", "ueberwachung", "anomalie", "siem"],
"action_hint": "monitor",
"object_hint": "Systemueberwachung",
"object_class": "system"
},
{
"subcontrol_id": "SI-5",
"title": "Security Alerts and Advisories",
"statement": "Sicherheitswarnungen muessen empfangen, bewertet und darauf reagiert werden.",
"keywords": ["alert", "warnung", "advisory", "cve"],
"action_hint": "monitor",
"object_hint": "Sicherheitswarnungen",
"object_class": "incident"
}
]
},
{
"domain_id": "SA",
"title": "System and Services Acquisition",
"aliases": ["system acquisition", "services acquisition", "systembeschaffung", "secure development"],
"keywords": ["beschaffung", "acquisition", "entwicklung", "development", "lieferkette", "supply chain"],
"subcontrols": [
{
"subcontrol_id": "SA-1",
"title": "Policy and Procedures",
"statement": "Beschaffungsrichtlinien mit Sicherheitsanforderungen muessen dokumentiert werden.",
"keywords": ["policy", "richtlinie", "beschaffung"],
"action_hint": "document",
"object_hint": "Beschaffungsrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "SA-8",
"title": "Security and Privacy Engineering Principles",
"statement": "Sicherheits- und Datenschutzprinzipien muessen in die Systementwicklung integriert werden.",
"keywords": ["engineering", "development", "prinzipien", "design"],
"action_hint": "implement",
"object_hint": "Security-by-Design-Prinzipien",
"object_class": "process"
},
{
"subcontrol_id": "SA-11",
"title": "Developer Testing and Evaluation",
"statement": "Entwickler muessen Sicherheitstests und Code-Reviews durchfuehren.",
"keywords": ["testing", "test", "code review", "evaluation"],
"action_hint": "test",
"object_hint": "Entwickler-Sicherheitstests",
"object_class": "process"
},
{
"subcontrol_id": "SA-12",
"title": "Supply Chain Protection",
"statement": "Lieferkettenrisiken muessen bewertet und Schutzmassnahmen implementiert werden.",
"keywords": ["supply chain", "lieferkette", "third party", "drittanbieter"],
"action_hint": "assess",
"object_hint": "Lieferkettenrisikobewertung",
"object_class": "risk_artifact"
}
]
}
]
}

View File

@@ -0,0 +1,353 @@
{
"framework_id": "OWASP_ASVS",
"display_name": "OWASP Application Security Verification Standard 4.0",
"license": {
"type": "cc_by_sa_4",
"rag_allowed": true,
"use_as_metadata": true
},
"domains": [
{
"domain_id": "V1",
"title": "Architecture, Design and Threat Modeling",
"aliases": ["architecture", "architektur", "design", "threat modeling", "bedrohungsmodellierung"],
"keywords": ["architektur", "design", "threat model", "bedrohung", "modellierung"],
"subcontrols": [
{
"subcontrol_id": "V1.1",
"title": "Secure Software Development Lifecycle",
"statement": "Ein sicherer Softwareentwicklungs-Lebenszyklus (SSDLC) muss definiert und angewendet werden.",
"keywords": ["sdlc", "lifecycle", "lebenszyklus", "entwicklung"],
"action_hint": "implement",
"object_hint": "Sicherer Entwicklungs-Lebenszyklus",
"object_class": "process"
},
{
"subcontrol_id": "V1.2",
"title": "Authentication Architecture",
"statement": "Die Authentifizierungsarchitektur muss dokumentiert und regelmaessig ueberprueft werden.",
"keywords": ["authentication", "authentifizierung", "architektur"],
"action_hint": "document",
"object_hint": "Authentifizierungsarchitektur",
"object_class": "policy"
},
{
"subcontrol_id": "V1.4",
"title": "Access Control Architecture",
"statement": "Die Zugriffskontrollarchitektur muss dokumentiert und zentral durchgesetzt werden.",
"keywords": ["access control", "zugriffskontrolle", "architektur"],
"action_hint": "document",
"object_hint": "Zugriffskontrollarchitektur",
"object_class": "policy"
},
{
"subcontrol_id": "V1.5",
"title": "Input and Output Architecture",
"statement": "Eingabe- und Ausgabevalidierung muss architektonisch verankert und durchgaengig angewendet werden.",
"keywords": ["input", "output", "eingabe", "ausgabe", "validierung"],
"action_hint": "implement",
"object_hint": "Ein-/Ausgabevalidierung",
"object_class": "technical_control"
},
{
"subcontrol_id": "V1.6",
"title": "Cryptographic Architecture",
"statement": "Kryptographische Mechanismen muessen architektonisch definiert und standardisiert sein.",
"keywords": ["crypto", "kryptographie", "verschluesselung"],
"action_hint": "define",
"object_hint": "Kryptographie-Architektur",
"object_class": "cryptographic_control"
}
]
},
{
"domain_id": "V2",
"title": "Authentication",
"aliases": ["authentication", "authentifizierung", "anmeldung", "login"],
"keywords": ["authentication", "authentifizierung", "passwort", "login", "anmeldung", "credential"],
"subcontrols": [
{
"subcontrol_id": "V2.1",
"title": "Password Security",
"statement": "Passwortrichtlinien muessen Mindestlaenge, Komplexitaet und Sperrmechanismen definieren.",
"keywords": ["passwort", "password", "laenge", "komplexitaet"],
"action_hint": "define",
"object_hint": "Passwortrichtlinie",
"object_class": "policy"
},
{
"subcontrol_id": "V2.2",
"title": "General Authenticator Security",
"statement": "Authentifizierungsmittel muessen sicher gespeichert und uebertragen werden.",
"keywords": ["authenticator", "credential", "speicherung"],
"action_hint": "implement",
"object_hint": "Sichere Credential-Verwaltung",
"object_class": "technical_control"
},
{
"subcontrol_id": "V2.7",
"title": "Out-of-Band Verification",
"statement": "Out-of-Band-Verifikationsmechanismen muessen sicher implementiert werden.",
"keywords": ["oob", "out-of-band", "sms", "push"],
"action_hint": "implement",
"object_hint": "Out-of-Band-Verifikation",
"object_class": "technical_control"
},
{
"subcontrol_id": "V2.8",
"title": "Multi-Factor Authentication",
"statement": "Multi-Faktor-Authentifizierung muss fuer sicherheitskritische Funktionen verfuegbar sein.",
"keywords": ["mfa", "multi-faktor", "totp", "fido"],
"action_hint": "implement",
"object_hint": "Multi-Faktor-Authentifizierung",
"object_class": "technical_control"
}
]
},
{
"domain_id": "V3",
"title": "Session Management",
"aliases": ["session", "sitzung", "session management", "sitzungsverwaltung"],
"keywords": ["session", "sitzung", "token", "cookie", "timeout"],
"subcontrols": [
{
"subcontrol_id": "V3.1",
"title": "Session Management Security",
"statement": "Sitzungstoken muessen sicher erzeugt, uebertragen und invalidiert werden.",
"keywords": ["token", "sitzung", "sicherheit"],
"action_hint": "implement",
"object_hint": "Sichere Sitzungsverwaltung",
"object_class": "technical_control"
},
{
"subcontrol_id": "V3.3",
"title": "Session Termination",
"statement": "Sitzungen muessen nach Inaktivitaet und bei Abmeldung zuverlaessig beendet werden.",
"keywords": ["termination", "timeout", "abmeldung", "beenden"],
"action_hint": "configure",
"object_hint": "Sitzungstimeout",
"object_class": "configuration"
},
{
"subcontrol_id": "V3.5",
"title": "Token-Based Session Management",
"statement": "Tokenbasierte Sitzungsmechanismen muessen gegen Diebstahl und Replay geschuetzt sein.",
"keywords": ["jwt", "token", "replay", "diebstahl"],
"action_hint": "implement",
"object_hint": "Token-Schutz",
"object_class": "technical_control"
}
]
},
{
"domain_id": "V5",
"title": "Validation, Sanitization and Encoding",
"aliases": ["validation", "validierung", "sanitization", "encoding", "eingabevalidierung"],
"keywords": ["validierung", "sanitization", "encoding", "xss", "injection", "eingabe"],
"subcontrols": [
{
"subcontrol_id": "V5.1",
"title": "Input Validation",
"statement": "Alle Eingabedaten muessen serverseitig validiert werden.",
"keywords": ["input", "eingabe", "validierung", "serverseitig"],
"action_hint": "implement",
"object_hint": "Eingabevalidierung",
"object_class": "technical_control"
},
{
"subcontrol_id": "V5.2",
"title": "Sanitization and Sandboxing",
"statement": "Eingaben muessen bereinigt und in sicherer Umgebung verarbeitet werden.",
"keywords": ["sanitization", "bereinigung", "sandbox"],
"action_hint": "implement",
"object_hint": "Eingabebereinigung",
"object_class": "technical_control"
},
{
"subcontrol_id": "V5.3",
"title": "Output Encoding and Injection Prevention",
"statement": "Ausgaben muessen kontextabhaengig kodiert werden, um Injection-Angriffe zu verhindern.",
"keywords": ["output", "encoding", "injection", "xss", "sql"],
"action_hint": "implement",
"object_hint": "Ausgabe-Encoding",
"object_class": "technical_control"
}
]
},
{
"domain_id": "V6",
"title": "Stored Cryptography",
"aliases": ["cryptography", "kryptographie", "verschluesselung", "stored cryptography"],
"keywords": ["kryptographie", "verschluesselung", "hashing", "schluessel", "key management"],
"subcontrols": [
{
"subcontrol_id": "V6.1",
"title": "Data Classification",
"statement": "Daten muessen klassifiziert und entsprechend ihrer Schutzklasse behandelt werden.",
"keywords": ["klassifizierung", "classification", "schutzklasse"],
"action_hint": "define",
"object_hint": "Datenklassifizierung",
"object_class": "data"
},
{
"subcontrol_id": "V6.2",
"title": "Algorithms",
"statement": "Nur zugelassene und aktuelle kryptographische Algorithmen duerfen verwendet werden.",
"keywords": ["algorithmus", "algorithm", "aes", "rsa"],
"action_hint": "configure",
"object_hint": "Kryptographische Algorithmen",
"object_class": "cryptographic_control"
},
{
"subcontrol_id": "V6.4",
"title": "Secret Management",
"statement": "Geheimnisse (Schluessel, Passwoerter, Tokens) muessen in einem Secret-Management-System verwaltet werden.",
"keywords": ["secret", "geheimnis", "vault", "key management"],
"action_hint": "maintain",
"object_hint": "Secret-Management",
"object_class": "cryptographic_control"
}
]
},
{
"domain_id": "V8",
"title": "Data Protection",
"aliases": ["data protection", "datenschutz", "datenverarbeitung"],
"keywords": ["datenschutz", "data protection", "pii", "personenbezogen", "privacy"],
"subcontrols": [
{
"subcontrol_id": "V8.1",
"title": "General Data Protection",
"statement": "Personenbezogene Daten muessen gemaess Datenschutzanforderungen geschuetzt werden.",
"keywords": ["personenbezogen", "pii", "datenschutz"],
"action_hint": "implement",
"object_hint": "Datenschutzmassnahmen",
"object_class": "data"
},
{
"subcontrol_id": "V8.2",
"title": "Client-Side Data Protection",
"statement": "Clientseitig gespeicherte sensible Daten muessen geschuetzt und minimiert werden.",
"keywords": ["client", "browser", "localstorage", "cookie"],
"action_hint": "implement",
"object_hint": "Clientseitiger Datenschutz",
"object_class": "technical_control"
},
{
"subcontrol_id": "V8.3",
"title": "Sensitive Private Data",
"statement": "Sensible Daten muessen bei Speicherung und Verarbeitung besonders geschuetzt werden.",
"keywords": ["sensibel", "vertraulich", "speicherung"],
"action_hint": "encrypt",
"object_hint": "Verschluesselung sensibler Daten",
"object_class": "data"
}
]
},
{
"domain_id": "V9",
"title": "Communication",
"aliases": ["communication", "kommunikation", "tls", "transport"],
"keywords": ["tls", "ssl", "https", "transport", "kommunikation", "verschluesselung"],
"subcontrols": [
{
"subcontrol_id": "V9.1",
"title": "Client Communication Security",
"statement": "Alle Client-Server-Kommunikation muss ueber TLS verschluesselt werden.",
"keywords": ["tls", "https", "client", "server"],
"action_hint": "encrypt",
"object_hint": "TLS-Transportverschluesselung",
"object_class": "cryptographic_control"
},
{
"subcontrol_id": "V9.2",
"title": "Server Communication Security",
"statement": "Server-zu-Server-Kommunikation muss authentifiziert und verschluesselt erfolgen.",
"keywords": ["server", "mtls", "backend"],
"action_hint": "encrypt",
"object_hint": "Server-Kommunikationsverschluesselung",
"object_class": "cryptographic_control"
}
]
},
{
"domain_id": "V13",
"title": "API and Web Service",
"aliases": ["api", "web service", "rest", "graphql", "webservice"],
"keywords": ["api", "rest", "graphql", "webservice", "endpoint", "schnittstelle"],
"subcontrols": [
{
"subcontrol_id": "V13.1",
"title": "Generic Web Service Security",
"statement": "Web-Services muessen gegen gaengige Angriffe abgesichert werden.",
"keywords": ["web service", "sicherheit", "angriff"],
"action_hint": "implement",
"object_hint": "Web-Service-Absicherung",
"object_class": "interface"
},
{
"subcontrol_id": "V13.2",
"title": "RESTful Web Service",
"statement": "REST-APIs muessen Input-Validierung, Rate Limiting und sichere Authentifizierung implementieren.",
"keywords": ["rest", "api", "rate limiting", "input"],
"action_hint": "implement",
"object_hint": "REST-API-Absicherung",
"object_class": "interface"
},
{
"subcontrol_id": "V13.4",
"title": "GraphQL and Web Services",
"statement": "GraphQL-Endpoints muessen gegen Query-Complexity-Angriffe und Introspection geschuetzt werden.",
"keywords": ["graphql", "query", "complexity", "introspection"],
"action_hint": "configure",
"object_hint": "GraphQL-Absicherung",
"object_class": "interface"
}
]
},
{
"domain_id": "V14",
"title": "Configuration",
"aliases": ["configuration", "konfiguration", "hardening", "haertung"],
"keywords": ["konfiguration", "hardening", "haertung", "header", "deployment"],
"subcontrols": [
{
"subcontrol_id": "V14.1",
"title": "Build and Deploy",
"statement": "Build- und Deployment-Prozesse muessen sicher konfiguriert und reproduzierbar sein.",
"keywords": ["build", "deploy", "ci/cd", "pipeline"],
"action_hint": "configure",
"object_hint": "Sichere Build-Pipeline",
"object_class": "configuration"
},
{
"subcontrol_id": "V14.2",
"title": "Dependency Management",
"statement": "Abhaengigkeiten muessen auf Schwachstellen geprueft und aktuell gehalten werden.",
"keywords": ["dependency", "abhaengigkeit", "sca", "sbom"],
"action_hint": "maintain",
"object_hint": "Abhaengigkeitsverwaltung",
"object_class": "system"
},
{
"subcontrol_id": "V14.3",
"title": "Unintended Security Disclosure",
"statement": "Fehlermeldungen und Debug-Informationen duerfen keine sicherheitsrelevanten Details preisgeben.",
"keywords": ["disclosure", "fehlermeldung", "debug", "information leakage"],
"action_hint": "configure",
"object_hint": "Fehlerbehandlung",
"object_class": "configuration"
},
{
"subcontrol_id": "V14.4",
"title": "HTTP Security Headers",
"statement": "HTTP-Sicherheitsheader muessen korrekt konfiguriert sein.",
"keywords": ["header", "csp", "hsts", "x-frame"],
"action_hint": "configure",
"object_hint": "HTTP-Sicherheitsheader",
"object_class": "configuration"
}
]
}
]
}

View File

@@ -1493,7 +1493,37 @@ def _normalize_object(object_raw: str) -> str:
return obj[:80] or "unknown" return obj[:80] or "unknown"
# ── 7b. Output Validator (Negativregeln) ───────────────────────────────── # ── 7b. Framework / Composite Detection ──────────────────────────────────
_FRAMEWORK_KEYWORDS: list[str] = [
"praktiken", "kontrollen gemäß", "maßnahmen gemäß", "anforderungen aus",
"anforderungen gemäß", "gemäß .+ umzusetzen", "framework", "standard",
"controls for", "practices for", "requirements from",
]
_COMPOSITE_OBJECT_KEYWORDS: list[str] = [
"ccm", "nist", "iso 27001", "iso 27002", "owasp", "bsi",
"cis controls", "cobit", "sox", "pci dss", "hitrust",
"soc 2", "soc2", "enisa", "kritis",
]
_COMPOSITE_RE = re.compile(
"|".join(_FRAMEWORK_KEYWORDS + _COMPOSITE_OBJECT_KEYWORDS),
re.IGNORECASE,
)
def _is_composite_obligation(obligation_text: str, object_: str) -> bool:
"""Detect framework-level / composite obligations that are NOT atomic.
Returns True if the obligation references a framework domain, standard,
or set of practices rather than a single auditable requirement.
"""
combined = f"{obligation_text} {object_}"
return bool(_COMPOSITE_RE.search(combined))
# ── 7c. Output Validator (Negativregeln) ─────────────────────────────────
def _validate_atomic_control( def _validate_atomic_control(
atomic: "AtomicControlCandidate", atomic: "AtomicControlCandidate",
@@ -1544,6 +1574,9 @@ def _validate_atomic_control(
if object_class == "general": if object_class == "general":
issues.append("WARN: object_class is 'general' (unclassified)") issues.append("WARN: object_class is 'general' (unclassified)")
if getattr(atomic, "_is_composite", False):
issues.append("WARN: composite/framework obligation — requires further decomposition")
for issue in issues: for issue in issues:
if issue.startswith("ERROR:"): if issue.startswith("ERROR:"):
logger.warning("Validation: %s — title=%s", issue, atomic.title[:60]) logger.warning("Validation: %s — title=%s", issue, atomic.title[:60])
@@ -1703,6 +1736,12 @@ def _compose_deterministic(
atomic._deadline_hours = deadline_hours # type: ignore[attr-defined] atomic._deadline_hours = deadline_hours # type: ignore[attr-defined]
atomic._frequency = frequency # type: ignore[attr-defined] atomic._frequency = frequency # type: ignore[attr-defined]
# ── Composite / Framework detection ───────────────────────
is_composite = _is_composite_obligation(obligation_text, object_)
atomic._is_composite = is_composite # type: ignore[attr-defined]
atomic._atomicity = "composite" if is_composite else "atomic" # type: ignore[attr-defined]
atomic._requires_decomposition = is_composite # type: ignore[attr-defined]
# ── Validate (log issues, never reject) ─────────────────── # ── Validate (log issues, never reject) ───────────────────
validation_issues = _validate_atomic_control(atomic, action_type, object_class) validation_issues = _validate_atomic_control(atomic, action_type, object_class)
atomic._validation_issues = validation_issues # type: ignore[attr-defined] atomic._validation_issues = validation_issues # type: ignore[attr-defined]
@@ -2403,23 +2442,7 @@ class DecompositionPass:
else: else:
# Deterministic engine — no LLM required # Deterministic engine — no LLM required
for obl in batch: for obl in batch:
sub_actions = _split_compound_action(obl["action"]) await self._route_and_compose(obl, stats)
for sub_action in sub_actions:
atomic = _compose_deterministic(
obligation_text=obl["obligation_text"],
action=sub_action,
object_=obl["object"],
parent_title=obl["parent_title"],
parent_severity=obl["parent_severity"],
parent_category=obl["parent_category"],
is_test=obl["is_test"],
is_reporting=obl["is_reporting"],
trigger_type=obl.get("trigger_type"),
condition=obl.get("condition"),
)
await self._process_pass0b_control(
obl, {}, stats, atomic=atomic,
)
# Commit after each successful sub-batch # Commit after each successful sub-batch
self.db.commit() self.db.commit()
@@ -2435,6 +2458,107 @@ class DecompositionPass:
logger.info("Pass 0b: %s", stats) logger.info("Pass 0b: %s", stats)
return stats return stats
async def _route_and_compose(
self, obl: dict, stats: dict,
) -> None:
"""Route an obligation through the framework detection layer,
then compose atomic controls.
Routing types:
- atomic: compose directly via _compose_deterministic
- compound: split compound verbs, compose each
- framework_container: decompose via framework registry,
then compose each sub-obligation
"""
from compliance.services.framework_decomposition import (
classify_routing,
decompose_framework_container,
)
routing = classify_routing(
obligation_text=obl["obligation_text"],
action_raw=obl["action"],
object_raw=obl["object"],
condition_raw=obl.get("condition"),
)
if routing.routing_type == "framework_container" and routing.framework_ref:
# Decompose framework container into sub-obligations
result = decompose_framework_container(
obligation_candidate_id=obl["candidate_id"],
parent_control_id=obl["parent_control_id"],
obligation_text=obl["obligation_text"],
framework_ref=routing.framework_ref,
framework_domain=routing.framework_domain,
)
stats.setdefault("framework_decomposed", 0)
stats.setdefault("framework_sub_obligations", 0)
if result.release_state == "decomposed" and result.decomposed_obligations:
stats["framework_decomposed"] += 1
stats["framework_sub_obligations"] += len(result.decomposed_obligations)
logger.info(
"Framework decomposition: %s%s/%s%d sub-obligations",
obl["candidate_id"], routing.framework_ref,
routing.framework_domain, len(result.decomposed_obligations),
)
# Compose each sub-obligation
for d_obl in result.decomposed_obligations:
sub_obl = {
**obl,
"obligation_text": d_obl.obligation_text,
"action": d_obl.action_raw,
"object": d_obl.object_raw,
}
sub_actions = _split_compound_action(sub_obl["action"])
for sub_action in sub_actions:
atomic = _compose_deterministic(
obligation_text=sub_obl["obligation_text"],
action=sub_action,
object_=sub_obl["object"],
parent_title=obl["parent_title"],
parent_severity=obl["parent_severity"],
parent_category=obl["parent_category"],
is_test=obl["is_test"],
is_reporting=obl["is_reporting"],
trigger_type=obl.get("trigger_type"),
condition=obl.get("condition"),
)
# Enrich gen_meta with framework info
atomic._framework_ref = routing.framework_ref # type: ignore[attr-defined]
atomic._framework_domain = routing.framework_domain # type: ignore[attr-defined]
atomic._framework_subcontrol_id = d_obl.subcontrol_id # type: ignore[attr-defined]
atomic._decomposition_source = "framework_decomposition" # type: ignore[attr-defined]
await self._process_pass0b_control(
obl, {}, stats, atomic=atomic,
)
return
else:
# Unmatched framework — fall through to normal composition
logger.warning(
"Framework decomposition unmatched: %s%s",
obl["candidate_id"], result.issues,
)
# Atomic or compound or unmatched framework: normal composition
sub_actions = _split_compound_action(obl["action"])
for sub_action in sub_actions:
atomic = _compose_deterministic(
obligation_text=obl["obligation_text"],
action=sub_action,
object_=obl["object"],
parent_title=obl["parent_title"],
parent_severity=obl["parent_severity"],
parent_category=obl["parent_category"],
is_test=obl["is_test"],
is_reporting=obl["is_reporting"],
trigger_type=obl.get("trigger_type"),
condition=obl.get("condition"),
)
await self._process_pass0b_control(
obl, {}, stats, atomic=atomic,
)
async def _process_pass0b_control( async def _process_pass0b_control(
self, obl: dict, parsed: dict, stats: dict, self, obl: dict, parsed: dict, stats: dict,
atomic: Optional[AtomicControlCandidate] = None, atomic: Optional[AtomicControlCandidate] = None,
@@ -2855,6 +2979,13 @@ class DecompositionPass:
"deadline_hours": getattr(atomic, "_deadline_hours", None), "deadline_hours": getattr(atomic, "_deadline_hours", None),
"frequency": getattr(atomic, "_frequency", None), "frequency": getattr(atomic, "_frequency", None),
"validation_issues": getattr(atomic, "_validation_issues", []), "validation_issues": getattr(atomic, "_validation_issues", []),
"is_composite": getattr(atomic, "_is_composite", False),
"atomicity": getattr(atomic, "_atomicity", "atomic"),
"requires_decomposition": getattr(atomic, "_requires_decomposition", False),
"framework_ref": getattr(atomic, "_framework_ref", None),
"framework_domain": getattr(atomic, "_framework_domain", None),
"framework_subcontrol_id": getattr(atomic, "_framework_subcontrol_id", None),
"decomposition_source": getattr(atomic, "_decomposition_source", "direct"),
}), }),
"framework_id": "14b1bdd2-abc7-4a43-adae-14471ee5c7cf", "framework_id": "14b1bdd2-abc7-4a43-adae-14471ee5c7cf",
}, },

View File

@@ -0,0 +1,714 @@
"""Framework Decomposition Engine — decomposes framework-container obligations.
Sits between Pass 0a (obligation extraction) and Pass 0b (atomic control
composition). Detects obligations that reference a framework domain (e.g.
"CCM-Praktiken fuer AIS") and decomposes them into concrete sub-obligations
using an internal framework registry.
Three routing types:
atomic → pass through to Pass 0b unchanged
compound → split compound verbs, then Pass 0b
framework_container → decompose via registry, then Pass 0b
The registry is a set of JSON files under compliance/data/frameworks/.
"""
import json
import logging
import os
import re
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Registry loading
# ---------------------------------------------------------------------------
_REGISTRY_DIR = Path(__file__).resolve().parent.parent / "data" / "frameworks"
_REGISTRY: dict[str, dict] = {} # framework_id → framework dict
def _load_registry() -> dict[str, dict]:
"""Load all framework JSON files from the registry directory."""
registry: dict[str, dict] = {}
if not _REGISTRY_DIR.is_dir():
logger.warning("Framework registry dir not found: %s", _REGISTRY_DIR)
return registry
for fpath in sorted(_REGISTRY_DIR.glob("*.json")):
try:
with open(fpath, encoding="utf-8") as f:
fw = json.load(f)
fw_id = fw.get("framework_id", fpath.stem)
registry[fw_id] = fw
logger.info(
"Loaded framework: %s (%d domains)",
fw_id,
len(fw.get("domains", [])),
)
except Exception:
logger.exception("Failed to load framework file: %s", fpath)
return registry
def get_registry() -> dict[str, dict]:
"""Return the global framework registry (lazy-loaded)."""
global _REGISTRY
if not _REGISTRY:
_REGISTRY = _load_registry()
return _REGISTRY
def reload_registry() -> dict[str, dict]:
"""Force-reload the framework registry from disk."""
global _REGISTRY
_REGISTRY = _load_registry()
return _REGISTRY
# ---------------------------------------------------------------------------
# Framework alias index (built from registry)
# ---------------------------------------------------------------------------
def _build_alias_index(registry: dict[str, dict]) -> dict[str, str]:
"""Build a lowercase alias → framework_id lookup."""
idx: dict[str, str] = {}
for fw_id, fw in registry.items():
# Framework-level aliases
idx[fw_id.lower()] = fw_id
name = fw.get("display_name", "")
if name:
idx[name.lower()] = fw_id
# Common short forms
for part in fw_id.lower().replace("_", " ").split():
if len(part) >= 3:
idx[part] = fw_id
return idx
# ---------------------------------------------------------------------------
# Routing — classify obligation type
# ---------------------------------------------------------------------------
# Extended patterns for framework detection (beyond the simple _COMPOSITE_RE
# in decomposition_pass.py — here we also capture the framework name)
_FRAMEWORK_PATTERN = re.compile(
r"(?:praktiken|kontrollen|ma(?:ss|ß)nahmen|anforderungen|vorgaben|controls|practices|measures|requirements)"
r"\s+(?:f(?:ue|ü)r|aus|gem(?:ae|ä)(?:ss|ß)|nach|from|of|for|per)\s+"
r"(.+?)(?:\s+(?:m(?:ue|ü)ssen|sollen|sind|werden|implementieren|umsetzen|einf(?:ue|ü)hren)|\.|,|$)",
re.IGNORECASE,
)
# Direct framework name references
_DIRECT_FRAMEWORK_RE = re.compile(
r"\b(?:CSA\s*CCM|NIST\s*(?:SP\s*)?800-53|OWASP\s*(?:ASVS|SAMM|Top\s*10)"
r"|CIS\s*Controls|BSI\s*(?:IT-)?Grundschutz|ENISA|ISO\s*2700[12]"
r"|COBIT|SOX|PCI\s*DSS|HITRUST|SOC\s*2|KRITIS)\b",
re.IGNORECASE,
)
# Compound verb patterns (multiple main verbs)
_COMPOUND_VERB_RE = re.compile(
r"\b(?:und|sowie|als\s+auch|or|and)\b",
re.IGNORECASE,
)
# No-split phrases that look compound but aren't
_NO_SPLIT_PHRASES = [
"pflegen und aufrechterhalten",
"dokumentieren und pflegen",
"definieren und dokumentieren",
"erstellen und freigeben",
"pruefen und genehmigen",
"identifizieren und bewerten",
"erkennen und melden",
"define and maintain",
"create and maintain",
"establish and maintain",
"monitor and review",
"detect and respond",
]
@dataclass
class RoutingResult:
"""Result of obligation routing classification."""
routing_type: str # atomic | compound | framework_container | unknown_review
framework_ref: Optional[str] = None
framework_domain: Optional[str] = None
domain_title: Optional[str] = None
confidence: float = 0.0
reason: str = ""
def classify_routing(
obligation_text: str,
action_raw: str,
object_raw: str,
condition_raw: Optional[str] = None,
) -> RoutingResult:
"""Classify an obligation into atomic / compound / framework_container."""
combined = f"{obligation_text} {object_raw}".lower()
# --- Step 1: Framework container detection ---
fw_result = _detect_framework(obligation_text, object_raw)
if fw_result.routing_type == "framework_container":
return fw_result
# --- Step 2: Compound verb detection ---
if _is_compound_obligation(action_raw, obligation_text):
return RoutingResult(
routing_type="compound",
confidence=0.7,
reason="multiple_main_verbs",
)
# --- Step 3: Default = atomic ---
return RoutingResult(
routing_type="atomic",
confidence=0.9,
reason="single_action_single_object",
)
def _detect_framework(
obligation_text: str, object_raw: str,
) -> RoutingResult:
"""Detect if obligation references a framework domain."""
combined = f"{obligation_text} {object_raw}"
registry = get_registry()
alias_idx = _build_alias_index(registry)
# Strategy 1: direct framework name match
m = _DIRECT_FRAMEWORK_RE.search(combined)
if m:
fw_name = m.group(0).strip()
fw_id = _resolve_framework_id(fw_name, alias_idx, registry)
if fw_id:
domain_id, domain_title = _match_domain(
combined, registry[fw_id],
)
return RoutingResult(
routing_type="framework_container",
framework_ref=fw_id,
framework_domain=domain_id,
domain_title=domain_title,
confidence=0.95 if domain_id else 0.75,
reason=f"direct_framework_match:{fw_name}",
)
else:
# Framework name recognized but not in registry
return RoutingResult(
routing_type="framework_container",
framework_ref=None,
framework_domain=None,
confidence=0.6,
reason=f"direct_framework_match_no_registry:{fw_name}",
)
# Strategy 2: pattern match ("Praktiken fuer X")
m2 = _FRAMEWORK_PATTERN.search(combined)
if m2:
ref_text = m2.group(1).strip()
fw_id, domain_id, domain_title = _resolve_from_ref_text(
ref_text, registry, alias_idx,
)
if fw_id:
return RoutingResult(
routing_type="framework_container",
framework_ref=fw_id,
framework_domain=domain_id,
domain_title=domain_title,
confidence=0.85 if domain_id else 0.65,
reason=f"pattern_match:{ref_text}",
)
# Strategy 3: keyword-heavy object
if _has_framework_keywords(object_raw):
return RoutingResult(
routing_type="framework_container",
framework_ref=None,
framework_domain=None,
confidence=0.5,
reason="framework_keywords_in_object",
)
return RoutingResult(routing_type="atomic", confidence=0.0)
def _resolve_framework_id(
name: str,
alias_idx: dict[str, str],
registry: dict[str, dict],
) -> Optional[str]:
"""Resolve a framework name to its registry ID."""
normalized = re.sub(r"\s+", " ", name.strip().lower())
# Direct alias match
if normalized in alias_idx:
return alias_idx[normalized]
# Try compact form (strip spaces, hyphens, underscores)
compact = re.sub(r"[\s_\-]+", "", normalized)
for alias, fw_id in alias_idx.items():
if re.sub(r"[\s_\-]+", "", alias) == compact:
return fw_id
# Substring match in display names
for fw_id, fw in registry.items():
display = fw.get("display_name", "").lower()
if normalized in display or display in normalized:
return fw_id
# Partial match: check if normalized contains any alias (for multi-word refs)
for alias, fw_id in alias_idx.items():
if len(alias) >= 4 and alias in normalized:
return fw_id
return None
def _match_domain(
text: str, framework: dict,
) -> tuple[Optional[str], Optional[str]]:
"""Match a domain within a framework from text references."""
text_lower = text.lower()
best_id: Optional[str] = None
best_title: Optional[str] = None
best_score = 0
for domain in framework.get("domains", []):
score = 0
domain_id = domain["domain_id"]
title = domain.get("title", "")
# Exact domain ID match (e.g. "AIS")
if re.search(rf"\b{re.escape(domain_id)}\b", text, re.IGNORECASE):
score += 10
# Full title match
if title.lower() in text_lower:
score += 8
# Alias match
for alias in domain.get("aliases", []):
if alias.lower() in text_lower:
score += 6
break
# Keyword overlap
kw_hits = sum(
1 for kw in domain.get("keywords", [])
if kw.lower() in text_lower
)
score += kw_hits
if score > best_score:
best_score = score
best_id = domain_id
best_title = title
if best_score >= 3:
return best_id, best_title
return None, None
def _resolve_from_ref_text(
ref_text: str,
registry: dict[str, dict],
alias_idx: dict[str, str],
) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""Resolve framework + domain from a reference text like 'AIS' or 'Application Security'."""
ref_lower = ref_text.lower()
for fw_id, fw in registry.items():
for domain in fw.get("domains", []):
# Check domain ID
if domain["domain_id"].lower() in ref_lower:
return fw_id, domain["domain_id"], domain.get("title")
# Check title
if domain.get("title", "").lower() in ref_lower:
return fw_id, domain["domain_id"], domain.get("title")
# Check aliases
for alias in domain.get("aliases", []):
if alias.lower() in ref_lower or ref_lower in alias.lower():
return fw_id, domain["domain_id"], domain.get("title")
return None, None, None
_FRAMEWORK_KW_SET = {
"praktiken", "kontrollen", "massnahmen", "maßnahmen",
"anforderungen", "vorgaben", "framework", "standard",
"baseline", "katalog", "domain", "family", "category",
"practices", "controls", "measures", "requirements",
}
def _has_framework_keywords(text: str) -> bool:
"""Check if text contains framework-indicator keywords."""
words = set(re.findall(r"[a-zäöüß]+", text.lower()))
return len(words & _FRAMEWORK_KW_SET) >= 2
def _is_compound_obligation(action_raw: str, obligation_text: str) -> bool:
"""Detect if the obligation has multiple competing main verbs."""
if not action_raw:
return False
action_lower = action_raw.lower().strip()
# Check no-split phrases first
for phrase in _NO_SPLIT_PHRASES:
if phrase in action_lower:
return False
# Must have a conjunction
if not _COMPOUND_VERB_RE.search(action_lower):
return False
# Split by conjunctions and check if we get 2+ meaningful verbs
parts = re.split(r"\b(?:und|sowie|als\s+auch|or|and)\b", action_lower)
meaningful = [p.strip() for p in parts if len(p.strip()) >= 3]
return len(meaningful) >= 2
# ---------------------------------------------------------------------------
# Framework Decomposition
# ---------------------------------------------------------------------------
@dataclass
class DecomposedObligation:
"""A concrete obligation derived from a framework container."""
obligation_candidate_id: str
parent_control_id: str
parent_framework_container_id: str
source_ref_law: str
source_ref_article: str
obligation_text: str
actor: str
action_raw: str
object_raw: str
condition_raw: Optional[str] = None
trigger_raw: Optional[str] = None
routing_type: str = "atomic"
release_state: str = "decomposed"
subcontrol_id: str = ""
# Metadata
action_hint: str = ""
object_hint: str = ""
object_class: str = ""
keywords: list[str] = field(default_factory=list)
@dataclass
class FrameworkDecompositionResult:
"""Result of framework decomposition."""
framework_container_id: str
source_obligation_candidate_id: str
framework_ref: Optional[str]
framework_domain: Optional[str]
domain_title: Optional[str]
matched_subcontrols: list[str]
decomposition_confidence: float
release_state: str # decomposed | unmatched | error
decomposed_obligations: list[DecomposedObligation]
issues: list[str]
def decompose_framework_container(
obligation_candidate_id: str,
parent_control_id: str,
obligation_text: str,
framework_ref: Optional[str],
framework_domain: Optional[str],
actor: str = "organization",
) -> FrameworkDecompositionResult:
"""Decompose a framework-container obligation into concrete sub-obligations.
Steps:
1. Resolve framework from registry
2. Resolve domain within framework
3. Select relevant subcontrols (keyword filter or full domain)
4. Generate decomposed obligations
"""
container_id = f"FWC-{uuid.uuid4().hex[:8]}"
registry = get_registry()
issues: list[str] = []
# Step 1: Resolve framework
fw = None
if framework_ref and framework_ref in registry:
fw = registry[framework_ref]
else:
# Try to find by name in text
fw, framework_ref = _find_framework_in_text(obligation_text, registry)
if not fw:
issues.append("ERROR: framework_not_matched")
return FrameworkDecompositionResult(
framework_container_id=container_id,
source_obligation_candidate_id=obligation_candidate_id,
framework_ref=framework_ref,
framework_domain=framework_domain,
domain_title=None,
matched_subcontrols=[],
decomposition_confidence=0.0,
release_state="unmatched",
decomposed_obligations=[],
issues=issues,
)
# Step 2: Resolve domain
domain_data = None
domain_title = None
if framework_domain:
for d in fw.get("domains", []):
if d["domain_id"].lower() == framework_domain.lower():
domain_data = d
domain_title = d.get("title")
break
if not domain_data:
# Try matching from text
domain_id, domain_title = _match_domain(obligation_text, fw)
if domain_id:
for d in fw.get("domains", []):
if d["domain_id"] == domain_id:
domain_data = d
framework_domain = domain_id
break
if not domain_data:
issues.append("WARN: domain_not_matched — using all domains")
# Fall back to all subcontrols across all domains
all_subcontrols = []
for d in fw.get("domains", []):
for sc in d.get("subcontrols", []):
sc["_domain_id"] = d["domain_id"]
all_subcontrols.append(sc)
subcontrols = _select_subcontrols(obligation_text, all_subcontrols)
if not subcontrols:
issues.append("ERROR: no_subcontrols_matched")
return FrameworkDecompositionResult(
framework_container_id=container_id,
source_obligation_candidate_id=obligation_candidate_id,
framework_ref=framework_ref,
framework_domain=framework_domain,
domain_title=None,
matched_subcontrols=[],
decomposition_confidence=0.0,
release_state="unmatched",
decomposed_obligations=[],
issues=issues,
)
else:
# Step 3: Select subcontrols from domain
raw_subcontrols = domain_data.get("subcontrols", [])
subcontrols = _select_subcontrols(obligation_text, raw_subcontrols)
if not subcontrols:
# Full domain decomposition
subcontrols = raw_subcontrols
# Quality check: too many subcontrols
if len(subcontrols) > 25:
issues.append(f"WARN: {len(subcontrols)} subcontrols — may be too broad")
# Step 4: Generate decomposed obligations
display_name = fw.get("display_name", framework_ref or "Unknown")
decomposed: list[DecomposedObligation] = []
matched_ids: list[str] = []
for sc in subcontrols:
sc_id = sc.get("subcontrol_id", "")
matched_ids.append(sc_id)
action_hint = sc.get("action_hint", "")
object_hint = sc.get("object_hint", "")
# Quality warnings
if not action_hint:
issues.append(f"WARN: {sc_id} missing action_hint")
if not object_hint:
issues.append(f"WARN: {sc_id} missing object_hint")
obl_id = f"{obligation_candidate_id}-{sc_id}"
decomposed.append(DecomposedObligation(
obligation_candidate_id=obl_id,
parent_control_id=parent_control_id,
parent_framework_container_id=container_id,
source_ref_law=display_name,
source_ref_article=sc_id,
obligation_text=sc.get("statement", ""),
actor=actor,
action_raw=action_hint or _infer_action(sc.get("statement", "")),
object_raw=object_hint or _infer_object(sc.get("statement", "")),
routing_type="atomic",
release_state="decomposed",
subcontrol_id=sc_id,
action_hint=action_hint,
object_hint=object_hint,
object_class=sc.get("object_class", ""),
keywords=sc.get("keywords", []),
))
# Check if decomposed are identical to container
for d in decomposed:
if d.obligation_text.strip() == obligation_text.strip():
issues.append(f"WARN: {d.subcontrol_id} identical to container text")
confidence = _compute_decomposition_confidence(
framework_ref, framework_domain, domain_data, len(subcontrols), issues,
)
return FrameworkDecompositionResult(
framework_container_id=container_id,
source_obligation_candidate_id=obligation_candidate_id,
framework_ref=framework_ref,
framework_domain=framework_domain,
domain_title=domain_title,
matched_subcontrols=matched_ids,
decomposition_confidence=confidence,
release_state="decomposed",
decomposed_obligations=decomposed,
issues=issues,
)
def _find_framework_in_text(
text: str, registry: dict[str, dict],
) -> tuple[Optional[dict], Optional[str]]:
"""Try to find a framework by searching text for known names."""
alias_idx = _build_alias_index(registry)
m = _DIRECT_FRAMEWORK_RE.search(text)
if m:
fw_id = _resolve_framework_id(m.group(0), alias_idx, registry)
if fw_id and fw_id in registry:
return registry[fw_id], fw_id
return None, None
def _select_subcontrols(
obligation_text: str, subcontrols: list[dict],
) -> list[dict]:
"""Select relevant subcontrols based on keyword matching.
Returns empty list if no targeted match found (caller falls back to
full domain).
"""
text_lower = obligation_text.lower()
scored: list[tuple[int, dict]] = []
for sc in subcontrols:
score = 0
for kw in sc.get("keywords", []):
if kw.lower() in text_lower:
score += 1
# Title match
title = sc.get("title", "").lower()
if title and title in text_lower:
score += 3
# Object hint in text
obj = sc.get("object_hint", "").lower()
if obj and obj in text_lower:
score += 2
if score > 0:
scored.append((score, sc))
if not scored:
return []
# Only return those with meaningful overlap (score >= 2)
scored.sort(key=lambda x: x[0], reverse=True)
return [sc for score, sc in scored if score >= 2]
def _infer_action(statement: str) -> str:
"""Infer a basic action verb from a statement."""
s = statement.lower()
if any(w in s for w in ["definiert", "definieren", "define"]):
return "definieren"
if any(w in s for w in ["implementiert", "implementieren", "implement"]):
return "implementieren"
if any(w in s for w in ["dokumentiert", "dokumentieren", "document"]):
return "dokumentieren"
if any(w in s for w in ["ueberwacht", "ueberwachen", "monitor"]):
return "ueberwachen"
if any(w in s for w in ["getestet", "testen", "test"]):
return "testen"
if any(w in s for w in ["geschuetzt", "schuetzen", "protect"]):
return "implementieren"
if any(w in s for w in ["verwaltet", "verwalten", "manage"]):
return "pflegen"
if any(w in s for w in ["gemeldet", "melden", "report"]):
return "melden"
return "implementieren"
def _infer_object(statement: str) -> str:
"""Infer the primary object from a statement (first noun phrase)."""
# Simple heuristic: take the text after "muessen"/"muss" up to the verb
m = re.search(
r"(?:muessen|muss|m(?:ü|ue)ssen)\s+(.+?)(?:\s+werden|\s+sein|\.|,|$)",
statement,
re.IGNORECASE,
)
if m:
return m.group(1).strip()[:80]
# Fallback: first 80 chars
return statement[:80] if statement else ""
def _compute_decomposition_confidence(
framework_ref: Optional[str],
domain: Optional[str],
domain_data: Optional[dict],
num_subcontrols: int,
issues: list[str],
) -> float:
"""Compute confidence score for the decomposition."""
score = 0.3
if framework_ref:
score += 0.25
if domain:
score += 0.20
if domain_data:
score += 0.10
if 1 <= num_subcontrols <= 15:
score += 0.10
elif num_subcontrols > 15:
score += 0.05 # less confident with too many
# Penalize errors
errors = sum(1 for i in issues if i.startswith("ERROR:"))
score -= errors * 0.15
return round(max(min(score, 1.0), 0.0), 2)
# ---------------------------------------------------------------------------
# Registry statistics (for admin/debugging)
# ---------------------------------------------------------------------------
def registry_stats() -> dict:
"""Return summary statistics about the loaded registry."""
reg = get_registry()
stats = {
"frameworks": len(reg),
"details": [],
}
total_domains = 0
total_subcontrols = 0
for fw_id, fw in reg.items():
domains = fw.get("domains", [])
n_sc = sum(len(d.get("subcontrols", [])) for d in domains)
total_domains += len(domains)
total_subcontrols += n_sc
stats["details"].append({
"framework_id": fw_id,
"display_name": fw.get("display_name", ""),
"domains": len(domains),
"subcontrols": n_sc,
})
stats["total_domains"] = total_domains
stats["total_subcontrols"] = total_subcontrols
return stats

View File

@@ -62,6 +62,7 @@ from compliance.services.decomposition_pass import (
_validate_atomic_control, _validate_atomic_control,
_PATTERN_CANDIDATES_MAP, _PATTERN_CANDIDATES_MAP,
_PATTERN_CANDIDATES_BY_ACTION, _PATTERN_CANDIDATES_BY_ACTION,
_is_composite_obligation,
) )
@@ -1049,6 +1050,123 @@ class TestOutputValidator:
issues = _validate_atomic_control(ac, "implement", "policy") issues = _validate_atomic_control(ac, "implement", "policy")
assert not any("raw infinitive" in i for i in issues) assert not any("raw infinitive" in i for i in issues)
def test_composite_obligation_warns(self):
"""Composite obligations produce a WARN in validation."""
ac = AtomicControlCandidate(
title="CCM-Praktiken", objective="x",
test_procedure=["tp"], evidence=["ev"],
)
ac._is_composite = True # type: ignore[attr-defined]
issues = _validate_atomic_control(ac, "implement", "policy")
assert any("composite" in i for i in issues)
def test_non_composite_no_warn(self):
"""Non-composite obligations do NOT produce composite WARN."""
ac = AtomicControlCandidate(
title="MFA", objective="x",
test_procedure=["tp"], evidence=["ev"],
)
ac._is_composite = False # type: ignore[attr-defined]
issues = _validate_atomic_control(ac, "implement", "technical_control")
assert not any("composite" in i for i in issues)
# ---------------------------------------------------------------------------
# COMPOSITE / FRAMEWORK DETECTION TESTS
# ---------------------------------------------------------------------------
class TestCompositeDetection:
"""Tests for _is_composite_obligation()."""
def test_ccm_praktiken_detected(self):
"""'CCM-Praktiken für AIS implementieren' is composite."""
assert _is_composite_obligation(
"CCM-Praktiken für AIS implementieren", "CCM-Praktiken"
)
def test_kontrollen_gemaess_nist(self):
"""'Kontrollen gemäß NIST umsetzen' is composite."""
assert _is_composite_obligation(
"Kontrollen gemäß NIST SP 800-53 umsetzen", "Kontrollen"
)
def test_iso_27001_referenced(self):
"""ISO 27001 reference in object triggers composite."""
assert _is_composite_obligation(
"Maßnahmen umsetzen", "ISO 27001 Anhang A"
)
def test_owasp_framework(self):
"""OWASP reference triggers composite."""
assert _is_composite_obligation(
"OWASP Top 10 Maßnahmen implementieren", "Sicherheitsmaßnahmen"
)
def test_bsi_grundschutz(self):
"""BSI reference triggers composite."""
assert _is_composite_obligation(
"BSI-Grundschutz-Kompendium anwenden", "IT-Grundschutz"
)
def test_anforderungen_gemaess(self):
"""'Anforderungen gemäß X' is composite."""
assert _is_composite_obligation(
"Anforderungen gemäß EU AI Act umsetzen", "Anforderungen"
)
def test_simple_mfa_not_composite(self):
"""'MFA implementieren' is atomic, not composite."""
assert not _is_composite_obligation(
"Multi-Faktor-Authentifizierung implementieren", "MFA"
)
def test_simple_policy_not_composite(self):
"""'Sicherheitsrichtlinie dokumentieren' is atomic."""
assert not _is_composite_obligation(
"Eine Sicherheitsrichtlinie dokumentieren und pflegen",
"Sicherheitsrichtlinie",
)
def test_encryption_not_composite(self):
"""'Daten verschlüsseln' is atomic."""
assert not _is_composite_obligation(
"Personenbezogene Daten bei der Übertragung verschlüsseln",
"Personenbezogene Daten",
)
def test_composite_flags_on_atomic(self):
"""_compose_deterministic sets composite flags on the atomic."""
atomic = _compose_deterministic(
obligation_text="CCM-Praktiken für AIS implementieren",
action="implementieren",
object_="CCM-Praktiken",
parent_title="AI System Controls",
parent_severity="high",
parent_category="security",
is_test=False,
is_reporting=False,
)
assert atomic._is_composite is True # type: ignore[attr-defined]
assert atomic._atomicity == "composite" # type: ignore[attr-defined]
assert atomic._requires_decomposition is True # type: ignore[attr-defined]
def test_non_composite_flags_on_atomic(self):
"""_compose_deterministic sets atomic flags for non-composite."""
atomic = _compose_deterministic(
obligation_text="MFA implementieren",
action="implementieren",
object_="MFA",
parent_title="Access Control",
parent_severity="high",
parent_category="security",
is_test=False,
is_reporting=False,
)
assert atomic._is_composite is False # type: ignore[attr-defined]
assert atomic._atomicity == "atomic" # type: ignore[attr-defined]
assert atomic._requires_decomposition is False # type: ignore[attr-defined]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# PROMPT BUILDER TESTS # PROMPT BUILDER TESTS

View File

@@ -0,0 +1,453 @@
"""Tests for Framework Decomposition Engine.
Covers:
- Registry loading
- Routing classification (atomic / compound / framework_container)
- Framework + domain matching
- Subcontrol selection
- Decomposition into sub-obligations
- Quality rules (warnings, errors)
- Inference helpers
"""
import pytest
from compliance.services.framework_decomposition import (
classify_routing,
decompose_framework_container,
get_registry,
registry_stats,
reload_registry,
DecomposedObligation,
FrameworkDecompositionResult,
RoutingResult,
_detect_framework,
_has_framework_keywords,
_infer_action,
_infer_object,
_is_compound_obligation,
_match_domain,
_select_subcontrols,
)
# ---------------------------------------------------------------------------
# REGISTRY TESTS
# ---------------------------------------------------------------------------
class TestRegistryLoading:
def test_registry_loads_successfully(self):
reg = get_registry()
assert len(reg) >= 3
def test_nist_in_registry(self):
reg = get_registry()
assert "NIST_SP800_53" in reg
def test_owasp_asvs_in_registry(self):
reg = get_registry()
assert "OWASP_ASVS" in reg
def test_csa_ccm_in_registry(self):
reg = get_registry()
assert "CSA_CCM" in reg
def test_nist_has_domains(self):
reg = get_registry()
nist = reg["NIST_SP800_53"]
assert len(nist["domains"]) >= 5
def test_nist_ac_has_subcontrols(self):
reg = get_registry()
nist = reg["NIST_SP800_53"]
ac = next(d for d in nist["domains"] if d["domain_id"] == "AC")
assert len(ac["subcontrols"]) >= 5
def test_registry_stats(self):
stats = registry_stats()
assert stats["frameworks"] >= 3
assert stats["total_domains"] >= 10
assert stats["total_subcontrols"] >= 30
def test_reload_registry(self):
reg = reload_registry()
assert len(reg) >= 3
# ---------------------------------------------------------------------------
# ROUTING TESTS
# ---------------------------------------------------------------------------
class TestClassifyRouting:
def test_atomic_simple_obligation(self):
result = classify_routing(
obligation_text="Multi-Faktor-Authentifizierung muss implementiert werden",
action_raw="implementieren",
object_raw="MFA",
)
assert result.routing_type == "atomic"
def test_framework_container_ccm_ais(self):
result = classify_routing(
obligation_text="Die CCM-Praktiken fuer Application and Interface Security (AIS) muessen implementiert werden",
action_raw="implementieren",
object_raw="CCM-Praktiken fuer AIS",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "CSA_CCM"
assert result.framework_domain == "AIS"
def test_framework_container_nist_800_53(self):
result = classify_routing(
obligation_text="Kontrollen gemaess NIST SP 800-53 umsetzen",
action_raw="umsetzen",
object_raw="Kontrollen gemaess NIST SP 800-53",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "NIST_SP800_53"
def test_framework_container_owasp_asvs(self):
result = classify_routing(
obligation_text="OWASP ASVS Anforderungen muessen implementiert werden",
action_raw="implementieren",
object_raw="OWASP ASVS Anforderungen",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "OWASP_ASVS"
def test_compound_obligation(self):
result = classify_routing(
obligation_text="Richtlinie erstellen und Schulungen durchfuehren",
action_raw="erstellen und durchfuehren",
object_raw="Richtlinie",
)
assert result.routing_type == "compound"
def test_no_split_phrase_not_compound(self):
result = classify_routing(
obligation_text="Richtlinie dokumentieren und pflegen",
action_raw="dokumentieren und pflegen",
object_raw="Richtlinie",
)
assert result.routing_type == "atomic"
def test_framework_keywords_in_object(self):
result = classify_routing(
obligation_text="Massnahmen umsetzen",
action_raw="umsetzen",
object_raw="Framework-Praktiken und Kontrollen",
)
assert result.routing_type == "framework_container"
def test_bsi_grundschutz_detected(self):
result = classify_routing(
obligation_text="BSI IT-Grundschutz Massnahmen umsetzen",
action_raw="umsetzen",
object_raw="BSI IT-Grundschutz Massnahmen",
)
assert result.routing_type == "framework_container"
# ---------------------------------------------------------------------------
# FRAMEWORK DETECTION TESTS
# ---------------------------------------------------------------------------
class TestFrameworkDetection:
def test_detect_csa_ccm_with_domain(self):
result = _detect_framework(
"CCM-Praktiken fuer AIS implementieren",
"CCM-Praktiken",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "CSA_CCM"
assert result.framework_domain == "AIS"
def test_detect_nist_without_domain(self):
result = _detect_framework(
"NIST SP 800-53 Kontrollen implementieren",
"Kontrollen",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "NIST_SP800_53"
def test_no_framework_in_simple_text(self):
result = _detect_framework(
"Passwortrichtlinie dokumentieren",
"Passwortrichtlinie",
)
assert result.routing_type == "atomic"
def test_csa_ccm_iam_domain(self):
result = _detect_framework(
"CSA CCM Identity and Access Management Kontrollen",
"IAM-Kontrollen",
)
assert result.routing_type == "framework_container"
assert result.framework_ref == "CSA_CCM"
assert result.framework_domain == "IAM"
# ---------------------------------------------------------------------------
# DOMAIN MATCHING TESTS
# ---------------------------------------------------------------------------
class TestDomainMatching:
def test_match_ais_by_id(self):
reg = get_registry()
ccm = reg["CSA_CCM"]
domain_id, title = _match_domain("AIS-Kontrollen implementieren", ccm)
assert domain_id == "AIS"
def test_match_by_full_title(self):
reg = get_registry()
ccm = reg["CSA_CCM"]
domain_id, title = _match_domain(
"Application and Interface Security Massnahmen", ccm,
)
assert domain_id == "AIS"
def test_match_nist_incident_response(self):
reg = get_registry()
nist = reg["NIST_SP800_53"]
domain_id, title = _match_domain(
"Vorfallreaktionsverfahren gemaess NIST IR", nist,
)
assert domain_id == "IR"
def test_no_match_generic_text(self):
reg = get_registry()
nist = reg["NIST_SP800_53"]
domain_id, title = _match_domain("etwas Allgemeines", nist)
assert domain_id is None
# ---------------------------------------------------------------------------
# SUBCONTROL SELECTION TESTS
# ---------------------------------------------------------------------------
class TestSubcontrolSelection:
def test_keyword_based_selection(self):
subcontrols = [
{"subcontrol_id": "SC-1", "title": "X", "keywords": ["api", "schnittstelle"], "object_hint": ""},
{"subcontrol_id": "SC-2", "title": "Y", "keywords": ["backup", "sicherung"], "object_hint": ""},
]
selected = _select_subcontrols("API-Schnittstellen schuetzen", subcontrols)
assert len(selected) == 1
assert selected[0]["subcontrol_id"] == "SC-1"
def test_no_keyword_match_returns_empty(self):
subcontrols = [
{"subcontrol_id": "SC-1", "keywords": ["backup"], "title": "Backup", "object_hint": ""},
]
selected = _select_subcontrols("Passwort aendern", subcontrols)
assert selected == []
def test_title_match_boosts_score(self):
subcontrols = [
{"subcontrol_id": "SC-1", "title": "Password Security", "keywords": ["passwort"], "object_hint": ""},
{"subcontrol_id": "SC-2", "title": "Network Security", "keywords": ["netzwerk"], "object_hint": ""},
]
selected = _select_subcontrols("Password Security muss implementiert werden", subcontrols)
assert len(selected) >= 1
assert selected[0]["subcontrol_id"] == "SC-1"
# ---------------------------------------------------------------------------
# DECOMPOSITION TESTS
# ---------------------------------------------------------------------------
class TestDecomposeFrameworkContainer:
def test_decompose_ccm_ais(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-001",
parent_control_id="COMP-001",
obligation_text="Die CCM-Praktiken fuer AIS muessen implementiert werden",
framework_ref="CSA_CCM",
framework_domain="AIS",
)
assert result.release_state == "decomposed"
assert result.framework_ref == "CSA_CCM"
assert result.framework_domain == "AIS"
assert len(result.decomposed_obligations) >= 3
assert len(result.matched_subcontrols) >= 3
def test_decomposed_obligations_have_ids(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-001",
parent_control_id="COMP-001",
obligation_text="CCM-Praktiken fuer AIS",
framework_ref="CSA_CCM",
framework_domain="AIS",
)
for d in result.decomposed_obligations:
assert d.obligation_candidate_id.startswith("OBL-001-AIS-")
assert d.parent_control_id == "COMP-001"
assert d.source_ref_law == "Cloud Security Alliance CCM v4"
assert d.routing_type == "atomic"
assert d.release_state == "decomposed"
def test_decomposed_have_action_and_object(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-002",
parent_control_id="COMP-002",
obligation_text="CSA CCM AIS Massnahmen implementieren",
framework_ref="CSA_CCM",
framework_domain="AIS",
)
for d in result.decomposed_obligations:
assert d.action_raw, f"{d.subcontrol_id} missing action_raw"
assert d.object_raw, f"{d.subcontrol_id} missing object_raw"
def test_unknown_framework_returns_unmatched(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-003",
parent_control_id="COMP-003",
obligation_text="XYZ-Framework Controls",
framework_ref="NONEXISTENT",
framework_domain="ABC",
)
assert result.release_state == "unmatched"
assert any("framework_not_matched" in i for i in result.issues)
assert len(result.decomposed_obligations) == 0
def test_unknown_domain_falls_back_to_full(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-004",
parent_control_id="COMP-004",
obligation_text="CSA CCM Kontrollen implementieren",
framework_ref="CSA_CCM",
framework_domain=None,
)
# Should still decompose (falls back to keyword match or all domains)
assert result.release_state in ("decomposed", "unmatched")
def test_nist_incident_response_decomposition(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-010",
parent_control_id="COMP-010",
obligation_text="NIST SP 800-53 Vorfallreaktionsmassnahmen implementieren",
framework_ref="NIST_SP800_53",
framework_domain="IR",
)
assert result.release_state == "decomposed"
assert len(result.decomposed_obligations) >= 3
sc_ids = [d.subcontrol_id for d in result.decomposed_obligations]
assert any("IR-" in sc for sc in sc_ids)
def test_confidence_high_with_full_match(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-005",
parent_control_id="COMP-005",
obligation_text="CSA CCM AIS",
framework_ref="CSA_CCM",
framework_domain="AIS",
)
assert result.decomposition_confidence >= 0.7
def test_confidence_low_without_framework(self):
result = decompose_framework_container(
obligation_candidate_id="OBL-006",
parent_control_id="COMP-006",
obligation_text="Unbekannte Massnahmen",
framework_ref=None,
framework_domain=None,
)
assert result.decomposition_confidence <= 0.3
# ---------------------------------------------------------------------------
# COMPOUND DETECTION TESTS
# ---------------------------------------------------------------------------
class TestCompoundDetection:
def test_compound_verb(self):
assert _is_compound_obligation(
"erstellen und schulen",
"Richtlinie erstellen und Schulungen durchfuehren",
)
def test_no_split_phrase(self):
assert not _is_compound_obligation(
"dokumentieren und pflegen",
"Richtlinie dokumentieren und pflegen",
)
def test_no_split_define_and_maintain(self):
assert not _is_compound_obligation(
"define and maintain",
"Define and maintain a security policy",
)
def test_single_verb_not_compound(self):
assert not _is_compound_obligation(
"implementieren",
"MFA implementieren",
)
def test_empty_action_not_compound(self):
assert not _is_compound_obligation("", "something")
# ---------------------------------------------------------------------------
# FRAMEWORK KEYWORD TESTS
# ---------------------------------------------------------------------------
class TestFrameworkKeywords:
def test_two_keywords_detected(self):
assert _has_framework_keywords("Framework-Praktiken implementieren")
def test_single_keyword_not_enough(self):
assert not _has_framework_keywords("Praktiken implementieren")
def test_no_keywords(self):
assert not _has_framework_keywords("MFA einrichten")
# ---------------------------------------------------------------------------
# INFERENCE HELPER TESTS
# ---------------------------------------------------------------------------
class TestInferAction:
def test_infer_implementieren(self):
assert _infer_action("Massnahmen muessen implementiert werden") == "implementieren"
def test_infer_dokumentieren(self):
assert _infer_action("Richtlinie muss dokumentiert werden") == "dokumentieren"
def test_infer_testen(self):
assert _infer_action("System wird getestet") == "testen"
def test_infer_ueberwachen(self):
assert _infer_action("Logs werden ueberwacht") == "ueberwachen"
def test_infer_default(self):
assert _infer_action("etwas passiert") == "implementieren"
class TestInferObject:
def test_infer_from_muessen_pattern(self):
result = _infer_object("Zugriffsrechte muessen ueberprueft werden")
assert "ueberprueft" in result or "Zugriffsrechte" in result
def test_infer_fallback(self):
result = _infer_object("Einfacher Satz ohne Modalverb")
assert len(result) > 0