From 4b1eede45b473cee9be364ba7241dea9d21b6332 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 19 Mar 2026 11:56:53 +0100 Subject: [PATCH] feat(tom): audit document, compliance checks, 25 controls, canonical control mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A: TOM document HTML generator (12 sections, inline CSS, A4 print) Phase B: TOMDocumentTab component (org-header form, revisions, print/download) Phase C: 11 compliance checks with severity-weighted scoring Phase D: MkDocs documentation for TOM module Phase E: 25 new controls (63 → 88) in 13 categories Canonical Control Mapping (three-layer architecture): - Migration 068: tom_control_mappings + tom_control_sync_state tables - 6 API endpoints: sync, list, by-tom, stats, manual add, delete - Category mapping: 13 TOM categories → 17 canonical categories - Frontend: sync button + coverage card (Overview), drill-down (Editor), belegende Controls count (Document) - 20 tests (unit + API with mocked DB) Co-Authored-By: Claude Opus 4.6 --- admin-compliance/app/sdk/tom/page.tsx | 32 +- .../sdk/tom-dashboard/TOMDocumentTab.tsx | 449 +++++++++ .../sdk/tom-dashboard/TOMEditorTab.tsx | 78 +- .../sdk/tom-dashboard/TOMOverviewTab.tsx | 99 +- .../components/sdk/tom-dashboard/index.ts | 1 + admin-compliance/lib/sdk/tom-compliance.ts | 553 +++++++++++ admin-compliance/lib/sdk/tom-document.ts | 906 ++++++++++++++++++ .../lib/sdk/tom-generator/controls/loader.ts | 648 ++++++++++++- backend-compliance/compliance/api/__init__.py | 2 + .../compliance/api/tom_mapping_routes.py | 537 +++++++++++ .../migrations/068_tom_control_mappings.sql | 65 ++ .../tests/test_tom_mapping_routes.py | 274 ++++++ docs-src/services/sdk-modules/tom.md | 271 ++++++ mkdocs.yml | 3 + 14 files changed, 3910 insertions(+), 8 deletions(-) create mode 100644 admin-compliance/components/sdk/tom-dashboard/TOMDocumentTab.tsx create mode 100644 admin-compliance/lib/sdk/tom-compliance.ts create mode 100644 admin-compliance/lib/sdk/tom-document.ts create mode 100644 backend-compliance/compliance/api/tom_mapping_routes.py create mode 100644 backend-compliance/migrations/068_tom_control_mappings.sql create mode 100644 backend-compliance/tests/test_tom_mapping_routes.py create mode 100644 docs-src/services/sdk-modules/tom.md diff --git a/admin-compliance/app/sdk/tom/page.tsx b/admin-compliance/app/sdk/tom/page.tsx index 7eb401a..09e2240 100644 --- a/admin-compliance/app/sdk/tom/page.tsx +++ b/admin-compliance/app/sdk/tom/page.tsx @@ -1,18 +1,19 @@ 'use client' -import React, { useState, useCallback, useMemo } from 'react' +import React, { useState, useCallback, useMemo, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useSDK } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { useTOMGenerator } from '@/lib/sdk/tom-generator/context' import { DerivedTOM } from '@/lib/sdk/tom-generator/types' -import { TOMOverviewTab, TOMEditorTab, TOMGapExportTab } from '@/components/sdk/tom-dashboard' +import { TOMOverviewTab, TOMEditorTab, TOMGapExportTab, TOMDocumentTab } from '@/components/sdk/tom-dashboard' +import { runTOMComplianceCheck, type TOMComplianceCheckResult } from '@/lib/sdk/tom-compliance' // ============================================================================= // TYPES // ============================================================================= -type Tab = 'uebersicht' | 'editor' | 'generator' | 'gap-export' +type Tab = 'uebersicht' | 'editor' | 'generator' | 'gap-export' | 'tom-dokument' interface TabDefinition { key: Tab @@ -24,6 +25,7 @@ const TABS: TabDefinition[] = [ { key: 'editor', label: 'Detail-Editor' }, { key: 'generator', label: 'Generator' }, { key: 'gap-export', label: 'Gap-Analyse & Export' }, + { key: 'tom-dokument', label: 'TOM-Dokument' }, ] // ============================================================================= @@ -41,6 +43,17 @@ export default function TOMPage() { const [tab, setTab] = useState('uebersicht') const [selectedTOMId, setSelectedTOMId] = useState(null) + const [complianceResult, setComplianceResult] = useState(null) + + // --------------------------------------------------------------------------- + // Compliance check (auto-run when derivedTOMs change) + // --------------------------------------------------------------------------- + + useEffect(() => { + if (state?.derivedTOMs && Array.isArray(state.derivedTOMs) && state.derivedTOMs.length > 0) { + setComplianceResult(runTOMComplianceCheck(state)) + } + }, [state?.derivedTOMs]) // --------------------------------------------------------------------------- // Computed / memoised values @@ -316,6 +329,17 @@ export default function TOMPage() { /> ) + // --------------------------------------------------------------------------- + // Tab 5 – TOM-Dokument + // --------------------------------------------------------------------------- + + const renderTOMDokument = () => ( + + ) + // --------------------------------------------------------------------------- // Tab content router // --------------------------------------------------------------------------- @@ -330,6 +354,8 @@ export default function TOMPage() { return renderGenerator() case 'gap-export': return renderGapExport() + case 'tom-dokument': + return renderTOMDokument() default: return renderUebersicht() } diff --git a/admin-compliance/components/sdk/tom-dashboard/TOMDocumentTab.tsx b/admin-compliance/components/sdk/tom-dashboard/TOMDocumentTab.tsx new file mode 100644 index 0000000..bfa2406 --- /dev/null +++ b/admin-compliance/components/sdk/tom-dashboard/TOMDocumentTab.tsx @@ -0,0 +1,449 @@ +'use client' + +import { useState, useEffect, useCallback, useMemo } from 'react' +import type { TOMGeneratorState } from '@/lib/sdk/tom-generator/types' +import type { TOMComplianceCheckResult } from '@/lib/sdk/tom-compliance' +import { + buildTOMDocumentHtml, + createDefaultTOMDocumentOrgHeader, + type TOMDocumentOrgHeader, + type TOMDocumentRevision, +} from '@/lib/sdk/tom-document' + +// ============================================================================= +// TYPES +// ============================================================================= + +interface TOMDocumentTabProps { + state: TOMGeneratorState + complianceResult: TOMComplianceCheckResult | null +} + +// ============================================================================= +// COMPONENT +// ============================================================================= + +function TOMDocumentTab({ state, complianceResult }: TOMDocumentTabProps) { + // --------------------------------------------------------------------------- + // State + // --------------------------------------------------------------------------- + + const [orgHeader, setOrgHeader] = useState(() => { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('bp_tom_document_orgheader') + if (saved) { + try { return JSON.parse(saved) } catch { /* ignore */ } + } + } + return createDefaultTOMDocumentOrgHeader() + }) + + const [revisions, setRevisions] = useState(() => { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('bp_tom_document_revisions') + if (saved) { + try { return JSON.parse(saved) } catch { /* ignore */ } + } + } + return [] + }) + + // --------------------------------------------------------------------------- + // localStorage persistence + // --------------------------------------------------------------------------- + + useEffect(() => { + localStorage.setItem('bp_tom_document_orgheader', JSON.stringify(orgHeader)) + }, [orgHeader]) + + useEffect(() => { + localStorage.setItem('bp_tom_document_revisions', JSON.stringify(revisions)) + }, [revisions]) + + // --------------------------------------------------------------------------- + // Computed values + // --------------------------------------------------------------------------- + + const tomCount = useMemo(() => { + if (!state?.derivedTOMs) return 0 + return Array.isArray(state.derivedTOMs) ? state.derivedTOMs.length : 0 + }, [state?.derivedTOMs]) + + const applicableTOMs = useMemo(() => { + if (!state?.derivedTOMs || !Array.isArray(state.derivedTOMs)) return [] + return state.derivedTOMs.filter(t => t.applicability !== 'NOT_APPLICABLE') + }, [state?.derivedTOMs]) + + const implementedCount = useMemo(() => { + return applicableTOMs.filter(t => t.implementationStatus === 'IMPLEMENTED').length + }, [applicableTOMs]) + + const [canonicalCount, setCanonicalCount] = useState(0) + useEffect(() => { + if (tomCount === 0) return + fetch('/api/sdk/v1/compliance/tom-mappings/stats') + .then(r => r.ok ? r.json() : null) + .then(data => { if (data?.sync_state?.canonical_controls_matched) setCanonicalCount(data.sync_state.canonical_controls_matched) }) + .catch(() => {}) + }, [tomCount]) + + // --------------------------------------------------------------------------- + // Handlers + // --------------------------------------------------------------------------- + + const handlePrintTOMDocument = useCallback(() => { + const html = buildTOMDocumentHtml( + state?.derivedTOMs || [], + orgHeader, + state?.companyProfile || null, + state?.riskProfile || null, + complianceResult, + revisions, + ) + const printWindow = window.open('', '_blank') + if (printWindow) { + printWindow.document.write(html) + printWindow.document.close() + setTimeout(() => printWindow.print(), 300) + } + }, [state, orgHeader, complianceResult, revisions]) + + const handleDownloadTOMDocumentHtml = useCallback(() => { + const html = buildTOMDocumentHtml( + state?.derivedTOMs || [], + orgHeader, + state?.companyProfile || null, + state?.riskProfile || null, + complianceResult, + revisions, + ) + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `TOM-Dokumentation-${orgHeader.organizationName || 'Organisation'}-${new Date().toISOString().split('T')[0]}.html` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + }, [state, orgHeader, complianceResult, revisions]) + + const handleAddRevision = useCallback(() => { + setRevisions(prev => [...prev, { + version: String(prev.length + 1) + '.0', + date: new Date().toISOString().split('T')[0], + author: '', + changes: '', + }]) + }, []) + + const handleUpdateRevision = useCallback((index: number, field: keyof TOMDocumentRevision, value: string) => { + setRevisions(prev => prev.map((r, i) => i === index ? { ...r, [field]: value } : r)) + }, []) + + const handleRemoveRevision = useCallback((index: number) => { + setRevisions(prev => prev.filter((_, i) => i !== index)) + }, []) + + const updateOrgHeader = useCallback((field: keyof TOMDocumentOrgHeader, value: string | string[]) => { + setOrgHeader(prev => ({ ...prev, [field]: value })) + }, []) + + // --------------------------------------------------------------------------- + // Render + // --------------------------------------------------------------------------- + + return ( +
+ {/* 1. Action Bar */} +
+
+
+

TOM-Dokument (Art. 32 DSGVO)

+

+ Auditfaehiges Dokument mit {applicableTOMs.length} Massnahmen generieren +

+
+
+ + +
+
+
+ + {/* 2. Org Header Form */} +
+

Organisationsdaten

+
+
+ + updateOrgHeader('organizationName', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('industry', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('dpoName', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('dpoContact', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('responsiblePerson', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('itSecurityContact', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('employeeCount', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('locations', e.target.value.split(',').map(s => s.trim()))} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('documentVersion', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('reviewInterval', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('lastReviewDate', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+ + updateOrgHeader('nextReviewDate', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> +
+
+
+ + {/* 3. Revisions Manager */} +
+
+

Aenderungshistorie

+ +
+ {revisions.length > 0 ? ( + + + + + + + + + + + + {revisions.map((revision, index) => ( + + + + + + + + ))} + +
VersionDatumAutorAenderungen
+ handleUpdateRevision(index, 'version', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> + + handleUpdateRevision(index, 'date', e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> + + handleUpdateRevision(index, 'author', e.target.value)} + placeholder="Name" + className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> + + handleUpdateRevision(index, 'changes', e.target.value)} + placeholder="Beschreibung der Aenderungen" + className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none" + /> + + +
+ ) : ( +

+ Noch keine Revisionen. Die erste Version wird automatisch im Dokument eingetragen. +

+ )} +
+ + {/* 4. Document Preview */} +
+

Dokument-Vorschau

+ {tomCount === 0 ? ( +
+

Starten Sie den TOM-Generator, um Massnahmen abzuleiten.

+
+ ) : ( +
+ {/* Cover preview */} +
+

TOM-Dokumentation

+

+ Art. 32 DSGVO — {orgHeader.organizationName || 'Organisation'} +

+

+ Version {orgHeader.documentVersion} | Stand: {new Date().toLocaleDateString('de-DE')} +

+
+ + {/* Stats */} +
+
+

{applicableTOMs.length}

+

Massnahmen

+
+
+

{implementedCount}

+

Umgesetzt

+
+
+

{canonicalCount || '-'}

+

Belegende Controls

+
+
+

12

+

Sektionen

+
+
+

+ {complianceResult ? complianceResult.score : '-'} +

+

Compliance-Score

+
+
+ + {/* 12 Sections list */} +
+

12 Dokument-Sektionen:

+
    +
  1. Ziel und Zweck
  2. +
  3. Geltungsbereich
  4. +
  5. Grundprinzipien Art. 32
  6. +
  7. Schutzbedarf und Risikoanalyse
  8. +
  9. Massnahmen-Uebersicht
  10. +
  11. Detaillierte Massnahmen
  12. +
  13. SDM Gewaehrleistungsziele
  14. +
  15. Verantwortlichkeiten
  16. +
  17. Pruef- und Revisionszyklus
  18. +
  19. Compliance-Status
  20. +
  21. Aenderungshistorie
  22. +
+
+
+ )} +
+
+ ) +} + +export { TOMDocumentTab } diff --git a/admin-compliance/components/sdk/tom-dashboard/TOMEditorTab.tsx b/admin-compliance/components/sdk/tom-dashboard/TOMEditorTab.tsx index aa42e53..5e73806 100644 --- a/admin-compliance/components/sdk/tom-dashboard/TOMEditorTab.tsx +++ b/admin-compliance/components/sdk/tom-dashboard/TOMEditorTab.tsx @@ -1,9 +1,18 @@ 'use client' -import { useMemo, useState, useEffect } from 'react' +import { useMemo, useState, useEffect, useCallback } from 'react' import { DerivedTOM, TOMGeneratorState } from '@/lib/sdk/tom-generator/types' import { getControlById } from '@/lib/sdk/tom-generator/controls/loader' +interface CanonicalMapping { + id: string + canonical_control_code: string + canonical_title: string | null + canonical_severity: string | null + canonical_objective: string | null + mapping_type: string +} + interface TOMEditorTabProps { state: TOMGeneratorState selectedTOMId: string | null @@ -46,6 +55,17 @@ export function TOMEditorTab({ state, selectedTOMId, onUpdateTOM, onBack }: TOME const [notes, setNotes] = useState('') const [linkedEvidence, setLinkedEvidence] = useState([]) const [selectedEvidenceId, setSelectedEvidenceId] = useState('') + const [canonicalMappings, setCanonicalMappings] = useState([]) + const [showCanonical, setShowCanonical] = useState(false) + + // Load canonical controls for this TOM's category + useEffect(() => { + if (!control?.category) { setCanonicalMappings([]); return } + fetch(`/api/sdk/v1/compliance/tom-mappings/by-tom/${encodeURIComponent(control.category)}`) + .then(r => r.ok ? r.json() : null) + .then(data => { if (data?.mappings) setCanonicalMappings(data.mappings) }) + .catch(() => setCanonicalMappings([])) + }, [control?.category]) useEffect(() => { if (tom) { @@ -341,6 +361,62 @@ export function TOMEditorTab({ state, selectedTOMId, onUpdateTOM, onBack }: TOME )} + {/* Canonical Controls (Belegende Security-Controls) */} + {canonicalMappings.length > 0 && ( +
+
+

+ Belegende Security-Controls ({canonicalMappings.length}) +

+ +
+
+ {(showCanonical ? canonicalMappings : canonicalMappings.slice(0, 5)).map(m => ( +
+
+ + {m.canonical_control_code} + +
+
+

{m.canonical_title || m.canonical_control_code}

+ {m.canonical_objective && showCanonical && ( +

{m.canonical_objective}

+ )} +
+
+ {m.canonical_severity && ( + + {m.canonical_severity} + + )} + + {m.mapping_type === 'manual' ? 'manuell' : 'auto'} + +
+
+ ))} +
+ {!showCanonical && canonicalMappings.length > 5 && ( +

+ + {canonicalMappings.length - 5} weitere Controls +

+ )} +
+ )} + {/* Framework Mappings */} {control?.mappings && control.mappings.length > 0 && (
diff --git a/admin-compliance/components/sdk/tom-dashboard/TOMOverviewTab.tsx b/admin-compliance/components/sdk/tom-dashboard/TOMOverviewTab.tsx index 06d0008..724e01c 100644 --- a/admin-compliance/components/sdk/tom-dashboard/TOMOverviewTab.tsx +++ b/admin-compliance/components/sdk/tom-dashboard/TOMOverviewTab.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, useEffect, useCallback } from 'react' import { DerivedTOM, TOMGeneratorState } from '@/lib/sdk/tom-generator/types' import { getControlById, getControlsByCategory, getAllCategories } from '@/lib/sdk/tom-generator/controls/loader' import { SDM_GOAL_LABELS, getSDMCoverageStats, SDMGewaehrleistungsziel } from '@/lib/sdk/tom-generator/sdm-mapping' @@ -11,6 +11,18 @@ interface TOMOverviewTabProps { onStartGenerator: () => void } +interface MappingStats { + sync_state: { + profile_hash: string | null + total_mappings: number + canonical_controls_matched: number + tom_controls_covered: number + last_synced_at: string | null + } + category_breakdown: { tom_category: string; total_mappings: number; unique_controls: number }[] + total_canonical_controls_available: number +} + const STATUS_BADGES: Record = { IMPLEMENTED: { label: 'Implementiert', className: 'bg-green-100 text-green-700' }, PARTIAL: { label: 'Teilweise', className: 'bg-yellow-100 text-yellow-700' }, @@ -34,9 +46,41 @@ export function TOMOverviewTab({ state, onSelectTOM, onStartGenerator }: TOMOver const [typeFilter, setTypeFilter] = useState('ALL') const [statusFilter, setStatusFilter] = useState('ALL') const [applicabilityFilter, setApplicabilityFilter] = useState('ALL') + const [mappingStats, setMappingStats] = useState(null) + const [syncing, setSyncing] = useState(false) const categories = useMemo(() => getAllCategories(), []) + // Load mapping stats + useEffect(() => { + if (state.derivedTOMs.length === 0) return + fetch('/api/sdk/v1/compliance/tom-mappings/stats') + .then(r => r.ok ? r.json() : null) + .then(data => { if (data) setMappingStats(data) }) + .catch(() => {}) + }, [state.derivedTOMs.length]) + + const handleSyncControls = useCallback(async () => { + setSyncing(true) + try { + const resp = await fetch('/api/sdk/v1/compliance/tom-mappings/sync', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + industry: state.companyProfile?.industry || null, + company_size: state.companyProfile?.size || null, + force: false, + }), + }) + if (resp.ok) { + // Reload stats after sync + const statsResp = await fetch('/api/sdk/v1/compliance/tom-mappings/stats') + if (statsResp.ok) setMappingStats(await statsResp.json()) + } + } catch { /* ignore */ } + setSyncing(false) + }, [state.companyProfile]) + const stats = useMemo(() => { const toms = state.derivedTOMs return { @@ -159,6 +203,59 @@ export function TOMOverviewTab({ state, onSelectTOM, onStartGenerator }: TOMOver
+ {/* Canonical Control Library Coverage */} +
+
+
+

Canonical Control Library

+

+ Belegende Security-Controls aus OWASP, NIST, ENISA +

+
+ +
+ {mappingStats ? ( +
+
+
{mappingStats.sync_state.total_mappings}
+
Zugeordnete Controls
+
+
+
{mappingStats.sync_state.canonical_controls_matched}
+
Einzigartige Controls
+
+
+
{mappingStats.sync_state.tom_controls_covered}/13
+
Kategorien abgedeckt
+
+
+
{mappingStats.total_canonical_controls_available}
+
Verfuegbare Controls
+
+
+ ) : ( +
+

+ Noch keine Controls synchronisiert. Klicken Sie "Controls synchronisieren", um relevante + Security-Controls aus der Canonical Control Library zuzuordnen. +

+
+ )} + {mappingStats?.sync_state?.last_synced_at && ( +

+ Letzte Synchronisation: {new Date(mappingStats.sync_state.last_synced_at).toLocaleDateString('de-DE', { + day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' + })} +

+ )} +
+ {/* Filter Controls */}
diff --git a/admin-compliance/components/sdk/tom-dashboard/index.ts b/admin-compliance/components/sdk/tom-dashboard/index.ts index 2c8239c..2822de4 100644 --- a/admin-compliance/components/sdk/tom-dashboard/index.ts +++ b/admin-compliance/components/sdk/tom-dashboard/index.ts @@ -1,3 +1,4 @@ export { TOMOverviewTab } from './TOMOverviewTab' export { TOMEditorTab } from './TOMEditorTab' export { TOMGapExportTab } from './TOMGapExportTab' +export { TOMDocumentTab } from './TOMDocumentTab' diff --git a/admin-compliance/lib/sdk/tom-compliance.ts b/admin-compliance/lib/sdk/tom-compliance.ts new file mode 100644 index 0000000..32534c0 --- /dev/null +++ b/admin-compliance/lib/sdk/tom-compliance.ts @@ -0,0 +1,553 @@ +// ============================================================================= +// TOM Module - Compliance Check Engine +// Prueft Technische und Organisatorische Massnahmen auf Vollstaendigkeit, +// Konsistenz und DSGVO-Konformitaet (Art. 32 DSGVO) +// ============================================================================= + +import type { + TOMGeneratorState, + DerivedTOM, + RiskProfile, + DataProfile, + ControlCategory, + ImplementationStatus, +} from './tom-generator/types' + +import { getControlById, getControlsByCategory, getAllCategories } from './tom-generator/controls/loader' +import { SDM_CATEGORY_MAPPING } from './tom-generator/types' + +// ============================================================================= +// TYPES +// ============================================================================= + +export type TOMComplianceIssueSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' + +export type TOMComplianceIssueType = + | 'MISSING_RESPONSIBLE' + | 'OVERDUE_REVIEW' + | 'MISSING_EVIDENCE' + | 'INCOMPLETE_CATEGORY' + | 'NO_ENCRYPTION_MEASURES' + | 'NO_PSEUDONYMIZATION' + | 'MISSING_AVAILABILITY' + | 'NO_REVIEW_PROCESS' + | 'UNCOVERED_SDM_GOAL' + | 'HIGH_RISK_WITHOUT_MEASURES' + | 'STALE_NOT_IMPLEMENTED' + +export interface TOMComplianceIssue { + id: string + controlId: string + controlName: string + type: TOMComplianceIssueType + severity: TOMComplianceIssueSeverity + title: string + description: string + recommendation: string +} + +export interface TOMComplianceCheckResult { + issues: TOMComplianceIssue[] + score: number // 0-100 + stats: { + total: number + passed: number + failed: number + bySeverity: Record + } +} + +// ============================================================================= +// CONSTANTS +// ============================================================================= + +export const TOM_SEVERITY_LABELS_DE: Record = { + CRITICAL: 'Kritisch', + HIGH: 'Hoch', + MEDIUM: 'Mittel', + LOW: 'Niedrig', +} + +export const TOM_SEVERITY_COLORS: Record = { + CRITICAL: '#dc2626', + HIGH: '#ea580c', + MEDIUM: '#d97706', + LOW: '#6b7280', +} + +// ============================================================================= +// HELPERS +// ============================================================================= + +let issueCounter = 0 + +function createIssueId(): string { + issueCounter++ + return `TCI-${Date.now()}-${String(issueCounter).padStart(4, '0')}` +} + +function createIssue( + controlId: string, + controlName: string, + type: TOMComplianceIssueType, + severity: TOMComplianceIssueSeverity, + title: string, + description: string, + recommendation: string +): TOMComplianceIssue { + return { id: createIssueId(), controlId, controlName, type, severity, title, description, recommendation } +} + +function daysBetween(date: Date, now: Date): number { + const diffMs = now.getTime() - date.getTime() + return Math.floor(diffMs / (1000 * 60 * 60 * 24)) +} + +// ============================================================================= +// PER-TOM CHECKS (1-3, 11) +// ============================================================================= + +/** + * Check 1: MISSING_RESPONSIBLE (MEDIUM) + * REQUIRED TOM without responsiblePerson AND responsibleDepartment. + */ +function checkMissingResponsible(tom: DerivedTOM): TOMComplianceIssue | null { + if (tom.applicability !== 'REQUIRED') return null + + if (!tom.responsiblePerson && !tom.responsibleDepartment) { + return createIssue( + tom.controlId, + tom.name, + 'MISSING_RESPONSIBLE', + 'MEDIUM', + 'Keine verantwortliche Person/Abteilung', + `Die TOM "${tom.name}" ist als REQUIRED eingestuft, hat aber weder eine verantwortliche Person noch eine verantwortliche Abteilung zugewiesen. Ohne klare Verantwortlichkeit kann die Massnahme nicht zuverlaessig umgesetzt und gepflegt werden.`, + 'Weisen Sie eine verantwortliche Person oder Abteilung zu, die fuer die Umsetzung und regelmaessige Pruefung dieser Massnahme zustaendig ist.' + ) + } + + return null +} + +/** + * Check 2: OVERDUE_REVIEW (MEDIUM) + * TOM with reviewDate in the past. + */ +function checkOverdueReview(tom: DerivedTOM): TOMComplianceIssue | null { + if (!tom.reviewDate) return null + + const reviewDate = new Date(tom.reviewDate) + const now = new Date() + + if (reviewDate < now) { + const overdueDays = daysBetween(reviewDate, now) + return createIssue( + tom.controlId, + tom.name, + 'OVERDUE_REVIEW', + 'MEDIUM', + 'Ueberfaellige Pruefung', + `Die TOM "${tom.name}" haette am ${reviewDate.toLocaleDateString('de-DE')} geprueft werden muessen. Die Pruefung ist ${overdueDays} Tag(e) ueberfaellig. Gemaess Art. 32 Abs. 1 lit. d DSGVO ist eine regelmaessige Ueberpruefung der Wirksamkeit von TOMs erforderlich.`, + 'Fuehren Sie umgehend eine Wirksamkeitspruefung dieser Massnahme durch und aktualisieren Sie das naechste Pruefungsdatum.' + ) + } + + return null +} + +/** + * Check 3: MISSING_EVIDENCE (HIGH) + * IMPLEMENTED TOM where linkedEvidence is empty but the control has evidenceRequirements. + */ +function checkMissingEvidence(tom: DerivedTOM): TOMComplianceIssue | null { + if (tom.implementationStatus !== 'IMPLEMENTED') return null + if (tom.linkedEvidence.length > 0) return null + + const control = getControlById(tom.controlId) + if (!control || control.evidenceRequirements.length === 0) return null + + return createIssue( + tom.controlId, + tom.name, + 'MISSING_EVIDENCE', + 'HIGH', + 'Kein Nachweis hinterlegt', + `Die TOM "${tom.name}" ist als IMPLEMENTED markiert, hat aber keine verknuepften Nachweisdokumente. Der Control erfordert ${control.evidenceRequirements.length} Nachweis(e): ${control.evidenceRequirements.join(', ')}. Ohne Nachweise ist die Umsetzung nicht auditfaehig.`, + 'Laden Sie die erforderlichen Nachweisdokumente hoch und verknuepfen Sie sie mit dieser Massnahme.' + ) +} + +/** + * Check 11: STALE_NOT_IMPLEMENTED (LOW) + * REQUIRED TOM that has been NOT_IMPLEMENTED for >90 days. + * Uses implementationDate === null and state.createdAt / state.updatedAt as reference. + */ +function checkStaleNotImplemented(tom: DerivedTOM, state: TOMGeneratorState): TOMComplianceIssue | null { + if (tom.applicability !== 'REQUIRED') return null + if (tom.implementationStatus !== 'NOT_IMPLEMENTED') return null + if (tom.implementationDate !== null) return null + + const referenceDate = state.createdAt ? new Date(state.createdAt) : (state.updatedAt ? new Date(state.updatedAt) : null) + if (!referenceDate) return null + + const ageInDays = daysBetween(referenceDate, new Date()) + if (ageInDays <= 90) return null + + return createIssue( + tom.controlId, + tom.name, + 'STALE_NOT_IMPLEMENTED', + 'LOW', + 'Langfristig nicht umgesetzte Pflichtmassnahme', + `Die TOM "${tom.name}" ist als REQUIRED eingestuft, aber seit ${ageInDays} Tagen nicht umgesetzt. Pflichtmassnahmen, die laenger als 90 Tage nicht implementiert werden, deuten auf organisatorische Blockaden oder unzureichende Priorisierung hin.`, + 'Pruefen Sie, ob die Massnahme weiterhin erforderlich ist, und erstellen Sie einen konkreten Umsetzungsplan mit Verantwortlichkeiten und Fristen.' + ) +} + +// ============================================================================= +// AGGREGATE CHECKS (4-10) +// ============================================================================= + +/** + * Check 4: INCOMPLETE_CATEGORY (HIGH) + * Category where ALL applicable (REQUIRED) controls are NOT_IMPLEMENTED. + */ +function checkIncompleteCategory(toms: DerivedTOM[]): TOMComplianceIssue[] { + const issues: TOMComplianceIssue[] = [] + + // Group applicable TOMs by category + const categoryMap = new Map() + + for (const tom of toms) { + const control = getControlById(tom.controlId) + if (!control) continue + + const category = control.category + if (!categoryMap.has(category)) { + categoryMap.set(category, []) + } + categoryMap.get(category)!.push(tom) + } + + for (const [category, categoryToms] of Array.from(categoryMap.entries())) { + // Only check categories that have at least one REQUIRED control + const requiredToms = categoryToms.filter((t: DerivedTOM) => t.applicability === 'REQUIRED') + if (requiredToms.length === 0) continue + + const allNotImplemented = requiredToms.every((t: DerivedTOM) => t.implementationStatus === 'NOT_IMPLEMENTED') + if (allNotImplemented) { + issues.push( + createIssue( + category, + category, + 'INCOMPLETE_CATEGORY', + 'HIGH', + `Kategorie "${category}" vollstaendig ohne Umsetzung`, + `Alle ${requiredToms.length} Pflichtmassnahme(n) in der Kategorie "${category}" sind nicht umgesetzt. Eine vollstaendig unabgedeckte Kategorie stellt eine erhebliche Luecke im TOM-Konzept dar.`, + `Setzen Sie mindestens die wichtigsten Massnahmen in der Kategorie "${category}" um, um eine Grundabdeckung sicherzustellen.` + ) + ) + } + } + + return issues +} + +/** + * Check 5: NO_ENCRYPTION_MEASURES (CRITICAL) + * No ENCRYPTION control with status IMPLEMENTED. + */ +function checkNoEncryption(toms: DerivedTOM[]): TOMComplianceIssue | null { + const hasImplementedEncryption = toms.some((tom) => { + const control = getControlById(tom.controlId) + return control?.category === 'ENCRYPTION' && tom.implementationStatus === 'IMPLEMENTED' + }) + + if (!hasImplementedEncryption) { + return createIssue( + 'ENCRYPTION', + 'Verschluesselung', + 'NO_ENCRYPTION_MEASURES', + 'CRITICAL', + 'Keine Verschluesselungsmassnahmen umgesetzt', + 'Es ist keine einzige Verschluesselungsmassnahme als IMPLEMENTED markiert. Art. 32 Abs. 1 lit. a DSGVO nennt Verschluesselung explizit als geeignete technische Massnahme. Ohne Verschluesselung sind personenbezogene Daten bei Zugriff oder Verlust ungeschuetzt.', + 'Implementieren Sie umgehend Verschluesselungsmassnahmen fuer Daten im Ruhezustand (Encryption at Rest) und waehrend der Uebertragung (Encryption in Transit).' + ) + } + + return null +} + +/** + * Check 6: NO_PSEUDONYMIZATION (MEDIUM) + * DataProfile has special categories (Art. 9) but no PSEUDONYMIZATION control implemented. + */ +function checkNoPseudonymization(toms: DerivedTOM[], dataProfile: DataProfile | null): TOMComplianceIssue | null { + if (!dataProfile || !dataProfile.hasSpecialCategories) return null + + const hasImplementedPseudonymization = toms.some((tom) => { + const control = getControlById(tom.controlId) + return control?.category === 'PSEUDONYMIZATION' && tom.implementationStatus === 'IMPLEMENTED' + }) + + if (!hasImplementedPseudonymization) { + return createIssue( + 'PSEUDONYMIZATION', + 'Pseudonymisierung', + 'NO_PSEUDONYMIZATION', + 'MEDIUM', + 'Keine Pseudonymisierung bei besonderen Datenkategorien', + 'Das Datenprofil enthaelt besondere Kategorien personenbezogener Daten (Art. 9 DSGVO), aber keine Pseudonymisierungsmassnahme ist umgesetzt. Art. 32 Abs. 1 lit. a DSGVO empfiehlt Pseudonymisierung ausdruecklich als Schutzmassnahme.', + 'Implementieren Sie Pseudonymisierungsmassnahmen fuer die Verarbeitung besonderer Datenkategorien, um das Risiko fuer betroffene Personen zu minimieren.' + ) + } + + return null +} + +/** + * Check 7: MISSING_AVAILABILITY (HIGH) + * No AVAILABILITY or RECOVERY control implemented AND no DR plan in securityProfile. + */ +function checkMissingAvailability(toms: DerivedTOM[], state: TOMGeneratorState): TOMComplianceIssue | null { + const hasAvailabilityOrRecovery = toms.some((tom) => { + const control = getControlById(tom.controlId) + return ( + (control?.category === 'AVAILABILITY' || control?.category === 'RECOVERY') && + tom.implementationStatus === 'IMPLEMENTED' + ) + }) + + const hasDRPlan = state.securityProfile?.hasDRPlan ?? false + + if (!hasAvailabilityOrRecovery && !hasDRPlan) { + return createIssue( + 'AVAILABILITY', + 'Verfuegbarkeit / Wiederherstellbarkeit', + 'MISSING_AVAILABILITY', + 'HIGH', + 'Keine Verfuegbarkeits- oder Wiederherstellungsmassnahmen', + 'Weder Verfuegbarkeits- noch Wiederherstellungsmassnahmen sind umgesetzt, und es existiert kein Disaster-Recovery-Plan im Security-Profil. Art. 32 Abs. 1 lit. b und c DSGVO verlangen die Faehigkeit zur raschen Wiederherstellung der Verfuegbarkeit personenbezogener Daten.', + 'Implementieren Sie Backup-Konzepte, Redundanzloesungen und einen Disaster-Recovery-Plan, um die Verfuegbarkeit und Wiederherstellbarkeit sicherzustellen.' + ) + } + + return null +} + +/** + * Check 8: NO_REVIEW_PROCESS (MEDIUM) + * No REVIEW control implemented (Art. 32 Abs. 1 lit. d requires periodic review). + */ +function checkNoReviewProcess(toms: DerivedTOM[]): TOMComplianceIssue | null { + const hasImplementedReview = toms.some((tom) => { + const control = getControlById(tom.controlId) + return control?.category === 'REVIEW' && tom.implementationStatus === 'IMPLEMENTED' + }) + + if (!hasImplementedReview) { + return createIssue( + 'REVIEW', + 'Ueberpruefung & Bewertung', + 'NO_REVIEW_PROCESS', + 'MEDIUM', + 'Kein Verfahren zur regelmaessigen Ueberpruefung', + 'Es ist keine Ueberpruefungsmassnahme als IMPLEMENTED markiert. Art. 32 Abs. 1 lit. d DSGVO verlangt ein Verfahren zur regelmaessigen Ueberpruefung, Bewertung und Evaluierung der Wirksamkeit der technischen und organisatorischen Massnahmen.', + 'Implementieren Sie einen regelmaessigen Review-Prozess (z.B. quartalsweise TOM-Audits, jaehrliche Wirksamkeitspruefung) und dokumentieren Sie die Ergebnisse.' + ) + } + + return null +} + +/** + * Check 9: UNCOVERED_SDM_GOAL (HIGH) + * SDM goal with 0% coverage — no implemented control maps to it via SDM_CATEGORY_MAPPING. + */ +function checkUncoveredSDMGoal(toms: DerivedTOM[]): TOMComplianceIssue[] { + const issues: TOMComplianceIssue[] = [] + + // Build reverse mapping: SDM goal -> ControlCategories that cover it + const sdmGoals = [ + 'Verfuegbarkeit', + 'Integritaet', + 'Vertraulichkeit', + 'Nichtverkettung', + 'Intervenierbarkeit', + 'Transparenz', + 'Datenminimierung', + ] as const + + const goalToCategoriesMap = new Map() + for (const goal of sdmGoals) { + goalToCategoriesMap.set(goal, []) + } + + // Build reverse lookup from SDM_CATEGORY_MAPPING + for (const [category, goals] of Object.entries(SDM_CATEGORY_MAPPING)) { + for (const goal of goals) { + const existing = goalToCategoriesMap.get(goal) + if (existing) { + existing.push(category as ControlCategory) + } + } + } + + // Collect implemented categories + const implementedCategories = new Set() + for (const tom of toms) { + if (tom.implementationStatus !== 'IMPLEMENTED') continue + const control = getControlById(tom.controlId) + if (control) { + implementedCategories.add(control.category) + } + } + + // Check each SDM goal + for (const goal of sdmGoals) { + const coveringCategories = goalToCategoriesMap.get(goal) ?? [] + const hasCoverage = coveringCategories.some((cat) => implementedCategories.has(cat)) + + if (!hasCoverage) { + issues.push( + createIssue( + `SDM-${goal}`, + goal, + 'UNCOVERED_SDM_GOAL', + 'HIGH', + `SDM-Gewaehrleistungsziel "${goal}" nicht abgedeckt`, + `Das Gewaehrleistungsziel "${goal}" des Standard-Datenschutzmodells (SDM) ist durch keine umgesetzte Massnahme abgedeckt. Zugehoerige Kategorien (${coveringCategories.join(', ')}) haben keine IMPLEMENTED Controls. Das SDM ist die anerkannte Methodik zur Umsetzung der DSGVO-Anforderungen.`, + `Setzen Sie mindestens eine Massnahme aus den Kategorien ${coveringCategories.join(', ')} um, um das SDM-Ziel "${goal}" abzudecken.` + ) + ) + } + } + + return issues +} + +/** + * Check 10: HIGH_RISK_WITHOUT_MEASURES (CRITICAL) + * Protection level VERY_HIGH but < 50% of REQUIRED controls implemented. + */ +function checkHighRiskWithoutMeasures(toms: DerivedTOM[], riskProfile: RiskProfile | null): TOMComplianceIssue | null { + if (!riskProfile || riskProfile.protectionLevel !== 'VERY_HIGH') return null + + const requiredToms = toms.filter((t) => t.applicability === 'REQUIRED') + if (requiredToms.length === 0) return null + + const implementedCount = requiredToms.filter((t) => t.implementationStatus === 'IMPLEMENTED').length + const implementationRate = implementedCount / requiredToms.length + + if (implementationRate < 0.5) { + const percentage = Math.round(implementationRate * 100) + return createIssue( + 'RISK-PROFILE', + 'Risikoprofil VERY_HIGH', + 'HIGH_RISK_WITHOUT_MEASURES', + 'CRITICAL', + 'Sehr hoher Schutzbedarf bei niedriger Umsetzungsrate', + `Der Schutzbedarf ist als VERY_HIGH eingestuft, aber nur ${implementedCount} von ${requiredToms.length} Pflichtmassnahmen (${percentage}%) sind umgesetzt. Bei sehr hohem Schutzbedarf muessen mindestens 50% der Pflichtmassnahmen implementiert sein, um ein angemessenes Schutzniveau gemaess Art. 32 DSGVO zu gewaehrleisten.`, + 'Priorisieren Sie die Umsetzung der verbleibenden Pflichtmassnahmen. Beginnen Sie mit CRITICAL- und HIGH-Priority Controls. Erwaeegen Sie einen Umsetzungsplan mit klaren Meilensteinen.' + ) + } + + return null +} + +// ============================================================================= +// MAIN COMPLIANCE CHECK +// ============================================================================= + +/** + * Fuehrt einen vollstaendigen Compliance-Check ueber alle TOMs durch. + * + * @param state - Der vollstaendige TOMGeneratorState + * @returns TOMComplianceCheckResult mit Issues, Score und Statistiken + */ +export function runTOMComplianceCheck(state: TOMGeneratorState): TOMComplianceCheckResult { + // Reset counter for deterministic IDs within a single check run + issueCounter = 0 + + const issues: TOMComplianceIssue[] = [] + + // Filter to applicable TOMs only (REQUIRED or RECOMMENDED, exclude NOT_APPLICABLE) + const applicableTOMs = state.derivedTOMs.filter( + (tom) => tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED' + ) + + // Run per-TOM checks (1-3, 11) on each applicable TOM + for (const tom of applicableTOMs) { + const perTomChecks = [ + checkMissingResponsible(tom), + checkOverdueReview(tom), + checkMissingEvidence(tom), + checkStaleNotImplemented(tom, state), + ] + + for (const issue of perTomChecks) { + if (issue !== null) { + issues.push(issue) + } + } + } + + // Run aggregate checks (4-10) + issues.push(...checkIncompleteCategory(applicableTOMs)) + + const aggregateChecks = [ + checkNoEncryption(applicableTOMs), + checkNoPseudonymization(applicableTOMs, state.dataProfile), + checkMissingAvailability(applicableTOMs, state), + checkNoReviewProcess(applicableTOMs), + checkHighRiskWithoutMeasures(applicableTOMs, state.riskProfile), + ] + + for (const issue of aggregateChecks) { + if (issue !== null) { + issues.push(issue) + } + } + + issues.push(...checkUncoveredSDMGoal(applicableTOMs)) + + // Calculate score + const bySeverity: Record = { + LOW: 0, + MEDIUM: 0, + HIGH: 0, + CRITICAL: 0, + } + + for (const issue of issues) { + bySeverity[issue.severity]++ + } + + const rawScore = + 100 - + (bySeverity.CRITICAL * 15 + + bySeverity.HIGH * 10 + + bySeverity.MEDIUM * 5 + + bySeverity.LOW * 2) + + const score = Math.max(0, rawScore) + + // Calculate pass/fail per TOM + const failedControlIds = new Set( + issues.filter((i) => !i.controlId.startsWith('SDM-') && i.controlId !== 'RISK-PROFILE').map((i) => i.controlId) + ) + const totalTOMs = applicableTOMs.length + const failedCount = failedControlIds.size + const passedCount = Math.max(0, totalTOMs - failedCount) + + return { + issues, + score, + stats: { + total: totalTOMs, + passed: passedCount, + failed: failedCount, + bySeverity, + }, + } +} diff --git a/admin-compliance/lib/sdk/tom-document.ts b/admin-compliance/lib/sdk/tom-document.ts new file mode 100644 index 0000000..834d86a --- /dev/null +++ b/admin-compliance/lib/sdk/tom-document.ts @@ -0,0 +1,906 @@ +// ============================================================================= +// TOM Module - TOM-Dokumentation Document Generator +// Generates a printable, audit-ready HTML document according to DSGVO Art. 32 +// ============================================================================= + +import type { + TOMGeneratorState, + DerivedTOM, + CompanyProfile, + RiskProfile, + ControlCategory, +} from './tom-generator/types' + +import { SDM_CATEGORY_MAPPING } from './tom-generator/types' + +import { + getControlById, + getControlsByCategory, + getAllCategories, + getCategoryMetadata, +} from './tom-generator/controls/loader' + +import type { TOMComplianceCheckResult, TOMComplianceIssueSeverity } from './tom-compliance' + +// ============================================================================= +// TYPES +// ============================================================================= + +export interface TOMDocumentOrgHeader { + organizationName: string + industry: string + dpoName: string + dpoContact: string + responsiblePerson: string + itSecurityContact: string + locations: string[] + employeeCount: string + documentVersion: string + lastReviewDate: string + nextReviewDate: string + reviewInterval: string +} + +export interface TOMDocumentRevision { + version: string + date: string + author: string + changes: string +} + +// ============================================================================= +// DEFAULTS +// ============================================================================= + +export function createDefaultTOMDocumentOrgHeader(): TOMDocumentOrgHeader { + const now = new Date() + const nextYear = new Date() + nextYear.setFullYear(nextYear.getFullYear() + 1) + + return { + organizationName: '', + industry: '', + dpoName: '', + dpoContact: '', + responsiblePerson: '', + itSecurityContact: '', + locations: [], + employeeCount: '', + documentVersion: '1.0', + lastReviewDate: now.toISOString().split('T')[0], + nextReviewDate: nextYear.toISOString().split('T')[0], + reviewInterval: 'Jaehrlich', + } +} + +// ============================================================================= +// SEVERITY LABELS (for Compliance Status section) +// ============================================================================= + +const SEVERITY_LABELS_DE: Record = { + CRITICAL: 'Kritisch', + HIGH: 'Hoch', + MEDIUM: 'Mittel', + LOW: 'Niedrig', +} + +const SEVERITY_COLORS: Record = { + CRITICAL: '#dc2626', + HIGH: '#ea580c', + MEDIUM: '#d97706', + LOW: '#6b7280', +} + +// ============================================================================= +// CATEGORY LABELS (German) +// ============================================================================= + +const CATEGORY_LABELS_DE: Record = { + ACCESS_CONTROL: 'Zutrittskontrolle', + ADMISSION_CONTROL: 'Zugangskontrolle', + ACCESS_AUTHORIZATION: 'Zugriffskontrolle', + TRANSFER_CONTROL: 'Weitergabekontrolle', + INPUT_CONTROL: 'Eingabekontrolle', + ORDER_CONTROL: 'Auftragskontrolle', + AVAILABILITY: 'Verfuegbarkeit', + SEPARATION: 'Trennbarkeit', + ENCRYPTION: 'Verschluesselung', + PSEUDONYMIZATION: 'Pseudonymisierung', + RESILIENCE: 'Belastbarkeit', + RECOVERY: 'Wiederherstellbarkeit', + REVIEW: 'Ueberpruefung & Bewertung', +} + +// ============================================================================= +// STATUS & APPLICABILITY LABELS +// ============================================================================= + +const STATUS_LABELS_DE: Record = { + IMPLEMENTED: 'Umgesetzt', + PARTIAL: 'Teilweise umgesetzt', + NOT_IMPLEMENTED: 'Nicht umgesetzt', +} + +const STATUS_BADGE_CLASSES: Record = { + IMPLEMENTED: 'badge-active', + PARTIAL: 'badge-review', + NOT_IMPLEMENTED: 'badge-critical', +} + +const APPLICABILITY_LABELS_DE: Record = { + REQUIRED: 'Erforderlich', + RECOMMENDED: 'Empfohlen', + OPTIONAL: 'Optional', + NOT_APPLICABLE: 'Nicht anwendbar', +} + +// ============================================================================= +// HTML DOCUMENT BUILDER +// ============================================================================= + +export function buildTOMDocumentHtml( + derivedTOMs: DerivedTOM[], + orgHeader: TOMDocumentOrgHeader, + companyProfile: CompanyProfile | null, + riskProfile: RiskProfile | null, + complianceResult: TOMComplianceCheckResult | null, + revisions: TOMDocumentRevision[] +): string { + const today = new Date().toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }) + + const orgName = orgHeader.organizationName || 'Organisation' + + // Filter out NOT_APPLICABLE TOMs for display + const applicableTOMs = derivedTOMs.filter(t => t.applicability !== 'NOT_APPLICABLE') + + // Group TOMs by category via control library lookup + const tomsByCategory = new Map() + for (const tom of applicableTOMs) { + const control = getControlById(tom.controlId) + const cat = control?.category || 'REVIEW' + if (!tomsByCategory.has(cat)) tomsByCategory.set(cat, []) + tomsByCategory.get(cat)!.push(tom) + } + + // Build role map: role/department → list of control codes + const roleMap = new Map() + for (const tom of applicableTOMs) { + const role = tom.responsiblePerson || tom.responsibleDepartment || 'Nicht zugewiesen' + if (!roleMap.has(role)) roleMap.set(role, []) + const control = getControlById(tom.controlId) + roleMap.get(role)!.push(control?.code || tom.controlId) + } + + // ========================================================================= + // HTML Template + // ========================================================================= + + let html = ` + + + +TOM-Dokumentation — ${escHtml(orgName)} + + + +` + + // ========================================================================= + // Section 0: Cover Page + // ========================================================================= + html += ` +
+

TOM-Dokumentation

+
Technische und Organisatorische Massnahmen gemaess Art. 32 DSGVO
+
+
Organisation: ${escHtml(orgName)}
+ ${orgHeader.industry ? `
Branche: ${escHtml(orgHeader.industry)}
` : ''} + ${orgHeader.dpoName ? `
DSB: ${escHtml(orgHeader.dpoName)}
` : ''} + ${orgHeader.dpoContact ? `
DSB-Kontakt: ${escHtml(orgHeader.dpoContact)}
` : ''} + ${orgHeader.responsiblePerson ? `
Verantwortlicher: ${escHtml(orgHeader.responsiblePerson)}
` : ''} + ${orgHeader.itSecurityContact ? `
IT-Sicherheit: ${escHtml(orgHeader.itSecurityContact)}
` : ''} + ${orgHeader.employeeCount ? `
Mitarbeiter: ${escHtml(orgHeader.employeeCount)}
` : ''} + ${orgHeader.locations.length > 0 ? `
Standorte: ${escHtml(orgHeader.locations.join(', '))}
` : ''} +
+ +
+` + + // ========================================================================= + // Table of Contents + // ========================================================================= + const sections = [ + 'Ziel und Zweck', + 'Geltungsbereich', + 'Grundprinzipien Art. 32', + 'Schutzbedarf und Risikoanalyse', + 'Massnahmen-Uebersicht', + 'Detaillierte Massnahmen', + 'SDM Gewaehrleistungsziele', + 'Verantwortlichkeiten', + 'Pruef- und Revisionszyklus', + 'Compliance-Status', + 'Aenderungshistorie', + ] + + html += ` +
+

Inhaltsverzeichnis

+ ${sections.map((s, i) => `
${i + 1}. ${escHtml(s)}
`).join('\n ')} +
+` + + // ========================================================================= + // Section 1: Ziel und Zweck + // ========================================================================= + html += ` +
+
1. Ziel und Zweck
+
+

Diese TOM-Dokumentation beschreibt die technischen und organisatorischen Massnahmen + zum Schutz personenbezogener Daten bei ${escHtml(orgName)}. Sie dient + der Umsetzung folgender DSGVO-Anforderungen:

+ + + + + + +
RechtsgrundlageInhalt
Art. 32 Abs. 1 lit. a DSGVOPseudonymisierung und Verschluesselung personenbezogener Daten
Art. 32 Abs. 1 lit. b DSGVOFaehigkeit, die Vertraulichkeit, Integritaet, Verfuegbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherzustellen
Art. 32 Abs. 1 lit. c DSGVOFaehigkeit, die Verfuegbarkeit der personenbezogenen Daten und den Zugang zu ihnen bei einem physischen oder technischen Zwischenfall rasch wiederherzustellen
Art. 32 Abs. 1 lit. d DSGVOVerfahren zur regelmaessigen Ueberpruefung, Bewertung und Evaluierung der Wirksamkeit der technischen und organisatorischen Massnahmen
+

Die TOM-Dokumentation ist fester Bestandteil des Datenschutz-Managementsystems und wird + regelmaessig ueberprueft und aktualisiert.

+
+
+` + + // ========================================================================= + // Section 2: Geltungsbereich + // ========================================================================= + const industryInfo = companyProfile?.industry || orgHeader.industry || '' + const hostingInfo = companyProfile ? `Unternehmen: ${escHtml(companyProfile.name || orgName)}, Groesse: ${escHtml(companyProfile.size || '-')}` : '' + + html += ` +
+
2. Geltungsbereich
+
+

Diese TOM-Dokumentation gilt fuer alle IT-Systeme, Anwendungen und Verarbeitungsprozesse + von ${escHtml(orgName)}${industryInfo ? ` (Branche: ${escHtml(industryInfo)})` : ''}.

+ ${hostingInfo ? `

${hostingInfo}

` : ''} + ${orgHeader.locations.length > 0 ? `

Standorte: ${escHtml(orgHeader.locations.join(', '))}

` : ''} +

Die dokumentierten Massnahmen stammen aus zwei Quellen:

+
    +
  • Embedded Library (TOM-xxx): Integrierte Kontrollbibliothek mit spezifischen Massnahmen fuer Art. 32 DSGVO
  • +
  • Canonical Control Library (CP-CLIB): Uebergreifende Kontrollbibliothek mit framework-uebergreifenden Massnahmen
  • +
+

Insgesamt umfasst dieses Dokument ${applicableTOMs.length} anwendbare Massnahmen + in ${tomsByCategory.size} Kategorien.

+
+
+` + + // ========================================================================= + // Section 3: Grundprinzipien Art. 32 + // ========================================================================= + html += ` +
+
3. Grundprinzipien Art. 32
+
+
Vertraulichkeit: Schutz personenbezogener Daten vor unbefugter Kenntnisnahme durch Zutrittskontrolle, Zugangskontrolle, Zugriffskontrolle und Verschluesselung (Art. 32 Abs. 1 lit. b DSGVO).
+
Integritaet: Sicherstellung, dass personenbezogene Daten nicht unbefugt oder unbeabsichtigt veraendert werden koennen, durch Eingabekontrolle, Weitergabekontrolle und Protokollierung (Art. 32 Abs. 1 lit. b DSGVO).
+
Verfuegbarkeit und Belastbarkeit: Gewaehrleistung, dass Systeme und Dienste bei Lastspitzen und Stoerungen zuverlaessig funktionieren, durch Backup, Redundanz und Disaster Recovery (Art. 32 Abs. 1 lit. b DSGVO).
+
Rasche Wiederherstellbarkeit: Faehigkeit, nach einem physischen oder technischen Zwischenfall Daten und Systeme schnell wiederherzustellen, durch getestete Recovery-Prozesse (Art. 32 Abs. 1 lit. c DSGVO).
+
Regelmaessige Wirksamkeitspruefung: Verfahren zur regelmaessigen Ueberpruefung, Bewertung und Evaluierung der Wirksamkeit aller technischen und organisatorischen Massnahmen (Art. 32 Abs. 1 lit. d DSGVO).
+
+
+` + + // ========================================================================= + // Section 4: Schutzbedarf und Risikoanalyse + // ========================================================================= + html += ` +
+
4. Schutzbedarf und Risikoanalyse
+
+` + if (riskProfile) { + html += `

Die folgende Schutzbedarfsanalyse bildet die Grundlage fuer die Auswahl und Priorisierung + der technischen und organisatorischen Massnahmen:

+ + + + + + + + ${riskProfile.specialRisks.length > 0 ? `` : ''} + ${riskProfile.regulatoryRequirements.length > 0 ? `` : ''} +
KriteriumBewertung
Vertraulichkeit${riskProfile.ciaAssessment.confidentiality}/5
Integritaet${riskProfile.ciaAssessment.integrity}/5
Verfuegbarkeit${riskProfile.ciaAssessment.availability}/5
Schutzniveau${escHtml(riskProfile.protectionLevel)}
DSFA-Pflicht${riskProfile.dsfaRequired ? 'Ja' : 'Nein'}
Spezialrisiken${escHtml(riskProfile.specialRisks.join(', '))}
Regulatorische Anforderungen${escHtml(riskProfile.regulatoryRequirements.join(', '))}
+` + } else { + html += `

Die Schutzbedarfsanalyse wurde noch nicht durchgefuehrt. Fuehren Sie den + Risiko-Wizard im TOM-Generator durch, um den Schutzbedarf zu ermitteln.

+` + } + + html += `
+
+` + + // ========================================================================= + // Section 5: Massnahmen-Uebersicht + // ========================================================================= + html += ` +
+
5. Massnahmen-Uebersicht
+
+

Die folgende Tabelle zeigt eine Uebersicht aller ${applicableTOMs.length} anwendbaren Massnahmen + nach Kategorie:

+ + + + + + + + +` + const allCategories = getAllCategories() + for (const cat of allCategories) { + const tomsInCat = tomsByCategory.get(cat) + if (!tomsInCat || tomsInCat.length === 0) continue + + const implemented = tomsInCat.filter(t => t.implementationStatus === 'IMPLEMENTED').length + const partial = tomsInCat.filter(t => t.implementationStatus === 'PARTIAL').length + const notImpl = tomsInCat.filter(t => t.implementationStatus === 'NOT_IMPLEMENTED').length + const catLabel = CATEGORY_LABELS_DE[cat] || cat + + html += ` + + + + + + +` + } + + html += `
KategorieGesamtUmgesetztTeilweiseOffen
${escHtml(catLabel)}${tomsInCat.length}${implemented}${partial}${notImpl}
+
+
+` + + // ========================================================================= + // Section 6: Detaillierte Massnahmen + // ========================================================================= + html += ` +
+
6. Detaillierte Massnahmen
+
+` + + for (const cat of allCategories) { + const tomsInCat = tomsByCategory.get(cat) + if (!tomsInCat || tomsInCat.length === 0) continue + + const catLabel = CATEGORY_LABELS_DE[cat] || cat + const catMeta = getCategoryMetadata(cat) + const gdprRef = catMeta?.gdprReference || '' + + html += `

${escHtml(catLabel)}${gdprRef ? ` (${escHtml(gdprRef)})` : ''}

+` + + // Sort TOMs by control code + const sortedTOMs = [...tomsInCat].sort((a, b) => { + const codeA = getControlById(a.controlId)?.code || a.controlId + const codeB = getControlById(b.controlId)?.code || b.controlId + return codeA.localeCompare(codeB) + }) + + for (const tom of sortedTOMs) { + const control = getControlById(tom.controlId) + const code = control?.code || tom.controlId + const nameDE = control?.name?.de || tom.name + const descDE = control?.description?.de || tom.description + const typeLabel = control?.type === 'TECHNICAL' ? 'Technisch' : control?.type === 'ORGANIZATIONAL' ? 'Organisatorisch' : '-' + const statusLabel = STATUS_LABELS_DE[tom.implementationStatus] || tom.implementationStatus + const statusBadge = STATUS_BADGE_CLASSES[tom.implementationStatus] || 'badge-draft' + const applicabilityLabel = APPLICABILITY_LABELS_DE[tom.applicability] || tom.applicability + const responsible = [tom.responsiblePerson, tom.responsibleDepartment].filter(s => s && s.trim()).join(' / ') || '-' + const implDate = tom.implementationDate ? formatDateDE(typeof tom.implementationDate === 'string' ? tom.implementationDate : tom.implementationDate.toISOString()) : '-' + const reviewDate = tom.reviewDate ? formatDateDE(typeof tom.reviewDate === 'string' ? tom.reviewDate : tom.reviewDate.toISOString()) : '-' + + // Evidence + const evidenceInfo = tom.linkedEvidence.length > 0 + ? tom.linkedEvidence.join(', ') + : tom.evidenceGaps.length > 0 + ? `Fehlend: ${escHtml(tom.evidenceGaps.join(', '))}` + : '-' + + // Framework mappings + let mappingsHtml = '-' + if (control?.mappings && control.mappings.length > 0) { + mappingsHtml = control.mappings.map(m => `${escHtml(m.framework)}: ${escHtml(m.reference)}`).join('
') + } + + html += ` +
+
+ ${escHtml(code)} — ${escHtml(nameDE)} + ${escHtml(statusLabel)} +
+
+ + + + + + + + + + +
Beschreibung${escHtml(descDE)}
Massnahmentyp${escHtml(typeLabel)}
Anwendbarkeit${escHtml(applicabilityLabel)}${tom.applicabilityReason ? ` — ${escHtml(tom.applicabilityReason)}` : ''}
Umsetzungsstatus${escHtml(statusLabel)}
Verantwortlich${escHtml(responsible)}
Umsetzungsdatum${implDate}
Naechste Pruefung${reviewDate}
Evidence${evidenceInfo}
Framework-Mappings${mappingsHtml}
+
+
+` + } + } + + html += `
+
+` + + // ========================================================================= + // Section 7: SDM Gewaehrleistungsziele + // ========================================================================= + const sdmGoals: Array<{ goal: string; categories: ControlCategory[] }> = [] + const allSDMGoals = [ + 'Verfuegbarkeit', + 'Integritaet', + 'Vertraulichkeit', + 'Nichtverkettung', + 'Intervenierbarkeit', + 'Transparenz', + 'Datenminimierung', + ] as const + + for (const goal of allSDMGoals) { + const cats: ControlCategory[] = [] + for (const [cat, goals] of Object.entries(SDM_CATEGORY_MAPPING)) { + if (goals.includes(goal)) { + cats.push(cat as ControlCategory) + } + } + sdmGoals.push({ goal, categories: cats }) + } + + html += ` +
+
7. SDM Gewaehrleistungsziele
+
+

Die folgende Tabelle zeigt die Abdeckung der sieben Gewaehrleistungsziele des + Standard-Datenschutzmodells (SDM) durch die implementierten Massnahmen:

+ + + + + + + +` + for (const { goal, categories } of sdmGoals) { + let totalInGoal = 0 + let implementedInGoal = 0 + for (const cat of categories) { + const tomsInCat = tomsByCategory.get(cat) || [] + totalInGoal += tomsInCat.length + implementedInGoal += tomsInCat.filter(t => t.implementationStatus === 'IMPLEMENTED').length + } + const percentage = totalInGoal > 0 ? Math.round((implementedInGoal / totalInGoal) * 100) : 0 + + html += ` + + + + + +` + } + + html += `
GewaehrleistungszielAbgedecktGesamtAbdeckung (%)
${escHtml(goal)}${implementedInGoal}${totalInGoal}${percentage}%
+
+
+` + + // ========================================================================= + // Section 8: Verantwortlichkeiten + // ========================================================================= + html += ` +
+
8. Verantwortlichkeiten
+
+

Die folgende Rollenmatrix zeigt, welche Personen oder Abteilungen fuer welche Massnahmen + die Umsetzungsverantwortung tragen:

+ + +` + for (const [role, controls] of roleMap.entries()) { + html += ` + + + + +` + } + + html += `
Rolle / VerantwortlichMassnahmenAnzahl
${escHtml(role)}${controls.map(c => escHtml(c)).join(', ')}${controls.length}
+
+
+` + + // ========================================================================= + // Section 9: Pruef- und Revisionszyklus + // ========================================================================= + html += ` +
+
9. Pruef- und Revisionszyklus
+
+ + + + + + +
EigenschaftWert
Aktuelles Pruefintervall${escHtml(orgHeader.reviewInterval)}
Letzte Pruefung${formatDateDE(orgHeader.lastReviewDate)}
Naechste Pruefung${formatDateDE(orgHeader.nextReviewDate)}
Aktuelle Version${escHtml(orgHeader.documentVersion)}
+

Bei jeder Pruefung wird die TOM-Dokumentation auf folgende Punkte ueberprueft:

+
    +
  • Vollstaendigkeit aller Massnahmen (neue Systeme oder Verarbeitungen erfasst?)
  • +
  • Aktualitaet des Umsetzungsstatus (Aenderungen seit letzter Pruefung?)
  • +
  • Wirksamkeit der technischen Massnahmen (Penetration-Tests, Audit-Ergebnisse)
  • +
  • Angemessenheit der organisatorischen Massnahmen (Schulungen, Richtlinien aktuell?)
  • +
  • Abdeckung aller SDM-Gewaehrleistungsziele
  • +
  • Zuordnung von Verantwortlichkeiten zu allen Massnahmen
  • +
+
+
+` + + // ========================================================================= + // Section 10: Compliance-Status + // ========================================================================= + html += ` +
+
10. Compliance-Status
+
+` + if (complianceResult) { + const scoreClass = complianceResult.score >= 90 ? 'score-excellent' + : complianceResult.score >= 75 ? 'score-good' + : complianceResult.score >= 50 ? 'score-needs-work' + : 'score-poor' + const scoreLabel = complianceResult.score >= 90 ? 'Ausgezeichnet' + : complianceResult.score >= 75 ? 'Gut' + : complianceResult.score >= 50 ? 'Verbesserungswuerdig' + : 'Mangelhaft' + + html += `

${complianceResult.score}/100 ${escHtml(scoreLabel)}

+ + + + + +
KennzahlWert
Gepruefte Massnahmen${complianceResult.stats.total}
Bestanden${complianceResult.stats.passed}
Beanstandungen${complianceResult.stats.failed}
+` + if (complianceResult.issues.length > 0) { + html += `

Befunde nach Schweregrad:

+ + +` + const severityOrder: TOMComplianceIssueSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] + for (const sev of severityOrder) { + const count = complianceResult.stats.bySeverity[sev] + if (count === 0) continue + const issuesForSev = complianceResult.issues.filter(i => i.severity === sev) + html += ` + + + + +` + } + html += `
SchweregradAnzahlBefunde
${SEVERITY_LABELS_DE[sev]}${count}${issuesForSev.map(i => escHtml(i.title)).join('; ')}
+` + } else { + html += `

Keine Beanstandungen. Alle Massnahmen sind konform.

+` + } + } else { + html += `

Compliance-Check wurde noch nicht ausgefuehrt. Fuehren Sie den Check im + Export-Tab durch, um den Status in das Dokument aufzunehmen.

+` + } + + html += `
+
+` + + // ========================================================================= + // Section 11: Aenderungshistorie + // ========================================================================= + html += ` +
+
11. Aenderungshistorie
+
+ + +` + if (revisions.length > 0) { + for (const rev of revisions) { + html += ` + + + + + +` + } + } else { + html += ` + + + + + +` + } + + html += `
VersionDatumAutorAenderungen
${escHtml(rev.version)}${formatDateDE(rev.date)}${escHtml(rev.author)}${escHtml(rev.changes)}
${escHtml(orgHeader.documentVersion)}${today}${escHtml(orgHeader.dpoName || orgHeader.responsiblePerson || '-')}Erstversion der TOM-Dokumentation
+
+
+` + + // ========================================================================= + // Footer + // ========================================================================= + html += ` + + + +` + + return html +} + +// ============================================================================= +// INTERNAL HELPERS +// ============================================================================= + +function escHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +function formatDateDE(dateStr: string | null | undefined): string { + if (!dateStr) return '-' + try { + const date = new Date(dateStr) + if (isNaN(date.getTime())) return '-' + return date.toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }) + } catch { + return '-' + } +} diff --git a/admin-compliance/lib/sdk/tom-generator/controls/loader.ts b/admin-compliance/lib/sdk/tom-generator/controls/loader.ts index 0236002..211daed 100644 --- a/admin-compliance/lib/sdk/tom-generator/controls/loader.ts +++ b/admin-compliance/lib/sdk/tom-generator/controls/loader.ts @@ -89,9 +89,9 @@ export interface ControlLibrary { const CONTROL_LIBRARY_DATA: ControlLibrary = { metadata: { - version: '1.0.0', - lastUpdated: '2026-02-04', - totalControls: 60, + version: '1.1.0', + lastUpdated: '2026-03-19', + totalControls: 88, }, categories: new Map([ [ @@ -2353,6 +2353,648 @@ const CONTROL_LIBRARY_DATA: ControlLibrary = { complexity: 'MEDIUM', tags: ['training', 'security-awareness', 'phishing', 'social-engineering'], }, + + // ========================================================================= + // NEW CONTROLS (v1.1.0) — 25 additional measures + // ========================================================================= + + // ENCRYPTION — 2 new + { + id: 'TOM-ENC-04', + code: 'TOM-ENC-04', + category: 'ENCRYPTION', + type: 'TECHNICAL', + name: { de: 'Zertifikatsmanagement (TLS/SSL)', en: 'Certificate Management (TLS/SSL)' }, + description: { + de: 'Systematische Verwaltung, Ueberwachung und rechtzeitige Erneuerung aller TLS/SSL-Zertifikate zur Vermeidung von Sicherheitsluecken durch abgelaufene Zertifikate.', + en: 'Systematic management, monitoring and timely renewal of all TLS/SSL certificates to prevent security gaps from expired certificates.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. a' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.10.1.2' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'CON.1' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.encryptionInTransit', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Zertifikatsinventar', 'Monitoring-Konfiguration', 'Erneuerungsprotokolle'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['encryption', 'certificates', 'tls'], + }, + { + id: 'TOM-ENC-05', + code: 'TOM-ENC-05', + category: 'ENCRYPTION', + type: 'ORGANIZATIONAL', + name: { de: 'Schluesselmanagement-Policy', en: 'Key Management Policy' }, + description: { + de: 'Dokumentierte Richtlinie fuer den gesamten Lebenszyklus kryptografischer Schluessel inkl. Erzeugung, Verteilung, Speicherung, Rotation und Vernichtung.', + en: 'Documented policy for the full lifecycle of cryptographic keys including generation, distribution, storage, rotation and destruction.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. a' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.10.1.2' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.encryptionAtRest', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + { field: 'dataProfile.hasSpecialCategories', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 30 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Schluesselmanagement-Richtlinie', 'Schluesselrotationsplan'], + reviewFrequency: 'ANNUAL', + priority: 'HIGH', + complexity: 'LOW', + tags: ['encryption', 'key-management', 'policy'], + }, + + // PSEUDONYMIZATION — 2 new + { + id: 'TOM-PS-03', + code: 'TOM-PS-03', + category: 'PSEUDONYMIZATION', + type: 'TECHNICAL', + name: { de: 'Anonymisierung fuer Analysezwecke', en: 'Anonymization for Analytics' }, + description: { + de: 'Technische Verfahren zur irreversiblen Anonymisierung personenbezogener Daten fuer statistische Auswertungen und Analysen.', + en: 'Technical procedures for irreversible anonymization of personal data for statistical evaluations and analyses.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. a' }, + { framework: 'GDPR_ART25', reference: 'Art. 25 Abs. 1' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.dataVolume', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['Anonymisierungsverfahren-Dokumentation', 'Re-Identifizierungs-Risikoanalyse'], + reviewFrequency: 'ANNUAL', + priority: 'MEDIUM', + complexity: 'HIGH', + tags: ['pseudonymization', 'anonymization', 'analytics'], + }, + { + id: 'TOM-PS-04', + code: 'TOM-PS-04', + category: 'PSEUDONYMIZATION', + type: 'ORGANIZATIONAL', + name: { de: 'Pseudonymisierungskonzept', en: 'Pseudonymization Concept' }, + description: { + de: 'Dokumentiertes Konzept fuer die Pseudonymisierung personenbezogener Daten mit Definition der Verfahren, Zustaendigkeiten und Zuordnungsregeln.', + en: 'Documented concept for pseudonymization of personal data with definition of procedures, responsibilities and mapping rules.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. a' }, + { framework: 'GDPR_ART25', reference: 'Art. 25 Abs. 1' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'CON.2' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.hasSpecialCategories', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Pseudonymisierungskonzept', 'Verfahrensdokumentation'], + reviewFrequency: 'ANNUAL', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['pseudonymization', 'concept', 'documentation'], + }, + + // INPUT_CONTROL — 1 new + { + id: 'TOM-IN-05', + code: 'TOM-IN-05', + category: 'INPUT_CONTROL', + type: 'TECHNICAL', + name: { de: 'Automatisierte Eingabevalidierung', en: 'Automated Input Validation' }, + description: { + de: 'Technische Validierung aller Benutzereingaben zur Verhinderung von Injection-Angriffen und Sicherstellung der Datenintegritaet.', + en: 'Technical validation of all user inputs to prevent injection attacks and ensure data integrity.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.14.2.5' }, + ], + applicabilityConditions: [], + defaultApplicability: 'REQUIRED', + evidenceRequirements: ['Validierungsregeln-Dokumentation', 'Penetrationstest-Berichte'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['input-validation', 'security', 'injection-prevention'], + }, + + // ORDER_CONTROL — 2 new + { + id: 'TOM-OR-05', + code: 'TOM-OR-05', + category: 'ORDER_CONTROL', + type: 'ORGANIZATIONAL', + name: { de: 'Auftragsverarbeiter-Monitoring', en: 'Processor Monitoring' }, + description: { + de: 'Regelmaessige Ueberpruefung und Bewertung der Datenschutz-Massnahmen bei Auftragsverarbeitern gemaess Art. 28 Abs. 3 lit. h DSGVO.', + en: 'Regular review and assessment of data protection measures at processors according to Art. 28(3)(h) GDPR.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 28 Abs. 3 lit. h' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.15.2.1' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.hasSubprocessors', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Audit-Berichte der Auftragsverarbeiter', 'Monitoring-Checklisten'], + reviewFrequency: 'ANNUAL', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['order-control', 'processor', 'monitoring'], + }, + { + id: 'TOM-OR-06', + code: 'TOM-OR-06', + category: 'ORDER_CONTROL', + type: 'ORGANIZATIONAL', + name: { de: 'Sub-Processor Management', en: 'Sub-Processor Management' }, + description: { + de: 'Dokumentiertes Verfahren zur Genehmigung, Ueberwachung und Dokumentation von Unterauftragsverarbeitern.', + en: 'Documented process for approval, monitoring and documentation of sub-processors.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 28 Abs. 2, 4' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.15.1.2' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.hasSubprocessors', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + { field: 'architectureProfile.subprocessorCount', operator: 'GREATER_THAN', value: 3, result: 'REQUIRED', priority: 20 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Sub-Processor-Register', 'Genehmigungsverfahren', 'Vertragsdokumentation'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['order-control', 'sub-processor'], + }, + + // RESILIENCE — 2 new + { + id: 'TOM-RE-04', + code: 'TOM-RE-04', + category: 'RESILIENCE', + type: 'TECHNICAL', + name: { de: 'DDoS-Abwehr (erweitert)', en: 'DDoS Mitigation (Advanced)' }, + description: { + de: 'Erweiterte DDoS-Schutzmassnahmen inkl. Traffic-Analyse, automatischer Mitigation und Incident-Response-Integration.', + en: 'Advanced DDoS protection measures including traffic analysis, automatic mitigation and incident response integration.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.13.1.1' }, + ], + applicabilityConditions: [ + { field: 'riskProfile.protectionLevel', operator: 'EQUALS', value: 'VERY_HIGH', result: 'REQUIRED', priority: 25 }, + { field: 'architectureProfile.hostingModel', operator: 'IN', value: ['PUBLIC_CLOUD', 'HYBRID'], result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['DDoS-Schutzkonzept (erweitert)', 'Mitigation-Berichte', 'Incident-Playbooks'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'HIGH', + tags: ['resilience', 'ddos', 'advanced'], + }, + { + id: 'TOM-RE-05', + code: 'TOM-RE-05', + category: 'RESILIENCE', + type: 'ORGANIZATIONAL', + name: { de: 'Kapazitaetsplanung', en: 'Capacity Planning' }, + description: { + de: 'Systematische Planung und Ueberwachung von IT-Kapazitaeten zur Sicherstellung der Systemverfuegbarkeit bei wachsender Nutzung.', + en: 'Systematic planning and monitoring of IT capacities to ensure system availability with growing usage.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.12.1.3' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.dataVolume', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'REQUIRED', priority: 20 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Kapazitaetsplan', 'Trend-Analysen', 'Skalierungskonzept'], + reviewFrequency: 'QUARTERLY', + priority: 'MEDIUM', + complexity: 'MEDIUM', + tags: ['resilience', 'capacity', 'planning'], + }, + + // RECOVERY — 2 new + { + id: 'TOM-RC-04', + code: 'TOM-RC-04', + category: 'RECOVERY', + type: 'TECHNICAL', + name: { de: 'Georedundantes Backup', en: 'Geo-Redundant Backup' }, + description: { + de: 'Speicherung von Backup-Kopien an geografisch getrennten Standorten zum Schutz vor standortbezogenen Katastrophen.', + en: 'Storage of backup copies at geographically separated locations to protect against site-specific disasters.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. c' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.12.3.1' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'CON.3' }, + ], + applicabilityConditions: [ + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'REQUIRED', priority: 25 }, + { field: 'riskProfile.ciaAssessment.availability', operator: 'GREATER_THAN', value: 3, result: 'REQUIRED', priority: 20 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Georedundanz-Konzept', 'Backup-Standort-Dokumentation', 'Wiederherstellungstests'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'HIGH', + complexity: 'HIGH', + tags: ['recovery', 'backup', 'geo-redundancy'], + }, + { + id: 'TOM-RC-05', + code: 'TOM-RC-05', + category: 'RECOVERY', + type: 'ORGANIZATIONAL', + name: { de: 'Notfallwiederherstellungs-Tests', en: 'Disaster Recovery Testing' }, + description: { + de: 'Regelmaessige Durchfuehrung und Dokumentation von Notfallwiederherstellungstests zur Validierung der RTO/RPO-Ziele.', + en: 'Regular execution and documentation of disaster recovery tests to validate RTO/RPO targets.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. c, d' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.17.1.3' }, + ], + applicabilityConditions: [ + { field: 'securityProfile.hasDRPlan', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['DR-Testberichte', 'RTO/RPO-Messungen', 'Verbesserungsmassnahmen'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['recovery', 'dr-testing', 'rto', 'rpo'], + }, + + // SEPARATION — 2 new + { + id: 'TOM-SE-05', + code: 'TOM-SE-05', + category: 'SEPARATION', + type: 'TECHNICAL', + name: { de: 'Netzwerksegmentierung', en: 'Network Segmentation' }, + description: { + de: 'Aufteilung des Netzwerks in separate Sicherheitszonen mit kontrollierten Uebergaengen zur Begrenzung der Ausbreitung von Sicherheitsvorfaellen.', + en: 'Division of the network into separate security zones with controlled transitions to limit the spread of security incidents.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.13.1.3' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'NET.1.1' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.hostingModel', operator: 'IN', value: ['ON_PREMISE', 'PRIVATE_CLOUD', 'HYBRID'], result: 'REQUIRED', priority: 20 }, + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Netzwerkplan', 'Firewall-Regeln', 'Segmentierungskonzept'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'HIGH', + complexity: 'HIGH', + tags: ['separation', 'network', 'segmentation'], + }, + { + id: 'TOM-SE-06', + code: 'TOM-SE-06', + category: 'SEPARATION', + type: 'TECHNICAL', + name: { de: 'Mandantenisolierung in Cloud', en: 'Tenant Isolation in Cloud' }, + description: { + de: 'Technische Sicherstellung der vollstaendigen Datentrennung zwischen verschiedenen Mandanten in Multi-Tenant-Cloud-Umgebungen.', + en: 'Technical assurance of complete data separation between different tenants in multi-tenant cloud environments.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.13.1.3' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.multiTenancy', operator: 'EQUALS', value: 'MULTI_TENANT', result: 'REQUIRED', priority: 30 }, + { field: 'architectureProfile.hostingModel', operator: 'IN', value: ['PUBLIC_CLOUD', 'HYBRID'], result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['Mandantentrennungskonzept', 'Isolierungstests', 'Cloud-Security-Assessment'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'CRITICAL', + complexity: 'HIGH', + tags: ['separation', 'multi-tenant', 'cloud'], + }, + + // ACCESS_CONTROL — 1 new + { + id: 'TOM-AC-06', + code: 'TOM-AC-06', + category: 'ACCESS_CONTROL', + type: 'ORGANIZATIONAL', + name: { de: 'Besuchermanagement (erweitert)', en: 'Visitor Management (Extended)' }, + description: { + de: 'Erweitertes Besuchermanagement mit Voranmeldung, Identitaetspruefung, Begleitpflicht und zeitlich begrenztem Zugang zu sicherheitsrelevanten Bereichen.', + en: 'Extended visitor management with pre-registration, identity verification, escort requirement and time-limited access to security-relevant areas.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.7.2' }, + ], + applicabilityConditions: [ + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'REQUIRED', priority: 20 }, + { field: 'dataProfile.hasSpecialCategories', operator: 'EQUALS', value: true, result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['Besuchermanagement-Richtlinie', 'Besucherprotokolle', 'Zonenkonzept'], + reviewFrequency: 'ANNUAL', + priority: 'MEDIUM', + complexity: 'LOW', + tags: ['physical-security', 'visitors', 'extended'], + }, + + // ADMISSION_CONTROL — 1 new + { + id: 'TOM-ADM-06', + code: 'TOM-ADM-06', + category: 'ADMISSION_CONTROL', + type: 'TECHNICAL', + name: { de: 'Endpoint Detection & Response (EDR)', en: 'Endpoint Detection & Response (EDR)' }, + description: { + de: 'Einsatz von EDR-Loesungen zur Erkennung und Abwehr von Bedrohungen auf Endgeraeten in Echtzeit.', + en: 'Deployment of EDR solutions for real-time threat detection and response on endpoints.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.12.2.1' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'OPS.1.1.4' }, + ], + applicabilityConditions: [ + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'REQUIRED', priority: 25 }, + { field: 'companyProfile.size', operator: 'IN', value: ['LARGE', 'ENTERPRISE'], result: 'RECOMMENDED', priority: 10 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['EDR-Konfiguration', 'Bedrohungsberichte', 'Incident-Response-Statistiken'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'HIGH', + tags: ['endpoint', 'edr', 'threat-detection'], + }, + + // ACCESS_AUTHORIZATION — 2 new + { + id: 'TOM-AZ-06', + code: 'TOM-AZ-06', + category: 'ACCESS_AUTHORIZATION', + type: 'TECHNICAL', + name: { de: 'API-Zugriffskontrolle', en: 'API Access Control' }, + description: { + de: 'Implementierung von Authentifizierungs- und Autorisierungsmechanismen fuer APIs (OAuth 2.0, API-Keys, Rate Limiting).', + en: 'Implementation of authentication and authorization mechanisms for APIs (OAuth 2.0, API keys, rate limiting).', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.9.4.1' }, + ], + applicabilityConditions: [ + { field: 'architectureProfile.hostingModel', operator: 'IN', value: ['PUBLIC_CLOUD', 'HYBRID'], result: 'REQUIRED', priority: 20 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['API-Security-Konzept', 'OAuth-Konfiguration', 'Rate-Limiting-Regeln'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['authorization', 'api', 'oauth'], + }, + { + id: 'TOM-AZ-07', + code: 'TOM-AZ-07', + category: 'ACCESS_AUTHORIZATION', + type: 'ORGANIZATIONAL', + name: { de: 'Regelmaessiger Berechtigungsreview', en: 'Regular Permission Review' }, + description: { + de: 'Systematische Ueberpruefung und Bereinigung von Zugriffsberechtigungen in regelmaessigen Abstaenden durch die jeweiligen Fachverantwortlichen.', + en: 'Systematic review and cleanup of access permissions at regular intervals by the respective department heads.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. d' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.9.2.5' }, + ], + applicabilityConditions: [], + defaultApplicability: 'REQUIRED', + evidenceRequirements: ['Review-Protokolle', 'Berechtigungsaenderungslog', 'Freigabedokumentation'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'HIGH', + complexity: 'LOW', + tags: ['authorization', 'review', 'permissions'], + }, + + // TRANSFER_CONTROL — 2 new + { + id: 'TOM-TR-06', + code: 'TOM-TR-06', + category: 'TRANSFER_CONTROL', + type: 'TECHNICAL', + name: { de: 'E-Mail-Verschluesselung (erweitert)', en: 'Email Encryption (Extended)' }, + description: { + de: 'Erweiterte E-Mail-Verschluesselung mit automatischer Erkennung sensibler Inhalte und erzwungener Gateway-Verschluesselung.', + en: 'Extended email encryption with automatic detection of sensitive content and enforced gateway encryption.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. a' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.13.2.3' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.hasSpecialCategories', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['E-Mail-Verschluesselungs-Policy', 'Gateway-Konfiguration', 'DLP-Regeln'], + reviewFrequency: 'SEMI_ANNUAL', + priority: 'MEDIUM', + complexity: 'MEDIUM', + tags: ['transfer', 'email', 'encryption'], + }, + { + id: 'TOM-TR-07', + code: 'TOM-TR-07', + category: 'TRANSFER_CONTROL', + type: 'ORGANIZATIONAL', + name: { de: 'Drittstaat-Transferbewertung', en: 'Third Country Transfer Assessment' }, + description: { + de: 'Dokumentierte Bewertung und Absicherung von Datenuebermittlungen in Drittstaaten gemaess Art. 44-49 DSGVO (Standardvertragsklauseln, TIA).', + en: 'Documented assessment and safeguarding of data transfers to third countries according to Art. 44-49 GDPR (Standard Contractual Clauses, TIA).', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 44-49' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.15.1.2' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.thirdCountryTransfers', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 30 }, + { field: 'architectureProfile.hostingLocation', operator: 'IN', value: ['THIRD_COUNTRY_ADEQUATE', 'THIRD_COUNTRY'], result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['Transfer Impact Assessment', 'Standardvertragsklauseln', 'Angemessenheitsbeschluss-Pruefung'], + reviewFrequency: 'ANNUAL', + priority: 'CRITICAL', + complexity: 'MEDIUM', + tags: ['transfer', 'third-country', 'schrems-ii'], + }, + + // AVAILABILITY — 2 new + { + id: 'TOM-AV-06', + code: 'TOM-AV-06', + category: 'AVAILABILITY', + type: 'TECHNICAL', + name: { de: 'Monitoring und Alerting', en: 'Monitoring and Alerting' }, + description: { + de: 'Implementierung einer umfassenden Ueberwachung aller IT-Systeme mit automatischen Benachrichtigungen bei Stoerungen oder Schwellenwert-Ueberschreitungen.', + en: 'Implementation of comprehensive monitoring of all IT systems with automatic notifications for disruptions or threshold violations.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.12.4.1' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'OPS.1.1.2' }, + ], + applicabilityConditions: [], + defaultApplicability: 'REQUIRED', + evidenceRequirements: ['Monitoring-Konzept', 'Alerting-Konfiguration', 'Eskalationsmatrix'], + reviewFrequency: 'QUARTERLY', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['availability', 'monitoring', 'alerting'], + }, + { + id: 'TOM-AV-07', + code: 'TOM-AV-07', + category: 'AVAILABILITY', + type: 'ORGANIZATIONAL', + name: { de: 'Service Level Management', en: 'Service Level Management' }, + description: { + de: 'Definition und Ueberwachung von Service Level Agreements (SLAs) fuer alle kritischen IT-Services mit klaren Verfuegbarkeitszielen.', + en: 'Definition and monitoring of Service Level Agreements (SLAs) for all critical IT services with clear availability targets.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. b' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.15.2.1' }, + ], + applicabilityConditions: [ + { field: 'companyProfile.size', operator: 'IN', value: ['MEDIUM', 'LARGE', 'ENTERPRISE'], result: 'RECOMMENDED', priority: 10 }, + { field: 'architectureProfile.hasSubprocessors', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 20 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['SLA-Dokumentation', 'Verfuegbarkeitsberichte', 'Eskalationsverfahren'], + reviewFrequency: 'QUARTERLY', + priority: 'MEDIUM', + complexity: 'LOW', + tags: ['availability', 'sla', 'service-management'], + }, + + // SEPARATION — 1 more new (TOM-DL-05) + { + id: 'TOM-DL-05', + code: 'TOM-DL-05', + category: 'SEPARATION', + type: 'ORGANIZATIONAL', + name: { de: 'Datenloesch-Audit', en: 'Data Deletion Audit' }, + description: { + de: 'Regelmaessige Ueberpruefung der Wirksamkeit und Vollstaendigkeit von Datenloeschvorgaengen durch unabhaengige Stellen.', + en: 'Regular review of the effectiveness and completeness of data deletion processes by independent parties.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 5 Abs. 1 lit. e' }, + { framework: 'GDPR_ART32', reference: 'Art. 17' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.8.3.2' }, + ], + applicabilityConditions: [ + { field: 'dataProfile.hasSpecialCategories', operator: 'EQUALS', value: true, result: 'REQUIRED', priority: 25 }, + ], + defaultApplicability: 'RECOMMENDED', + evidenceRequirements: ['Audit-Berichte', 'Loeschprotokolle', 'Stichproben-Ergebnisse'], + reviewFrequency: 'ANNUAL', + priority: 'MEDIUM', + complexity: 'MEDIUM', + tags: ['separation', 'deletion', 'audit'], + }, + + // REVIEW — 3 new + { + id: 'TOM-RV-09', + code: 'TOM-RV-09', + category: 'REVIEW', + type: 'ORGANIZATIONAL', + name: { de: 'Datenschutz-Audit-Programm', en: 'Data Protection Audit Program' }, + description: { + de: 'Systematisches Programm zur regelmaessigen internen Ueberpruefung aller Datenschutzmassnahmen mit dokumentierten Ergebnissen und Massnahmenverfolgung.', + en: 'Systematic program for regular internal review of all data protection measures with documented results and action tracking.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. d' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.18.2.1' }, + { framework: 'BSI_IT_GRUNDSCHUTZ', reference: 'DER.3.1' }, + ], + applicabilityConditions: [], + defaultApplicability: 'REQUIRED', + evidenceRequirements: ['Audit-Programm', 'Audit-Berichte', 'Massnahmenplan'], + reviewFrequency: 'ANNUAL', + priority: 'HIGH', + complexity: 'MEDIUM', + tags: ['review', 'audit', 'data-protection'], + }, + { + id: 'TOM-RV-10', + code: 'TOM-RV-10', + category: 'REVIEW', + type: 'TECHNICAL', + name: { de: 'Automatisierte Compliance-Pruefung', en: 'Automated Compliance Checking' }, + description: { + de: 'Einsatz automatisierter Tools zur kontinuierlichen Ueberpruefung der Einhaltung von Sicherheits- und Datenschutzrichtlinien.', + en: 'Use of automated tools for continuous monitoring of compliance with security and data protection policies.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. d' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.18.2.2' }, + ], + applicabilityConditions: [ + { field: 'companyProfile.size', operator: 'IN', value: ['MEDIUM', 'LARGE', 'ENTERPRISE'], result: 'RECOMMENDED', priority: 10 }, + { field: 'riskProfile.protectionLevel', operator: 'IN', value: ['HIGH', 'VERY_HIGH'], result: 'RECOMMENDED', priority: 15 }, + ], + defaultApplicability: 'OPTIONAL', + evidenceRequirements: ['Tool-Konfiguration', 'Compliance-Dashboard', 'Automatisierte Berichte'], + reviewFrequency: 'QUARTERLY', + priority: 'MEDIUM', + complexity: 'HIGH', + tags: ['review', 'automation', 'compliance'], + }, + { + id: 'TOM-RV-11', + code: 'TOM-RV-11', + category: 'REVIEW', + type: 'ORGANIZATIONAL', + name: { de: 'Management Review (Art. 32 Abs. 1 lit. d)', en: 'Management Review (Art. 32(1)(d))' }, + description: { + de: 'Regelmaessige Ueberpruefung der Wirksamkeit aller technischen und organisatorischen Massnahmen durch die Geschaeftsfuehrung mit dokumentierten Ergebnissen.', + en: 'Regular review of the effectiveness of all technical and organizational measures by management with documented results.', + }, + mappings: [ + { framework: 'GDPR_ART32', reference: 'Art. 32 Abs. 1 lit. d' }, + { framework: 'ISO27001_ANNEX_A', reference: 'A.18.2.1' }, + ], + applicabilityConditions: [], + defaultApplicability: 'REQUIRED', + evidenceRequirements: ['Management-Review-Protokolle', 'Massnahmenplan', 'Wirksamkeitsbewertung'], + reviewFrequency: 'ANNUAL', + priority: 'HIGH', + complexity: 'LOW', + tags: ['review', 'management', 'effectiveness'], + }, ], } diff --git a/backend-compliance/compliance/api/__init__.py b/backend-compliance/compliance/api/__init__.py index dc36085..d9551a6 100644 --- a/backend-compliance/compliance/api/__init__.py +++ b/backend-compliance/compliance/api/__init__.py @@ -56,6 +56,8 @@ _ROUTER_MODULES = [ "crosswalk_routes", "process_task_routes", "evidence_check_routes", + "vvt_library_routes", + "tom_mapping_routes", ] _loaded_count = 0 diff --git a/backend-compliance/compliance/api/tom_mapping_routes.py b/backend-compliance/compliance/api/tom_mapping_routes.py new file mode 100644 index 0000000..0e3d3e0 --- /dev/null +++ b/backend-compliance/compliance/api/tom_mapping_routes.py @@ -0,0 +1,537 @@ +""" +TOM ↔ Canonical Control Mapping Routes. + +Three-layer architecture: + TOM Measures (~88, audit-level) → Mapping Bridge → Canonical Controls (10,000+) + +Endpoints: + POST /v1/tom-mappings/sync — Sync canonical controls for company profile + GET /v1/tom-mappings — List all mappings for tenant/project + GET /v1/tom-mappings/by-tom/{code} — Mappings for a specific TOM control + GET /v1/tom-mappings/stats — Coverage statistics + POST /v1/tom-mappings/manual — Manually add a mapping + DELETE /v1/tom-mappings/{id} — Remove a mapping +""" + +from __future__ import annotations + +import hashlib +import json +import logging +from typing import Any, Optional + +from fastapi import APIRouter, HTTPException, Query, Header +from pydantic import BaseModel +from sqlalchemy import text + +from database import SessionLocal + +logger = logging.getLogger(__name__) +router = APIRouter(prefix="/tom-mappings", tags=["tom-control-mappings"]) + + +# ============================================================================= +# TOM CATEGORY → CANONICAL CATEGORY MAPPING +# ============================================================================= + +# Maps 13 TOM control categories to canonical_control_categories +# Each TOM category maps to 1-3 canonical categories for broad coverage +TOM_TO_CANONICAL_CATEGORIES: dict[str, list[str]] = { + "ACCESS_CONTROL": ["authentication", "identity", "physical"], + "ADMISSION_CONTROL": ["authentication", "identity", "system"], + "ACCESS_AUTHORIZATION": ["authentication", "identity"], + "TRANSFER_CONTROL": ["network", "data_protection", "encryption"], + "INPUT_CONTROL": ["application", "data_protection"], + "ORDER_CONTROL": ["supply_chain", "compliance"], + "AVAILABILITY": ["continuity", "system"], + "SEPARATION": ["network", "data_protection"], + "ENCRYPTION": ["encryption"], + "PSEUDONYMIZATION": ["data_protection", "encryption"], + "RESILIENCE": ["continuity", "system"], + "RECOVERY": ["continuity"], + "REVIEW": ["compliance", "governance", "risk"], +} + + +# ============================================================================= +# REQUEST / RESPONSE MODELS +# ============================================================================= + +class SyncRequest(BaseModel): + """Trigger a sync of canonical controls to TOM measures.""" + industry: Optional[str] = None + company_size: Optional[str] = None + force: bool = False + + +class ManualMappingRequest(BaseModel): + """Manually add a canonical control to a TOM measure.""" + tom_control_code: str + tom_category: str + canonical_control_id: str + canonical_control_code: str + canonical_category: Optional[str] = None + relevance_score: float = 1.0 + + +# ============================================================================= +# HELPERS +# ============================================================================= + +def _get_tenant_id(x_tenant_id: Optional[str]) -> str: + """Extract tenant ID from header.""" + if not x_tenant_id: + raise HTTPException(status_code=400, detail="X-Tenant-ID header required") + return x_tenant_id + + +def _compute_profile_hash(industry: Optional[str], company_size: Optional[str]) -> str: + """Compute a hash from profile parameters for change detection.""" + data = json.dumps({"industry": industry, "company_size": company_size}, sort_keys=True) + return hashlib.sha256(data.encode()).hexdigest()[:16] + + +def _mapping_row_to_dict(r) -> dict[str, Any]: + """Convert a mapping row to API response dict.""" + return { + "id": str(r.id), + "tenant_id": str(r.tenant_id), + "project_id": str(r.project_id) if r.project_id else None, + "tom_control_code": r.tom_control_code, + "tom_category": r.tom_category, + "canonical_control_id": str(r.canonical_control_id), + "canonical_control_code": r.canonical_control_code, + "canonical_category": r.canonical_category, + "mapping_type": r.mapping_type, + "relevance_score": float(r.relevance_score) if r.relevance_score else 1.0, + "created_at": r.created_at.isoformat() if r.created_at else None, + } + + +# ============================================================================= +# SYNC ENDPOINT +# ============================================================================= + +@router.post("/sync") +async def sync_mappings( + body: SyncRequest, + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), + project_id: Optional[str] = Query(None), +): + """ + Sync canonical controls to TOM measures based on company profile. + + Algorithm: + 1. Compute profile hash → skip if unchanged (unless force=True) + 2. For each TOM category, find matching canonical controls by: + - Category mapping (TOM category → canonical categories) + - Industry filter (applicable_industries JSONB containment) + - Company size filter (applicable_company_size JSONB containment) + - Only approved + customer_visible controls + 3. Delete old auto-mappings, insert new ones + 4. Update sync state + """ + tenant_id = _get_tenant_id(x_tenant_id) + profile_hash = _compute_profile_hash(body.industry, body.company_size) + + with SessionLocal() as db: + # Check if sync is needed (profile unchanged) + if not body.force: + existing = db.execute( + text(""" + SELECT profile_hash FROM tom_control_sync_state + WHERE tenant_id = :tid AND (project_id = :pid OR (project_id IS NULL AND :pid IS NULL)) + """), + {"tid": tenant_id, "pid": project_id}, + ).fetchone() + if existing and existing.profile_hash == profile_hash: + return { + "status": "unchanged", + "message": "Profile unchanged since last sync", + "profile_hash": profile_hash, + } + + # Delete old auto-mappings for this tenant+project + db.execute( + text(""" + DELETE FROM tom_control_mappings + WHERE tenant_id = :tid + AND (project_id = :pid OR (project_id IS NULL AND :pid IS NULL)) + AND mapping_type = 'auto' + """), + {"tid": tenant_id, "pid": project_id}, + ) + + total_mappings = 0 + canonical_ids_matched = set() + tom_codes_covered = set() + + # For each TOM category, find matching canonical controls + for tom_category, canonical_categories in TOM_TO_CANONICAL_CATEGORIES.items(): + # Build JSONB containment query for categories + cat_conditions = " OR ".join( + f"category = :cat_{i}" for i in range(len(canonical_categories)) + ) + cat_params = {f"cat_{i}": c for i, c in enumerate(canonical_categories)} + + # Build industry filter + industry_filter = "" + if body.industry: + industry_filter = """ + AND ( + applicable_industries IS NULL + OR applicable_industries @> '"all"'::jsonb + OR applicable_industries @> (:industry)::jsonb + ) + """ + cat_params["industry"] = json.dumps([body.industry]) + + # Build company size filter + size_filter = "" + if body.company_size: + size_filter = """ + AND ( + applicable_company_size IS NULL + OR applicable_company_size @> '"all"'::jsonb + OR applicable_company_size @> (:csize)::jsonb + ) + """ + cat_params["csize"] = json.dumps([body.company_size]) + + query = f""" + SELECT id, control_id, category + FROM canonical_controls + WHERE ({cat_conditions}) + AND release_state = 'approved' + AND customer_visible = true + {industry_filter} + {size_filter} + ORDER BY control_id + """ + + rows = db.execute(text(query), cat_params).fetchall() + + # Find TOM control codes in this category (query the frontend library + # codes; we use the category prefix pattern from the loader) + # TOM codes follow pattern: TOM-XX-NN where XX is category abbreviation + # We insert one mapping per canonical control per TOM category + for row in rows: + db.execute( + text(""" + INSERT INTO tom_control_mappings ( + tenant_id, project_id, tom_control_code, tom_category, + canonical_control_id, canonical_control_code, canonical_category, + mapping_type, relevance_score + ) VALUES ( + :tid, :pid, :tom_cat, :tom_cat, + :cc_id, :cc_code, :cc_category, + 'auto', 1.00 + ) + ON CONFLICT (tenant_id, project_id, tom_control_code, canonical_control_id) + DO NOTHING + """), + { + "tid": tenant_id, + "pid": project_id, + "tom_cat": tom_category, + "cc_id": str(row.id), + "cc_code": row.control_id, + "cc_category": row.category, + }, + ) + total_mappings += 1 + canonical_ids_matched.add(str(row.id)) + tom_codes_covered.add(tom_category) + + # Upsert sync state + db.execute( + text(""" + INSERT INTO tom_control_sync_state ( + tenant_id, project_id, profile_hash, + total_mappings, canonical_controls_matched, tom_controls_covered, + last_synced_at + ) VALUES ( + :tid, :pid, :hash, + :total, :matched, :covered, + NOW() + ) + ON CONFLICT (tenant_id, project_id) + DO UPDATE SET + profile_hash = :hash, + total_mappings = :total, + canonical_controls_matched = :matched, + tom_controls_covered = :covered, + last_synced_at = NOW() + """), + { + "tid": tenant_id, + "pid": project_id, + "hash": profile_hash, + "total": total_mappings, + "matched": len(canonical_ids_matched), + "covered": len(tom_codes_covered), + }, + ) + + db.commit() + + return { + "status": "synced", + "profile_hash": profile_hash, + "total_mappings": total_mappings, + "canonical_controls_matched": len(canonical_ids_matched), + "tom_categories_covered": len(tom_codes_covered), + } + + +# ============================================================================= +# LIST MAPPINGS +# ============================================================================= + +@router.get("") +async def list_mappings( + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), + project_id: Optional[str] = Query(None), + tom_category: Optional[str] = Query(None), + mapping_type: Optional[str] = Query(None), + limit: int = Query(500, ge=1, le=5000), + offset: int = Query(0, ge=0), +): + """List all TOM ↔ canonical control mappings for tenant/project.""" + tenant_id = _get_tenant_id(x_tenant_id) + + query = """ + SELECT m.*, cc.title as canonical_title, cc.severity as canonical_severity + FROM tom_control_mappings m + LEFT JOIN canonical_controls cc ON cc.id = m.canonical_control_id + WHERE m.tenant_id = :tid + AND (m.project_id = :pid OR (m.project_id IS NULL AND :pid IS NULL)) + """ + params: dict[str, Any] = {"tid": tenant_id, "pid": project_id} + + if tom_category: + query += " AND m.tom_category = :tcat" + params["tcat"] = tom_category + if mapping_type: + query += " AND m.mapping_type = :mtype" + params["mtype"] = mapping_type + + query += " ORDER BY m.tom_category, m.canonical_control_code" + query += " LIMIT :lim OFFSET :off" + params["lim"] = limit + params["off"] = offset + + count_query = """ + SELECT count(*) FROM tom_control_mappings + WHERE tenant_id = :tid + AND (project_id = :pid OR (project_id IS NULL AND :pid IS NULL)) + """ + count_params: dict[str, Any] = {"tid": tenant_id, "pid": project_id} + if tom_category: + count_query += " AND tom_category = :tcat" + count_params["tcat"] = tom_category + + with SessionLocal() as db: + rows = db.execute(text(query), params).fetchall() + total = db.execute(text(count_query), count_params).scalar() + + mappings = [] + for r in rows: + d = _mapping_row_to_dict(r) + d["canonical_title"] = getattr(r, "canonical_title", None) + d["canonical_severity"] = getattr(r, "canonical_severity", None) + mappings.append(d) + + return {"mappings": mappings, "total": total} + + +# ============================================================================= +# MAPPINGS BY TOM CONTROL +# ============================================================================= + +@router.get("/by-tom/{tom_code}") +async def get_mappings_by_tom( + tom_code: str, + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), + project_id: Optional[str] = Query(None), +): + """Get all canonical controls mapped to a specific TOM control code or category.""" + tenant_id = _get_tenant_id(x_tenant_id) + + with SessionLocal() as db: + rows = db.execute( + text(""" + SELECT m.*, cc.title as canonical_title, cc.severity as canonical_severity, + cc.objective as canonical_objective + FROM tom_control_mappings m + LEFT JOIN canonical_controls cc ON cc.id = m.canonical_control_id + WHERE m.tenant_id = :tid + AND (m.project_id = :pid OR (m.project_id IS NULL AND :pid IS NULL)) + AND (m.tom_control_code = :code OR m.tom_category = :code) + ORDER BY m.canonical_control_code + """), + {"tid": tenant_id, "pid": project_id, "code": tom_code}, + ).fetchall() + + mappings = [] + for r in rows: + d = _mapping_row_to_dict(r) + d["canonical_title"] = getattr(r, "canonical_title", None) + d["canonical_severity"] = getattr(r, "canonical_severity", None) + d["canonical_objective"] = getattr(r, "canonical_objective", None) + mappings.append(d) + + return {"tom_code": tom_code, "mappings": mappings, "total": len(mappings)} + + +# ============================================================================= +# STATS +# ============================================================================= + +@router.get("/stats") +async def get_mapping_stats( + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), + project_id: Optional[str] = Query(None), +): + """Coverage statistics for TOM ↔ canonical control mappings.""" + tenant_id = _get_tenant_id(x_tenant_id) + + with SessionLocal() as db: + # Sync state + sync_state = db.execute( + text(""" + SELECT * FROM tom_control_sync_state + WHERE tenant_id = :tid + AND (project_id = :pid OR (project_id IS NULL AND :pid IS NULL)) + """), + {"tid": tenant_id, "pid": project_id}, + ).fetchone() + + # Per-category breakdown + category_stats = db.execute( + text(""" + SELECT tom_category, + count(*) as total_mappings, + count(DISTINCT canonical_control_id) as unique_controls, + count(*) FILTER (WHERE mapping_type = 'auto') as auto_count, + count(*) FILTER (WHERE mapping_type = 'manual') as manual_count + FROM tom_control_mappings + WHERE tenant_id = :tid + AND (project_id = :pid OR (project_id IS NULL AND :pid IS NULL)) + GROUP BY tom_category + ORDER BY tom_category + """), + {"tid": tenant_id, "pid": project_id}, + ).fetchall() + + # Total canonical controls in DB (approved + visible) + total_canonical = db.execute( + text(""" + SELECT count(*) FROM canonical_controls + WHERE release_state = 'approved' AND customer_visible = true + """) + ).scalar() + + return { + "sync_state": { + "profile_hash": sync_state.profile_hash if sync_state else None, + "total_mappings": sync_state.total_mappings if sync_state else 0, + "canonical_controls_matched": sync_state.canonical_controls_matched if sync_state else 0, + "tom_controls_covered": sync_state.tom_controls_covered if sync_state else 0, + "last_synced_at": sync_state.last_synced_at.isoformat() if sync_state and sync_state.last_synced_at else None, + }, + "category_breakdown": [ + { + "tom_category": r.tom_category, + "total_mappings": r.total_mappings, + "unique_controls": r.unique_controls, + "auto_count": r.auto_count, + "manual_count": r.manual_count, + } + for r in category_stats + ], + "total_canonical_controls_available": total_canonical or 0, + } + + +# ============================================================================= +# MANUAL MAPPING +# ============================================================================= + +@router.post("/manual", status_code=201) +async def add_manual_mapping( + body: ManualMappingRequest, + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), + project_id: Optional[str] = Query(None), +): + """Manually add a canonical control to a TOM measure.""" + tenant_id = _get_tenant_id(x_tenant_id) + + with SessionLocal() as db: + # Verify canonical control exists + cc = db.execute( + text("SELECT id, control_id, category FROM canonical_controls WHERE id = CAST(:cid AS uuid)"), + {"cid": body.canonical_control_id}, + ).fetchone() + if not cc: + raise HTTPException(status_code=404, detail="Canonical control not found") + + try: + row = db.execute( + text(""" + INSERT INTO tom_control_mappings ( + tenant_id, project_id, tom_control_code, tom_category, + canonical_control_id, canonical_control_code, canonical_category, + mapping_type, relevance_score + ) VALUES ( + :tid, :pid, :tom_code, :tom_cat, + CAST(:cc_id AS uuid), :cc_code, :cc_category, + 'manual', :score + ) + RETURNING * + """), + { + "tid": tenant_id, + "pid": project_id, + "tom_code": body.tom_control_code, + "tom_cat": body.tom_category, + "cc_id": body.canonical_control_id, + "cc_code": body.canonical_control_code, + "cc_category": body.canonical_category or cc.category, + "score": body.relevance_score, + }, + ).fetchone() + db.commit() + except Exception as e: + if "unique" in str(e).lower() or "duplicate" in str(e).lower(): + raise HTTPException(status_code=409, detail="Mapping already exists") + raise + + return _mapping_row_to_dict(row) + + +# ============================================================================= +# DELETE MAPPING +# ============================================================================= + +@router.delete("/{mapping_id}", status_code=204) +async def delete_mapping( + mapping_id: str, + x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), +): + """Remove a mapping (manual or auto).""" + tenant_id = _get_tenant_id(x_tenant_id) + + with SessionLocal() as db: + result = db.execute( + text(""" + DELETE FROM tom_control_mappings + WHERE id = CAST(:mid AS uuid) AND tenant_id = :tid + """), + {"mid": mapping_id, "tid": tenant_id}, + ) + if result.rowcount == 0: + raise HTTPException(status_code=404, detail="Mapping not found") + db.commit() + + return None diff --git a/backend-compliance/migrations/068_tom_control_mappings.sql b/backend-compliance/migrations/068_tom_control_mappings.sql new file mode 100644 index 0000000..7841141 --- /dev/null +++ b/backend-compliance/migrations/068_tom_control_mappings.sql @@ -0,0 +1,65 @@ +-- Migration 068: TOM ↔ Canonical Control Mappings +-- Bridge table connecting TOM measures (88) to Canonical Controls (10,000+) +-- Enables three-layer architecture: TOM → Mapping → Canonical Controls + +-- ============================================================================ +-- 1. Mapping table (TOM control code → Canonical control) +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS tom_control_mappings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + project_id UUID, + + -- TOM side (references the embedded TOM control code, e.g. 'TOM-AC-01') + tom_control_code VARCHAR(20) NOT NULL, + tom_category VARCHAR(50) NOT NULL, + + -- Canonical control side + canonical_control_id UUID NOT NULL, + canonical_control_code VARCHAR(20) NOT NULL, + canonical_category VARCHAR(50), + + -- Mapping metadata + mapping_type VARCHAR(20) NOT NULL DEFAULT 'auto' + CHECK (mapping_type IN ('auto', 'manual')), + relevance_score NUMERIC(3,2) DEFAULT 1.00 + CHECK (relevance_score >= 0 AND relevance_score <= 1), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- No duplicate mappings per tenant+project+TOM+canonical + UNIQUE (tenant_id, project_id, tom_control_code, canonical_control_id) +); + +CREATE INDEX IF NOT EXISTS idx_tcm_tenant_project + ON tom_control_mappings (tenant_id, project_id); +CREATE INDEX IF NOT EXISTS idx_tcm_tom_code + ON tom_control_mappings (tom_control_code); +CREATE INDEX IF NOT EXISTS idx_tcm_canonical_id + ON tom_control_mappings (canonical_control_id); +CREATE INDEX IF NOT EXISTS idx_tcm_tom_category + ON tom_control_mappings (tom_category); + +-- ============================================================================ +-- 2. Sync state (tracks when the last sync ran + profile hash) +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS tom_control_sync_state ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + project_id UUID, + + -- Profile hash to detect changes (SHA-256 of serialized company profile) + profile_hash VARCHAR(64), + + -- Stats from last sync + total_mappings INTEGER DEFAULT 0, + canonical_controls_matched INTEGER DEFAULT 0, + tom_controls_covered INTEGER DEFAULT 0, + + last_synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- One sync state per tenant+project + UNIQUE (tenant_id, project_id) +); diff --git a/backend-compliance/tests/test_tom_mapping_routes.py b/backend-compliance/tests/test_tom_mapping_routes.py new file mode 100644 index 0000000..6f38ece --- /dev/null +++ b/backend-compliance/tests/test_tom_mapping_routes.py @@ -0,0 +1,274 @@ +""" +Tests for TOM ↔ Canonical Control Mapping Routes. + +Tests the three-layer architecture: + TOM Measures → Mapping Bridge → Canonical Controls +""" + +import uuid +import pytest +from unittest.mock import MagicMock, patch +from fastapi.testclient import TestClient + +from compliance.api.tom_mapping_routes import ( + router, + TOM_TO_CANONICAL_CATEGORIES, + _compute_profile_hash, +) + + +# ============================================================================= +# FIXTURES +# ============================================================================= + +@pytest.fixture +def app(): + """Create a test FastAPI app with the TOM mapping router.""" + from fastapi import FastAPI + app = FastAPI() + app.include_router(router) + return app + + +@pytest.fixture +def client(app): + return TestClient(app) + + +TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" +PROJECT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" +HEADERS = {"X-Tenant-ID": TENANT_ID} + + +# ============================================================================= +# UNIT TESTS +# ============================================================================= + +class TestCategoryMapping: + """Test the TOM → Canonical category mapping dictionary.""" + + def test_all_13_tom_categories_mapped(self): + expected = { + "ACCESS_CONTROL", "ADMISSION_CONTROL", "ACCESS_AUTHORIZATION", + "TRANSFER_CONTROL", "INPUT_CONTROL", "ORDER_CONTROL", + "AVAILABILITY", "SEPARATION", "ENCRYPTION", "PSEUDONYMIZATION", + "RESILIENCE", "RECOVERY", "REVIEW", + } + assert set(TOM_TO_CANONICAL_CATEGORIES.keys()) == expected + + def test_each_category_has_at_least_one_canonical(self): + for tom_cat, canonical_cats in TOM_TO_CANONICAL_CATEGORIES.items(): + assert len(canonical_cats) >= 1, f"{tom_cat} has no canonical categories" + + def test_canonical_categories_are_valid(self): + """All referenced canonical categories must exist in the DB seed (migration 047).""" + valid_canonical = { + "encryption", "authentication", "network", "data_protection", + "logging", "incident", "continuity", "compliance", "supply_chain", + "physical", "personnel", "application", "system", "risk", + "governance", "hardware", "identity", + } + for tom_cat, canonical_cats in TOM_TO_CANONICAL_CATEGORIES.items(): + for cc in canonical_cats: + assert cc in valid_canonical, f"Invalid canonical category '{cc}' in {tom_cat}" + + +class TestProfileHash: + """Test profile hash computation.""" + + def test_same_input_same_hash(self): + h1 = _compute_profile_hash("Telekommunikation", "medium") + h2 = _compute_profile_hash("Telekommunikation", "medium") + assert h1 == h2 + + def test_different_input_different_hash(self): + h1 = _compute_profile_hash("Telekommunikation", "medium") + h2 = _compute_profile_hash("Gesundheitswesen", "large") + assert h1 != h2 + + def test_none_values_produce_hash(self): + h = _compute_profile_hash(None, None) + assert len(h) == 16 + + def test_hash_is_16_chars(self): + h = _compute_profile_hash("test", "small") + assert len(h) == 16 + + +# ============================================================================= +# API ENDPOINT TESTS (with mocked DB) +# ============================================================================= + +class TestSyncEndpoint: + """Test POST /tom-mappings/sync.""" + + def test_sync_requires_tenant_header(self, client): + resp = client.post("/tom-mappings/sync", json={"industry": "IT"}) + assert resp.status_code == 400 + assert "X-Tenant-ID" in resp.json()["detail"] + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_sync_unchanged_profile_skips(self, mock_session_cls, client): + """When profile hash matches, sync should return 'unchanged'.""" + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + profile_hash = _compute_profile_hash("IT", "medium") + mock_row = MagicMock() + mock_row.profile_hash = profile_hash + mock_db.execute.return_value.fetchone.return_value = mock_row + + resp = client.post( + "/tom-mappings/sync", + json={"industry": "IT", "company_size": "medium"}, + headers=HEADERS, + ) + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "unchanged" + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_sync_force_ignores_hash(self, mock_session_cls, client): + """force=True should sync even if hash matches.""" + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + # Return empty results for canonical control queries + mock_db.execute.return_value.fetchall.return_value = [] + mock_db.execute.return_value.fetchone.return_value = None + + resp = client.post( + "/tom-mappings/sync", + json={"industry": "IT", "company_size": "medium", "force": True}, + headers=HEADERS, + ) + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "synced" + + +class TestListEndpoint: + """Test GET /tom-mappings.""" + + def test_list_requires_tenant_header(self, client): + resp = client.get("/tom-mappings") + assert resp.status_code == 400 + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_list_returns_mappings(self, mock_session_cls, client): + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + mock_db.execute.return_value.fetchall.return_value = [] + mock_db.execute.return_value.scalar.return_value = 0 + + resp = client.get("/tom-mappings", headers=HEADERS) + assert resp.status_code == 200 + data = resp.json() + assert "mappings" in data + assert "total" in data + + +class TestByTomEndpoint: + """Test GET /tom-mappings/by-tom/{code}.""" + + def test_by_tom_requires_tenant_header(self, client): + resp = client.get("/tom-mappings/by-tom/ENCRYPTION") + assert resp.status_code == 400 + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_by_tom_returns_mappings(self, mock_session_cls, client): + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + mock_db.execute.return_value.fetchall.return_value = [] + + resp = client.get("/tom-mappings/by-tom/ENCRYPTION", headers=HEADERS) + assert resp.status_code == 200 + data = resp.json() + assert data["tom_code"] == "ENCRYPTION" + assert "mappings" in data + + +class TestStatsEndpoint: + """Test GET /tom-mappings/stats.""" + + def test_stats_requires_tenant_header(self, client): + resp = client.get("/tom-mappings/stats") + assert resp.status_code == 400 + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_stats_returns_structure(self, mock_session_cls, client): + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + mock_db.execute.return_value.fetchone.return_value = None + mock_db.execute.return_value.fetchall.return_value = [] + mock_db.execute.return_value.scalar.return_value = 0 + + resp = client.get("/tom-mappings/stats", headers=HEADERS) + assert resp.status_code == 200 + data = resp.json() + assert "sync_state" in data + assert "category_breakdown" in data + assert "total_canonical_controls_available" in data + + +class TestManualMappingEndpoint: + """Test POST /tom-mappings/manual.""" + + def test_manual_requires_tenant_header(self, client): + resp = client.post("/tom-mappings/manual", json={ + "tom_control_code": "TOM-ENC-01", + "tom_category": "ENCRYPTION", + "canonical_control_id": str(uuid.uuid4()), + "canonical_control_code": "CRYP-001", + }) + assert resp.status_code == 400 + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_manual_404_if_canonical_not_found(self, mock_session_cls, client): + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + mock_db.execute.return_value.fetchone.return_value = None + + resp = client.post( + "/tom-mappings/manual", + json={ + "tom_control_code": "TOM-ENC-01", + "tom_category": "ENCRYPTION", + "canonical_control_id": str(uuid.uuid4()), + "canonical_control_code": "CRYP-001", + }, + headers=HEADERS, + ) + assert resp.status_code == 404 + + +class TestDeleteMappingEndpoint: + """Test DELETE /tom-mappings/{id}.""" + + def test_delete_requires_tenant_header(self, client): + resp = client.delete(f"/tom-mappings/{uuid.uuid4()}") + assert resp.status_code == 400 + + @patch("compliance.api.tom_mapping_routes.SessionLocal") + def test_delete_404_if_not_found(self, mock_session_cls, client): + mock_db = MagicMock() + mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_db) + mock_session_cls.return_value.__exit__ = MagicMock(return_value=False) + + mock_db.execute.return_value.rowcount = 0 + + resp = client.delete( + f"/tom-mappings/{uuid.uuid4()}", + headers=HEADERS, + ) + assert resp.status_code == 404 diff --git a/docs-src/services/sdk-modules/tom.md b/docs-src/services/sdk-modules/tom.md new file mode 100644 index 0000000..ec5ef77 --- /dev/null +++ b/docs-src/services/sdk-modules/tom.md @@ -0,0 +1,271 @@ +# TOM — Technische und Organisatorische Massnahmen (Art. 32 DSGVO) + +## Uebersicht + +Das TOM-Modul implementiert die systematische Ableitung, Dokumentation und Ueberpruefung technischer und organisatorischer Massnahmen gemaess Art. 32 DSGVO. Es bietet einen 6-Schritt-Generator-Wizard, eine regelbasierte Kontrollbibliothek mit 88 Massnahmen, Gap-Analyse, SDM-Mapping, auditfaehige Dokumentengenerierung und 11 Compliance-Checks. + +**Route:** `/sdk/tom` | **Backend:** `backend-compliance:8002` | **Checkpoint:** `CP-TOM` + +--- + +## Art. 32 DSGVO Anforderungen + +| Absatz | Anforderung | TOM-Modul Umsetzung | +|--------|-------------|---------------------| +| Art. 32 Abs. 1 lit. a | Pseudonymisierung und Verschluesselung | Kategorien ENCRYPTION (5 Controls) und PSEUDONYMIZATION (4 Controls) | +| Art. 32 Abs. 1 lit. b | Vertraulichkeit, Integritaet, Verfuegbarkeit, Belastbarkeit | 8 Kategorien: ACCESS_CONTROL, ADMISSION_CONTROL, ACCESS_AUTHORIZATION, TRANSFER_CONTROL, RESILIENCE, AVAILABILITY, SEPARATION, INPUT_CONTROL | +| Art. 32 Abs. 1 lit. c | Rasche Wiederherstellung nach Zwischenfall | Kategorie RECOVERY (5 Controls) | +| Art. 32 Abs. 1 lit. d | Regelmaessige Ueberpruefung und Bewertung | Kategorie REVIEW (11 Controls) + Compliance-Check NO_REVIEW_PROCESS | + +--- + +## TOM-Ableitung — Zwei Quellen + +TOMs werden aus zwei unabhaengigen Quellen abgeleitet: + +### 1. Scope/Profil-Module (Embedded Controls) + +Der 6-Schritt-Wizard erfasst: + +- **CompanyProfile**: Branche, Groesse, Rolle (Controller/Processor) +- **DataProfile**: Datenkategorien, besondere Kategorien (Art. 9), Betroffene +- **ArchitectureProfile**: Hosting, Cloud-Provider, Mandantenfaehigkeit +- **SecurityProfile**: Auth, Backup, Logging, DR-Plan +- **RiskProfile**: CIA-Bewertung, Schutzniveau + +Die **Rules Engine** wertet 88 Embedded Controls gegen die Profile-Daten aus. Jeder Control hat `applicabilityConditions` mit Operatoren (EQUALS, IN, GREATER_THAN, CONTAINS) und Prioritaeten. + +### 2. Canonical Control Library (CP-CLIB) + +Die dynamisch generierte **Canonical Control Library** (`/sdk/control-library`) enthaelt unternehmensrelevante Security-Controls aus OWASP, NIST, ENISA und weiteren Frameworks. Diese werden durch den Control Generator Pipeline erzeugt und haben einen eigenen Review-Workflow. + +!!! info "Herkunftsdokumentation" + Im TOM-Dokument (Sektion 2 und 6) wird die **Herkunft** jeder Massnahme dokumentiert — + ob sie aus der Embedded Control Library oder der Canonical Control Library stammt. + +--- + +## Frontend — 5-Tab-Aufbau + +| Tab | Beschreibung | +|-----|-------------| +| **Uebersicht** | Alle TOMs mit Filter (Kategorie, Typ, Status, Applicability), SDM-Abdeckung, Statistiken | +| **Detail-Editor** | Einzelne TOM bearbeiten: Status, Verantwortlich, Evidence, Review-Datum | +| **Generator** | 6-Schritt-Wizard starten, Quick-Stats | +| **Gap-Analyse & Export** | Gap-Analyse-Ergebnisse, SDM/Modul-Abdeckung, JSON/DOCX Export | +| **TOM-Dokument** | Auditfaehiges HTML-Dokument (12 Sektionen), Org-Header, Revisionsmanager, PDF-Druck | + +--- + +## Kontrollbibliothek (88 Massnahmen) + +| Kategorie | Code-Prefix | Anzahl | DSGVO-Referenz | +|-----------|-------------|--------|----------------| +| Zutrittskontrolle | TOM-AC | 6 | Art. 32 Abs. 1 lit. b | +| Zugangskontrolle | TOM-ADM | 6 | Art. 32 Abs. 1 lit. b | +| Zugriffskontrolle | TOM-AZ | 7 | Art. 32 Abs. 1 lit. b | +| Weitergabekontrolle | TOM-TR | 7 | Art. 32 Abs. 1 lit. b | +| Eingabekontrolle | TOM-IN | 5 | Art. 32 Abs. 1 lit. b | +| Auftragskontrolle | TOM-OR | 6 | Art. 28 | +| Verfuegbarkeit | TOM-AV | 7 | Art. 32 Abs. 1 lit. b, c | +| Trennbarkeit | TOM-SE | 6 | Art. 32 Abs. 1 lit. b | +| Verschluesselung | TOM-ENC | 5 | Art. 32 Abs. 1 lit. a | +| Pseudonymisierung | TOM-PS | 4 | Art. 32 Abs. 1 lit. a | +| Belastbarkeit | TOM-RE | 5 | Art. 32 Abs. 1 lit. b | +| Wiederherstellbarkeit | TOM-RC | 5 | Art. 32 Abs. 1 lit. c | +| Ueberpruefung & Bewertung | TOM-RV / TOM-DL / TOM-TR | 11 | Art. 32 Abs. 1 lit. d | + +--- + +## SDM Gewaehrleistungsziele + +Das [Standard-Datenschutzmodell](https://www.datenschutz-wiki.de/SDM) definiert 7 Gewaehrleistungsziele: + +| Ziel | Relevante Kategorien | +|------|---------------------| +| Verfuegbarkeit | AVAILABILITY, RESILIENCE, RECOVERY | +| Integritaet | ADMISSION_CONTROL, TRANSFER_CONTROL, INPUT_CONTROL, ENCRYPTION, RECOVERY | +| Vertraulichkeit | ACCESS_CONTROL, ADMISSION_CONTROL, ACCESS_AUTHORIZATION, TRANSFER_CONTROL, ENCRYPTION | +| Nichtverkettung | ACCESS_AUTHORIZATION, SEPARATION, PSEUDONYMIZATION | +| Intervenierbarkeit | ORDER_CONTROL, REVIEW | +| Transparenz | INPUT_CONTROL, ORDER_CONTROL, REVIEW | +| Datenminimierung | SEPARATION, PSEUDONYMIZATION | + +!!! tip "SDM-Abdeckung" + Die Gap-Analyse (Tab 4) zeigt pro SDM-Gewaehrleistungsziel den Abdeckungsgrad + in Prozent. Ziele mit 0% Abdeckung loesen den Compliance-Check `UNCOVERED_SDM_GOAL` + (Schweregrad HIGH) aus. + +--- + +## 11 Compliance-Checks + +| # | Check | Schweregrad | Ausloeser | +|---|-------|-------------|-----------| +| 1 | `MISSING_RESPONSIBLE` | MEDIUM | REQUIRED-TOM ohne verantwortliche Person/Abteilung | +| 2 | `OVERDUE_REVIEW` | MEDIUM | TOM mit reviewDate in der Vergangenheit | +| 3 | `MISSING_EVIDENCE` | HIGH | IMPLEMENTED-TOM ohne Evidence (obwohl evidenceRequirements > 0) | +| 4 | `INCOMPLETE_CATEGORY` | HIGH | Kategorie, in der alle REQUIRED-Controls NOT_IMPLEMENTED sind | +| 5 | `NO_ENCRYPTION_MEASURES` | CRITICAL | Kein ENCRYPTION-Control implementiert | +| 6 | `NO_PSEUDONYMIZATION` | MEDIUM | Besondere Datenkategorien (Art. 9) ohne PSEUDONYMIZATION | +| 7 | `MISSING_AVAILABILITY` | HIGH | Kein AVAILABILITY/RECOVERY implementiert + kein DR-Plan | +| 8 | `NO_REVIEW_PROCESS` | MEDIUM | Kein REVIEW-Control implementiert | +| 9 | `UNCOVERED_SDM_GOAL` | HIGH | SDM-Gewaehrleistungsziel mit 0% Abdeckung | +| 10 | `HIGH_RISK_WITHOUT_MEASURES` | CRITICAL | Schutzniveau VERY_HIGH aber < 50% implementiert | +| 11 | `STALE_NOT_IMPLEMENTED` | LOW | REQUIRED-TOM seit > 90 Tagen NOT_IMPLEMENTED | + +**Score-Berechnung:** `100 - (CRITICAL*15 + HIGH*10 + MEDIUM*5 + LOW*2)`, Minimum 0. + +--- + +## Backend API (9+ Endpoints) + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| `GET` | `/api/v1/tom/state` | TOM-Generator-State laden | +| `POST` | `/api/v1/tom/state` | State speichern | +| `POST` | `/api/v1/tom/evaluate` | Controls evaluieren (Rules Engine) | +| `POST` | `/api/v1/tom/gap-analysis` | Gap-Analyse durchfuehren | +| `POST` | `/api/v1/tom/evidence/upload` | Evidence-Dokument hochladen | +| `POST` | `/api/v1/tom/evidence/analyze` | KI-Analyse eines Evidence-Dokuments | +| `POST` | `/api/v1/tom/export` | Export (JSON, DOCX, PDF, ZIP) | +| `GET` | `/api/v1/tom/controls` | Kontrollbibliothek abrufen | +| `GET` | `/api/v1/tom/controls/:id` | Einzelnen Control abrufen | + +### TOM ↔ Canonical Control Mapping API + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| `POST` | `/api/compliance/tom-mappings/sync` | Controls synchronisieren (Profil-basiert) | +| `GET` | `/api/compliance/tom-mappings` | Alle Mappings auflisten | +| `GET` | `/api/compliance/tom-mappings/by-tom/{code}` | Mappings pro TOM-Kategorie | +| `GET` | `/api/compliance/tom-mappings/stats` | Coverage-Statistiken | +| `POST` | `/api/compliance/tom-mappings/manual` | Manuelle Zuordnung | +| `DELETE` | `/api/compliance/tom-mappings/{id}` | Zuordnung entfernen | + +!!! info "Proxy-Route (Frontend)" + Das Admin-Frontend ruft die Endpoints ueber den Next.js-Proxy auf: + `/api/sdk/v1/tom/**` → `backend-compliance:8002/api/v1/tom/**` + `/api/sdk/v1/compliance/tom-mappings/**` → `backend-compliance:8002/api/compliance/tom-mappings/**` + +--- + +## TOM-Dokument (12 Sektionen) + +| # | Sektion | Inhalt | +|---|---------|--------| +| 0 | Deckblatt | Organisation, DSB, IT-Sicherheit, Version | +| — | Inhaltsverzeichnis | Automatisch generiert | +| 1 | Ziel und Zweck | Art. 32 DSGVO Rechtsrahmen | +| 2 | Geltungsbereich | Unternehmen, Hosting, Systeme, Control-Quellen | +| 3 | Grundprinzipien Art. 32 | Vertraulichkeit, Integritaet, Verfuegbarkeit, Belastbarkeit, Wirksamkeitspruefung | +| 4 | Schutzbedarf und Risikoanalyse | CIA-Bewertung, Schutzniveau, DSFA-Pflicht | +| 5 | Massnahmen-Uebersicht | Tabelle: Kategorie, Anzahl, Status-Verteilung | +| 6 | Detaillierte Massnahmen | Pro Kategorie: Detail-Karten mit Code, Status, Evidence, Mappings | +| 7 | SDM Gewaehrleistungsziele | Abdeckungstabelle (7 Ziele x Prozent) | +| 8 | Verantwortlichkeiten | Rollenmatrix | +| 9 | Pruef- und Revisionszyklus | Review-Zeitplan | +| 10 | Compliance-Status | Score, Issues nach Schweregrad | +| 11 | Aenderungshistorie | Versionstabelle | + +**Ausgabe:** HTML-Download oder PDF-Druck via Browser (`window.print()`). + +--- + +## Drei-Schichten-Architektur (TOM ↔ Canonical Controls) + +Die Audit-faehige Dokumentation nutzt eine **Drei-Schichten-Architektur**: + +```mermaid +graph TD + TOM["TOM-Massnahmen (~88, Audit-Level)"] + MAP["tom_control_mappings (Bridge-Tabelle)"] + CC["Canonical Controls (10.000+, Implementation-Level)"] + TOM --> MAP --> CC +``` + +| Schicht | Beschreibung | Beispiel | +|---------|-------------|---------| +| **TOM-Massnahmen** | 88 abstrakte, auditfaehige Massnahmen in 13 Kategorien | TOM-ENC-01: Transportverschluesselung | +| **Mapping-Bridge** | Verknuepfung TOM-Kategorie → Canonical Controls per Profil | ENCRYPTION → encryption-Kategorie, Industry-Filter | +| **Canonical Controls** | 10.000+ konkrete Security-Controls aus OWASP, NIST, ENISA | CRYP-001: Cryptographic Key Lifecycle | + +### Kategorie-Zuordnung + +| TOM-Kategorie | Canonical Kategorien | +|---------------|---------------------| +| ACCESS_CONTROL | authentication, identity, physical | +| ADMISSION_CONTROL | authentication, identity, system | +| ACCESS_AUTHORIZATION | authentication, identity | +| TRANSFER_CONTROL | network, data_protection, encryption | +| INPUT_CONTROL | application, data_protection | +| ORDER_CONTROL | supply_chain, compliance | +| AVAILABILITY | continuity, system | +| SEPARATION | network, data_protection | +| ENCRYPTION | encryption | +| PSEUDONYMIZATION | data_protection, encryption | +| RESILIENCE | continuity, system | +| RECOVERY | continuity | +| REVIEW | compliance, governance, risk | + +### Sync-Algorithmus + +1. CompanyProfile (Branche, Groesse) wird gehasht +2. Aenderung erkannt → alte auto-Mappings loeschen +3. Pro TOM-Kategorie: Canonical Controls mit passender `category`, `applicable_industries`, `applicable_company_size` und `release_state = 'approved'` suchen +4. Neue Mappings inserieren (ON CONFLICT DO NOTHING) +5. Sync-State aktualisieren + +--- + +## Cross-Modul-Integration + +| Modul | Integration | +|-------|------------| +| **DSFA** | TOM-Controls als Massnahmen in Datenschutz-Folgenabschaetzung | +| **VVT** | Verknuepfung von TOMs mit Verarbeitungstaetigkeiten | +| **Loeschfristen** | Loeschkontrollen (TOM-DL) referenzieren Loeschfristen-Policies | +| **Control Library** | Canonical Controls als belegende Security-Controls pro TOM-Kategorie | +| **Risk Assessment** | RiskProfile steuert Applicability der Controls | + +```mermaid +graph LR + Scope["Scope Engine"] -->|Profile| TOM["TOM (Art. 32)"] + CLIB["Control Library"] -->|Canonical Controls| TOM + TOM --> DSFA["DSFA (Art. 35)"] + TOM --> VVT["VVT (Art. 30)"] + TOM --> Loeschfristen["Loeschfristen"] + Risk["Risk Assessment"] -->|RiskProfile| TOM +``` + +--- + +## Audit-Faehigkeit + +Das TOM-Modul erfuellt 7 Audit-Kriterien: + +1. **Vollstaendigkeit**: 88 Controls decken alle Art. 32 Anforderungen ab +2. **Nachvollziehbarkeit**: Rules Engine dokumentiert Applicability-Gruende +3. **Aktualitaet**: Review-Zyklen + Compliance-Check OVERDUE_REVIEW +4. **Verantwortlichkeit**: Rollenmatrix im TOM-Dokument +5. **Evidence**: Evidence-Verknuepfung + Gap-Analyse +6. **Druckfaehigkeit**: Auditfaehiges HTML-Dokument mit 12 Sektionen +7. **Wirksamkeitspruefung**: 11 Compliance-Checks + Score + +--- + +## Datei-Uebersicht + +| Datei | Beschreibung | +|-------|--------------| +| `admin-compliance/app/sdk/tom/page.tsx` | Haupt-Seite mit 5 Tabs | +| `admin-compliance/components/sdk/tom-dashboard/` | Tab-Komponenten (Overview, Editor, GapExport, Document) | +| `admin-compliance/lib/sdk/tom-generator/types.ts` | Alle TypeScript-Typen | +| `admin-compliance/lib/sdk/tom-generator/controls/loader.ts` | 88 Embedded Controls | +| `admin-compliance/lib/sdk/tom-generator/rules-engine.ts` | Applicability-Auswertung | +| `admin-compliance/lib/sdk/tom-generator/context.tsx` | State Management (Reducer) | +| `admin-compliance/lib/sdk/tom-generator/sdm-mapping.ts` | SDM-Gewaehrleistungsziel-Mapping | +| `admin-compliance/lib/sdk/tom-compliance.ts` | 11 Compliance-Checks | +| `admin-compliance/lib/sdk/tom-document.ts` | HTML-Dokument-Generator | +| `backend-compliance/compliance/api/tom_mapping_routes.py` | TOM ↔ Canonical Control Mapping API (6 Endpoints) | +| `backend-compliance/migrations/068_tom_control_mappings.sql` | DB-Schema fuer Mapping-Bridge | diff --git a/mkdocs.yml b/mkdocs.yml index 015c3f3..6af017b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,7 +74,10 @@ nav: - Risiken (CP-RSK): services/sdk-modules/risks.md - Analyse-Module (Paket 2): services/sdk-modules/analyse-module.md - Dokumentations-Module (Paket 3+): services/sdk-modules/dokumentations-module.md + - VVT (Art. 30 DSGVO): services/sdk-modules/vvt.md + - Loeschfristen (Loeschkonzept): services/sdk-modules/loeschfristen.md - DSFA (Art. 35 DSGVO): services/sdk-modules/dsfa.md + - TOM (Art. 32 DSGVO): services/sdk-modules/tom.md - Rechtliche Texte (Paket 4): services/sdk-modules/rechtliche-texte.md - DSR (Betroffenenrechte): services/sdk-modules/dsr.md - E-Mail-Templates: services/sdk-modules/email-templates.md