diff --git a/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx b/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx new file mode 100644 index 0000000..5dd20aa --- /dev/null +++ b/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx @@ -0,0 +1,166 @@ +'use client' + +import React from 'react' + +const NORMS = [ + { value: 'ISO12100', label: 'ISO 12100 (Maschinensicherheit)' }, + { value: 'ENISO13849', label: 'EN ISO 13849 (Sicherheitsfunktionen)' }, + { value: 'IEC61508', label: 'IEC 61508 (Funktionale Sicherheit)' }, + { value: 'IEC62443', label: 'IEC 62443 (Industrielle Cybersecurity)' }, + { value: 'ISO27001', label: 'ISO 27001 (Informationssicherheit)' }, + { value: 'ISO27002', label: 'ISO 27002 (Security Controls)' }, + { value: 'EN61326', label: 'EN 61326 (EMV)' }, + { value: 'EN62368', label: 'EN 62368 (Audio/Video/IT-Sicherheit)' }, + { value: 'IEC60204', label: 'IEC 60204 (Elektrische Ausruestung)' }, + { value: 'ISO13485', label: 'ISO 13485 (Medizinprodukte QM)' }, + { value: 'ISO14971', label: 'ISO 14971 (Risikomanagement Medizin)' }, + { value: 'IEC62304', label: 'IEC 62304 (Medizin-Software Lifecycle)' }, + { value: 'ISO9001', label: 'ISO 9001 (Qualitaetsmanagement)' }, + { value: 'ISO22301', label: 'ISO 22301 (Business Continuity)' }, + { value: 'PCIDSS', label: 'PCI DSS (Zahlungssicherheit)' }, + { value: 'EN50581', label: 'EN 50581 (RoHS/REACH)' }, + { value: 'ASPICE', label: 'ASPICE (Automotive Software)' }, +] + +interface IstData { + applied_norms: string[] + has_risk_assessment: boolean + has_technical_file: boolean + has_operating_manual: boolean + has_sbom: boolean + has_vuln_management: boolean + has_update_mechanism: boolean + has_incident_response: boolean + has_supply_chain_mgmt: boolean + ce_marking_since: string + product_age: string +} + +interface Props { + data: IstData + onChange: (data: IstData) => void +} + +export function IstAssessment({ data, onChange }: Props) { + const update = (field: string, value: unknown) => { + onChange({ ...data, [field]: value }) + } + + const toggleNorm = (norm: string) => { + const norms = data.applied_norms.includes(norm) + ? data.applied_norms.filter(n => n !== norm) + : [...data.applied_norms, norm] + update('applied_norms', norms) + } + + return ( +
+
+

+ Geben Sie an was Sie bereits haben. Je mehr wir wissen, desto + praeziser ist die Gap-Analyse. Controls die bereits erfuellt sind + werden automatisch als "erledigt" markiert. +

+
+ + {/* CE-Kennzeichnung */} +
+

CE-Kennzeichnung

+
+
+ + update('ce_marking_since', e.target.value)} + placeholder="z.B. 2016" + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" + /> +
+
+ + +
+
+
+ + {/* Angewandte Normen */} +
+

Angewandte Normen

+
+ {NORMS.map(n => ( + + ))} +
+
+ + {/* Bestehende Dokumentation */} +
+

Bestehende Dokumentation

+
+ {[ + { field: 'has_risk_assessment', label: 'Risikobeurteilung vorhanden' }, + { field: 'has_technical_file', label: 'Technische Dokumentation vorhanden' }, + { field: 'has_operating_manual', label: 'Betriebsanleitung vorhanden' }, + { field: 'has_sbom', label: 'SBOM (Software Bill of Materials)' }, + ].map(item => ( + + ))} +
+
+ + {/* Bestehende Prozesse */} +
+

Bestehende Prozesse

+
+ {[ + { field: 'has_vuln_management', label: 'Schwachstellenmanagement' }, + { field: 'has_update_mechanism', label: 'Software-Update-Mechanismus' }, + { field: 'has_incident_response', label: 'Incident Response Prozess' }, + { field: 'has_supply_chain_mgmt', label: 'Lieferketten-Management' }, + ].map(item => ( + + ))} +
+
+
+ ) +} diff --git a/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx b/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx index f78137d..03f1a11 100644 --- a/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx +++ b/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useState } from 'react' +import { IstAssessment } from './IstAssessment' const PRODUCT_TYPES = [ { value: 'iot', label: 'IoT / Connected Device' }, @@ -60,6 +61,20 @@ export function ProductWizard({ onAnalyze, loading }: Props) { const [usesAI, setUsesAI] = useState(false) const [processesPersonalData, setProcessesPersonalData] = useState(false) const [isCriticalInfra, setIsCriticalInfra] = useState(false) + const [step, setStep] = useState(1) + const [istData, setIstData] = useState({ + applied_norms: [] as string[], + has_risk_assessment: false, + has_technical_file: false, + has_operating_manual: false, + has_sbom: false, + has_vuln_management: false, + has_update_mechanism: false, + has_incident_response: false, + has_supply_chain_mgmt: false, + ce_marking_since: '', + product_age: '', + }) const toggleArrayValue = ( arr: string[], @@ -83,11 +98,59 @@ export function ProductWizard({ onAnalyze, loading }: Props) { processes_personal_data: processesPersonalData, is_critical_infra_supplier: isCriticalInfra, existing_certifications: certifications, + ...istData, }) } return (
+ {/* Step Indicator */} +
+ + + +
+ + {step === 2 && ( + <> + +
+ + +
+ + )} + + {step === 1 && (<> {/* Produktname */}
- {/* Submit */} + {/* Next Step */} + )} ) } diff --git a/ai-compliance-sdk/internal/gap/gap_engine.go b/ai-compliance-sdk/internal/gap/gap_engine.go index b16f3fa..043f9ad 100644 --- a/ai-compliance-sdk/internal/gap/gap_engine.go +++ b/ai-compliance-sdk/internal/gap/gap_engine.go @@ -38,7 +38,7 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) { // Step 4: Assess gaps gaps := make([]GapItem, 0, len(mcGroups)) for _, mc := range mcGroups { - status := e.assessGapStatus(mc, profile.ExistingCertifications) + status := e.assessGapStatus(mc, profile) item := GapItem{ MCID: mc.MasterControlID, MCName: mc.CanonicalName, @@ -77,27 +77,80 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) { return report, nil } -// assessGapStatus determines if a MC is fulfilled based on existing certs. -func (e *Engine) assessGapStatus(mc MCGroup, certs []string) GapStatus { - // If customer has ISO 27001, many security controls are likely fulfilled - for _, cert := range certs { +// assessGapStatus determines if a MC is fulfilled based on IST-Zustand: +// IACE project data, applied norms, certifications, and existing processes. +func (e *Engine) assessGapStatus(mc MCGroup, profile *ProductProfile) GapStatus { + name := mc.CanonicalName + + // A) IACE-Projekt vorhanden → aus verified Mitigations ableiten + if profile.IACEProjectID != nil { + status := e.store.CheckIACECoverage(*profile.IACEProjectID, name) + if status == "verified" { + return GapFulfilled + } + if status == "implemented" { + return GapPartial + } + } + + // B) Bestehende Zertifizierungen + for _, cert := range profile.ExistingCertifications { switch cert { - case "ISO27001": - if isSecurityTopic(mc.CanonicalName) { - return GapPartial // Likely partially covered - } case "CE": - if isMachineryTopic(mc.CanonicalName) { + if isMachineryTopic(name) { return GapFulfilled } + case "ISO27001": + if isSecurityTopic(name) { + return GapPartial + } case "SOC2": - if isSecurityTopic(mc.CanonicalName) { + if isSecurityTopic(name) { + return GapPartial + } + case "ISO13485": + if contains(name, "risk_management") || contains(name, "documentation") { return GapPartial } } } - // Default: missing (customer must verify) + // C) Angewandte Normen → Controls als fulfilled erkennen + if normCoversControl(profile.AppliedNorms, name) { + return GapFulfilled + } + + // D) IST-Felder direkt matchen + if profile.HasSBOM && contains(name, "asset_management_inventory") { + return GapFulfilled + } + if profile.HasVulnManagement && contains(name, "vulnerability") { + return GapFulfilled + } + if profile.HasUpdateMechanism && contains(name, "patch_management") { + return GapFulfilled + } + if profile.HasIncidentResponse && contains(name, "incident") { + return GapFulfilled + } + if profile.HasRiskAssessment && contains(name, "risk_management") { + return GapFulfilled + } + if profile.HasTechnicalFile && contains(name, "documentation") { + return GapFulfilled + } + if profile.HasOperatingManual && contains(name, "operating_instructions") { + return GapFulfilled + } + if profile.HasSupplyChainMgmt && contains(name, "third_party_management") { + return GapFulfilled + } + + // E) CE-Kennzeichnung vorhanden → Produktsicherheit fulfilled + if profile.CEMarkingSince != nil && isMachineryTopic(name) { + return GapFulfilled + } + return GapMissing } diff --git a/ai-compliance-sdk/internal/gap/models.go b/ai-compliance-sdk/internal/gap/models.go index 224e42f..4862da7 100644 --- a/ai-compliance-sdk/internal/gap/models.go +++ b/ai-compliance-sdk/internal/gap/models.go @@ -52,6 +52,30 @@ type ProductProfile struct { // Existing certifications (reduces gap count) ExistingCertifications []string `json:"existing_certifications" db:"-"` // ISO27001, CE, SOC2 + // ── IST-Zustand (was hat der Hersteller bereits?) ────────────── + + // Verbindung zu bestehendem IACE Projekt + IACEProjectID *uuid.UUID `json:"iace_project_id" db:"iace_project_id"` + + // Angewandte Normen + AppliedNorms []string `json:"applied_norms" db:"-"` // ISO12100, EN61326, EN62368 + + // Bestehende Dokumentation + HasRiskAssessment bool `json:"has_risk_assessment" db:"has_risk_assessment"` + HasTechnicalFile bool `json:"has_technical_file" db:"has_technical_file"` + HasOperatingManual bool `json:"has_operating_manual" db:"has_operating_manual"` + HasSBOM bool `json:"has_sbom" db:"has_sbom"` + + // Bestehende Prozesse + HasVulnManagement bool `json:"has_vuln_management" db:"has_vuln_management"` + HasUpdateMechanism bool `json:"has_update_mechanism" db:"has_update_mechanism"` + HasIncidentResponse bool `json:"has_incident_response" db:"has_incident_response"` + HasSupplyChainMgmt bool `json:"has_supply_chain_mgmt" db:"has_supply_chain_mgmt"` + + // CE/Produktsicherheit + CEMarkingSince *string `json:"ce_marking_since" db:"ce_marking_since"` + ProductAge string `json:"product_age" db:"product_age"` + // Metadata CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` diff --git a/ai-compliance-sdk/internal/gap/norm_mapping.go b/ai-compliance-sdk/internal/gap/norm_mapping.go new file mode 100644 index 0000000..2dce62c --- /dev/null +++ b/ai-compliance-sdk/internal/gap/norm_mapping.go @@ -0,0 +1,73 @@ +package gap + +// NormToControlMapping maps applied norms to MC topic prefixes they cover. +// If a manufacturer has applied a norm, all matching MC topics are "fulfilled". +var NormToControlMapping = map[string][]string{ + // Machine Safety + "ISO12100": {"risk_management_assessment", "risk_management_documentation", "product_safety"}, + "ENISO13849": {"product_safety", "risk_management_assessment", "secure_development"}, + "IEC61508": {"product_safety", "risk_management", "secure_development"}, + "IEC62061": {"product_safety", "risk_management"}, + + // EMC / Electrical Safety + "EN61326": {"network_security", "physical_security"}, + "EN62368": {"physical_security", "product_safety"}, + "IEC60204": {"physical_security", "product_safety"}, + + // Information Security + "ISO27001": { + "access_control", "encryption", "incident", "audit_logging", + "vulnerability", "patch_management", "risk_management", + "human_resources_security", "physical_security", "backup", + "disaster_recovery", "change_management", "asset_management", + "monitoring", "network_security", + }, + "ISO27002": { + "access_control", "encryption", "audit_logging", + "vulnerability", "patch_management", + }, + + // Industrial Cybersecurity + "IEC62443": { + "network_security", "network_segmentation", "access_control", + "monitoring", "vulnerability", "patch_management", + "incident", "secure_development", + }, + + // Medical Devices + "ISO13485": {"risk_management", "documentation", "change_management", "training"}, + "IEC60601": {"physical_security", "product_safety"}, + "ISO14971": {"risk_management_assessment", "risk_management_documentation"}, + "IEC62304": {"secure_development", "change_management", "documentation"}, + + // Crypto/Fintech + "ISO22301": {"disaster_recovery", "backup", "incident"}, + "PCIDSS": {"encryption", "access_control", "audit_logging", "vulnerability", "network_segmentation"}, + + // Quality / Environmental + "ISO9001": {"change_management", "documentation", "training", "compliance_audit"}, + "ISO14001": {"compliance_audit", "documentation", "risk_management"}, + + // Product Safety / RoHS / REACH + "EN50581": {"supply_chain_due_diligence", "product_safety"}, + + // Functional Safety (software) + "ASPICE": {"secure_development", "change_management", "documentation"}, + "ISO26262": {"secure_development", "risk_management", "product_safety"}, +} + +// normCoversControl checks if any applied norm covers a given MC topic. +func normCoversControl(appliedNorms []string, mcTopic string) bool { + for _, norm := range appliedNorms { + topics, ok := NormToControlMapping[norm] + if !ok { + continue + } + for _, topic := range topics { + if contains(mcTopic, topic) { + return true + } + } + } + return false +} diff --git a/ai-compliance-sdk/internal/gap/store.go b/ai-compliance-sdk/internal/gap/store.go index e725014..9bc7d18 100644 --- a/ai-compliance-sdk/internal/gap/store.go +++ b/ai-compliance-sdk/internal/gap/store.go @@ -35,19 +35,29 @@ func (s *Store) CreateProfile(p *ProductProfile) error { marketsJSON, _ := json.Marshal(p.Markets) certsJSON, _ := json.Marshal(p.ExistingCertifications) + normsJSON, _ := json.Marshal(p.AppliedNorms) + _, err := s.pool.Exec(ctx, ` INSERT INTO compliance.gap_projects (id, tenant_id, name, description, product_type, technologies, data_processing, markets, connected_to_internet, has_software_updates, uses_ai, processes_personal_data, is_critical_infra_supplier, - existing_certifications, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`, + existing_certifications, applied_norms, + has_risk_assessment, has_technical_file, has_operating_manual, has_sbom, + has_vuln_management, has_update_mechanism, has_incident_response, has_supply_chain_mgmt, + ce_marking_since, product_age, iace_project_id, + created_at, updated_at) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28)`, p.ID, p.TenantID, p.Name, p.Description, p.ProductType, techJSON, dataJSON, marketsJSON, p.ConnectedToInternet, p.HasSoftwareUpdates, p.UsesAI, p.ProcessesPersonalData, p.IsCriticalInfraSupplier, - certsJSON, p.CreatedAt, p.UpdatedAt, + certsJSON, normsJSON, + p.HasRiskAssessment, p.HasTechnicalFile, p.HasOperatingManual, p.HasSBOM, + p.HasVulnManagement, p.HasUpdateMechanism, p.HasIncidentResponse, p.HasSupplyChainMgmt, + p.CEMarkingSince, p.ProductAge, p.IACEProjectID, + p.CreatedAt, p.UpdatedAt, ) return err } @@ -227,6 +237,58 @@ func sourceToRegID(source string) RegulationID { } } +// CheckIACECoverage checks if an IACE project has verified mitigations +// covering the given MC topic. +func (s *Store) CheckIACECoverage(projectID uuid.UUID, mcTopic string) string { + ctx := context.Background() + + // Map MC topics to IACE hazard categories + iaceCategory := mcTopicToIACECategory(mcTopic) + if iaceCategory == "" { + return "" + } + + var verifiedCount, implementedCount int + err := s.pool.QueryRow(ctx, ` + SELECT + COUNT(CASE WHEN m.status = 'verified' THEN 1 END), + COUNT(CASE WHEN m.status = 'implemented' THEN 1 END) + FROM iace_mitigations m + JOIN iace_hazards h ON h.id = m.hazard_id + WHERE h.project_id = $1 + AND (h.category ILIKE $2 OR h.sub_category ILIKE $2)`, + projectID, "%"+iaceCategory+"%", + ).Scan(&verifiedCount, &implementedCount) + + if err != nil || (verifiedCount == 0 && implementedCount == 0) { + return "" + } + if verifiedCount > 0 { + return "verified" + } + return "implemented" +} + +func mcTopicToIACECategory(topic string) string { + mapping := map[string]string{ + "encryption": "cyber", + "access_control": "software", + "network_security": "cyber", + "vulnerability": "cyber", + "product_safety": "mechanical", + "physical_security": "electrical", + "monitoring": "software", + "incident": "organizational", + "risk_management": "general", + } + for prefix, cat := range mapping { + if strings.HasPrefix(topic, prefix) { + return cat + } + } + return "" +} + func formatTitle(name string) string { return strings.ReplaceAll( strings.ReplaceAll(name, "_", " "), diff --git a/ai-compliance-sdk/migrations/025_gap_projects.sql b/ai-compliance-sdk/migrations/025_gap_projects.sql index 341bbb8..7596748 100644 --- a/ai-compliance-sdk/migrations/025_gap_projects.sql +++ b/ai-compliance-sdk/migrations/025_gap_projects.sql @@ -16,6 +16,18 @@ CREATE TABLE IF NOT EXISTS compliance.gap_projects ( processes_personal_data BOOLEAN DEFAULT false, is_critical_infra_supplier BOOLEAN DEFAULT false, existing_certifications JSONB DEFAULT '[]', + applied_norms JSONB DEFAULT '[]', + has_risk_assessment BOOLEAN DEFAULT false, + has_technical_file BOOLEAN DEFAULT false, + has_operating_manual BOOLEAN DEFAULT false, + has_sbom BOOLEAN DEFAULT false, + has_vuln_management BOOLEAN DEFAULT false, + has_update_mechanism BOOLEAN DEFAULT false, + has_incident_response BOOLEAN DEFAULT false, + has_supply_chain_mgmt BOOLEAN DEFAULT false, + ce_marking_since VARCHAR(20), + product_age VARCHAR(20), + iace_project_id UUID, last_analysis_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()