Merge feat/zeroclaw-compliance-agent into main

Brings all compliance doc-check features:
- 162 regex checks + 1874 Master Controls
- LLM-agnostic agent with tool calling
- Banner check (46 checks, 30 CMPs, stealth, Shadow DOM)
- Impressum check (24 checks)
- Deep consent verification (DataLayer, GCM, TCF)
- CMP E2E tests (39 tests)
- HTML email reports, FAQ, persistent history

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-11 11:44:20 +02:00
175 changed files with 20063 additions and 1283 deletions
@@ -0,0 +1,331 @@
import type { CompanyProfilePreset } from './company-profile-presets'
export const COMPANY_PROFILE_PRESETS: CompanyProfilePreset[] = [
{
id: 'saas_startup',
label: 'SaaS Startup',
description: 'B2B Software-Startup, 1-5 Mitarbeiter, Cloud-basiert, remote-first',
icon: '\u{1F680}',
profile: {
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
companySize: 'micro', employeeCount: '1-9', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
},
scopeHints: {
org_employee_count: '1-9', org_industry: 'tech', org_business_model: 'b2b',
proc_ai_usage: 'yes', tech_hosting_location: 'eu',
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
comp_documentation_level: 'basic',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi',
],
},
{
id: 'consumer_app',
label: 'App Startup (Consumer)',
description: 'B2C Mobile App, 1-5 Mitarbeiter, App Store, Nutzerdaten',
icon: '\u{1F4F1}',
profile: {
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2c',
companySize: 'micro', employeeCount: '1-9', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9', org_industry: 'tech', org_business_model: 'b2c',
data_volume: '1000-10000', proc_tracking: 'yes',
prod_consent_management: 'yes', tech_hosting_location: 'eu',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'terms_of_use', 'cookie_policy', 'cookie_banner',
'community_guidelines', 'acceptable_use', 'widerruf',
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi', 'social_media_dsi',
],
},
{
id: 'ecommerce',
label: 'E-Commerce / Online-Shop',
description: 'Online-Handel B2C, 5-20 Mitarbeiter, Webshop, Zahlungsabwicklung',
icon: '\u{1F6D2}',
profile: {
legalForm: 'GmbH', industry: ['retail'], businessModel: 'b2c',
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '10-49', org_industry: 'retail', org_business_model: 'b2c',
prod_webshop: 'yes', data_volume: '10000-100000',
tech_hosting_location: 'eu', prod_consent_management: 'yes',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'widerruf', 'cookie_policy', 'cookie_banner',
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi',
],
},
{
id: 'it_agency',
label: 'IT-Dienstleister / Agentur',
description: 'IT-Beratung oder Agentur, 10-50 Mitarbeiter, Kundenprojekte',
icon: '\u{1F4BB}',
profile: {
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
},
scopeHints: {
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b',
proc_ai_usage: 'yes', tech_hosting_location: 'eu',
comp_vendor_management: 'yes', comp_training: 'yes',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'sla', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi',
],
},
{
id: 'maschinenbau',
label: 'Maschinenbau KMU',
description: 'Maschinenbau B2B, 50-200 Mitarbeiter, Produktion, CE-Kennzeichnung',
icon: '\u{1F3ED}',
profile: {
legalForm: 'GmbH', industry: ['manufacturing'], businessModel: 'b2b',
companySize: 'medium', employeeCount: '50-249', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '50-249', org_industry: 'manufacturing', org_business_model: 'b2b',
proc_employee_monitoring: 'no', tech_hosting_location: 'eu',
comp_vendor_management: 'yes', comp_documentation_level: 'structured',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi', 'whistleblower_policy',
'dsfa', 'pflichtenregister',
],
},
{
id: 'law_firm',
label: 'Rechtsanwaltskanzlei',
description: 'Kanzlei, 5-20 Mitarbeiter, Mandantendaten, besondere Vertraulichkeit',
icon: '\u2696\uFE0F',
profile: {
legalForm: 'PartG', industry: ['legal'], businessModel: 'b2b',
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9', org_industry: 'legal', org_business_model: 'b2b',
data_art9: 'no', tech_encryption_transit: 'yes',
tech_encryption_rest: 'yes', comp_documentation_level: 'basic',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'tom_documentation', 'vvt_register', 'loeschkonzept',
'employee_dsi', 'applicant_dsi',
],
},
{
id: 'healthcare',
label: 'Arztpraxis / Gesundheit',
description: 'Gesundheitswesen, 5-50 Mitarbeiter, Patientendaten (Art. 9), hoher Schutzbedarf',
icon: '\u{1F3E5}',
profile: {
legalForm: 'GbR', industry: ['healthcare'], businessModel: 'b2c',
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9', org_industry: 'healthcare', org_business_model: 'b2c',
data_art9: 'yes', tech_encryption_transit: 'yes',
tech_encryption_rest: 'yes', comp_documentation_level: 'basic',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
'employee_dsi', 'applicant_dsi', 'pflichtenregister',
],
},
{
id: 'handwerk',
label: 'Handwerksbetrieb',
description: 'Handwerk, 5-20 Mitarbeiter, Kundendaten, einfache IT',
icon: '\u{1F527}',
profile: {
legalForm: 'GmbH', industry: ['crafts'], businessModel: 'b2c',
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9', org_industry: 'other', org_business_model: 'b2c',
data_art9: 'no', tech_hosting_location: 'eu', comp_documentation_level: 'none',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'tom_documentation', 'vvt_register', 'loeschkonzept', 'employee_dsi',
],
},
{
id: 'education',
label: 'Bildungseinrichtung',
description: 'Schule, Hochschule oder Weiterbildung, 20-100 Mitarbeiter, Schuelerdaten',
icon: '\u{1F393}',
profile: {
legalForm: 'gGmbH', industry: ['education'], businessModel: 'b2c',
companySize: 'medium', employeeCount: '10-49', headquartersCountry: 'DE',
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '10-49', org_industry: 'education', org_business_model: 'b2c',
data_minors: 'yes', tech_hosting_location: 'eu', comp_training: 'yes',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
'employee_dsi', 'applicant_dsi', 'pflichtenregister',
],
},
{
id: 'enterprise',
label: 'Konzern / Enterprise',
description: 'Grossunternehmen, 500+ MA, international, reguliert, ISO 27001',
icon: '\u{1F3E2}',
profile: {
legalForm: 'AG', industry: ['finance'], businessModel: 'b2b',
companySize: 'enterprise', employeeCount: '1000+', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU', 'US'], isDataController: true, isDataProcessor: true,
},
scopeHints: {
org_employee_count: '1000+', org_industry: 'finance', org_business_model: 'b2b',
org_cert_target: 'iso27001', data_art9: 'yes', data_volume: '>1000000',
proc_ai_usage: 'yes', tech_third_country: 'yes',
tech_hosting_location: 'eu_us_adequacy', comp_vendor_management: 'yes',
comp_training: 'yes', comp_documentation_level: 'comprehensive',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'sla', 'cloud_service_agreement',
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa', 'pflichtenregister',
'data_protection_concept', 'consent_texts', 'informationspflichten', 'verpflichtungserklaerung',
'dsr_process_art15', 'dsr_process_art16', 'dsr_process_art17',
'dsr_process_art18', 'dsr_process_art20', 'dsr_process_art21',
'isms_manual', 'it_security_concept', 'risk_management_concept',
'information_security_policy', 'access_control_policy', 'encryption_policy',
'change_management_policy', 'asset_management_policy',
'data_protection_policy', 'data_classification_policy',
'data_retention_policy', 'data_transfer_policy', 'privacy_incident_policy',
'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'social_media_dsi',
'employee_security_policy', 'security_awareness_policy', 'offboarding_policy',
'transfer_impact_assessment', 'scc_companion',
'vendor_risk_management_policy', 'third_party_security_policy',
'business_continuity_policy', 'disaster_recovery_policy', 'crisis_management_policy',
'ai_usage_policy', 'standard_operating_procedure',
],
},
{
id: 'cloud_provider',
label: 'Cloud / SaaS-Anbieter',
description: 'Cloud-Infrastruktur oder SaaS, 20-100 MA, DevOps, ISO 27001 Ziel',
icon: '\u2601\uFE0F',
profile: {
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
},
scopeHints: {
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b',
org_cert_iso27001: 'yes', proc_ai_usage: 'yes', tech_hosting_location: 'eu',
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
comp_vendor_management: 'yes', comp_documentation_level: 'structured',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'sla', 'cloud_service_agreement',
'tom_documentation', 'vvt_register', 'loeschkonzept', 'pflichtenregister',
'data_protection_concept', 'consent_texts',
'isms_manual', 'it_security_concept', 'backup_recovery_concept',
'logging_concept', 'incident_response_plan',
'access_control_concept', 'risk_management_concept',
'information_security_policy', 'access_control_policy', 'password_policy',
'encryption_policy', 'logging_policy', 'backup_policy',
'incident_response_policy', 'change_management_policy',
'patch_management_policy', 'asset_management_policy',
'cloud_security_policy', 'devsecops_policy',
'secrets_management_policy', 'vulnerability_management_policy',
'employee_dsi', 'applicant_dsi', 'employee_security_policy',
'remote_work_policy', 'offboarding_policy',
'vendor_risk_management_policy', 'third_party_security_policy',
'business_continuity_policy', 'disaster_recovery_policy',
'ai_usage_policy', 'cybersecurity_policy', 'byod_policy',
'standard_operating_procedure',
],
},
{
id: 'fintech',
label: 'Finanzdienstleister',
description: 'Finanz- oder Versicherungsbranche, 50-500 MA, reguliert',
icon: '\u{1F3E6}',
profile: {
legalForm: 'GmbH', industry: ['finance'], businessModel: 'b2b',
companySize: 'medium', employeeCount: '50-249', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
},
scopeHints: {
org_employee_count: '50-249', org_industry: 'finance', org_business_model: 'b2b',
data_art9: 'no', data_volume: '100000-1000000', tech_hosting_location: 'eu',
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
comp_vendor_management: 'yes', comp_training: 'yes',
comp_documentation_level: 'comprehensive',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
'dpa', 'nda', 'sla',
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa', 'pflichtenregister',
'data_protection_concept', 'verpflichtungserklaerung', 'informationspflichten',
'dsr_process_art15', 'dsr_process_art17', 'dsr_process_art20',
'data_protection_policy', 'data_classification_policy',
'data_retention_policy', 'data_transfer_policy', 'privacy_incident_policy',
'it_security_concept', 'risk_management_concept',
'information_security_policy', 'access_control_policy', 'encryption_policy',
'employee_dsi', 'applicant_dsi', 'whistleblower_policy',
'employee_security_policy', 'security_awareness_policy', 'offboarding_policy',
'transfer_impact_assessment', 'vendor_risk_management_policy',
'supplier_security_policy',
'business_continuity_policy', 'disaster_recovery_policy', 'crisis_management_policy',
'standard_operating_procedure',
],
},
{
id: 'platform',
label: 'Plattform / Marketplace',
description: 'Online-Plattform mit Nutzern, UGC, Community, 10-50 MA',
icon: '\u{1F310}',
profile: {
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b2c',
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
},
scopeHints: {
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b2c',
data_volume: '10000-100000', proc_tracking: 'yes',
prod_ugc_platform: 'yes', prod_consent_management: 'yes',
tech_hosting_location: 'eu',
},
recommendedDocs: [
'privacy_policy', 'impressum', 'terms_of_use', 'agb',
'cookie_policy', 'cookie_banner', 'dpa',
'community_guidelines', 'acceptable_use',
'media_content_policy', 'copyright_policy', 'data_usage_clause',
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
'consent_texts', 'social_media_dsi', 'video_conference_dsi',
'dsr_process_art15', 'dsr_process_art17', 'dsr_process_art20', 'dsr_process_art21',
'employee_dsi', 'applicant_dsi',
'ai_usage_policy',
],
},
]
@@ -0,0 +1,33 @@
/**
* Company Profile Presets — Branchenvorlagen fuer typische Kundenszenarien
*
* Jeder Preset enthaelt ein vorbefuelltes CompanyProfile + typische Scope-Antworten.
* Der Kunde waehlt beim Onboarding ein Profil und passt es dann an.
*
* Data split: Interface here, preset data in ./company-profile-preset-data.ts
*/
export interface CompanyProfilePreset {
id: string
label: string
description: string
icon: string
/** Vorbefuellte CompanyProfile-Felder */
profile: {
legalForm: string
industry: string[]
businessModel: string
companySize: string
employeeCount: string
headquartersCountry: string
targetMarkets: string[]
isDataController: boolean
isDataProcessor: boolean
}
/** Typische Scope-Antworten fuer diese Branche */
scopeHints: Record<string, string>
/** Typische Dokumente die diese Branche braucht */
recommendedDocs: string[]
}
export { COMPANY_PROFILE_PRESETS } from './company-profile-preset-data'
@@ -52,6 +52,15 @@ export const QUESTION_SCORE_WEIGHTS: Record<
comp_training: { risk: 5, complexity: 4, assurance: 7 },
comp_vendor_management: { risk: 6, complexity: 6, assurance: 7 },
comp_documentation_level: { risk: 6, complexity: 7, assurance: 8 },
// Zusaetzliche Fragen fuer Template-Empfehlungen (7 Fragen)
org_has_employees: { risk: 2, complexity: 3, assurance: 3 },
org_has_social_media: { risk: 3, complexity: 2, assurance: 3 },
org_has_video_conferencing: { risk: 2, complexity: 2, assurance: 2 },
proc_uses_ai_tools: { risk: 7, complexity: 6, assurance: 7 },
proc_byod_allowed: { risk: 5, complexity: 4, assurance: 5 },
prod_ugc_platform: { risk: 6, complexity: 5, assurance: 6 },
org_cert_iso27001: { risk: 2, complexity: 8, assurance: 9 },
}
// ============================================================================
@@ -0,0 +1,113 @@
/**
* DSFA Bundesland-Blacklists — Verarbeitungen die IMMER eine DSFA erfordern.
*
* Jede Aufsichtsbehoerde fuehrt eine eigene Positiv-/Negativliste
* gemaess Art. 35 Abs. 4 DSGVO. Diese Listen definieren Verarbeitungen
* die UNABHAENGIG von der Schwellwertanalyse eine DSFA erfordern.
*
* Quellen: Offizielle Muss-Listen der LfDI/BfDI (oeffentlich zugaenglich).
* KEIN Normtext — eigene Zusammenfassung der Kriterien.
*/
export interface BlacklistEntry {
id: string
description: string
triggerKeywords: string[]
}
export interface BundeslandBlacklist {
state: string
stateCode: string
authority: string
authorityUrl: string
entries: BlacklistEntry[]
}
// Gemeinsame Kriterien die in ALLEN Bundeslaendern gelten (DSK-Liste)
const DSK_COMMON: BlacklistEntry[] = [
{ id: 'dsk-01', description: 'Umfangreiche Verarbeitung besonderer Kategorien (Art. 9)', triggerKeywords: ['art9', 'gesundheit', 'biometrie', 'genetik', 'religion', 'gewerkschaft'] },
{ id: 'dsk-02', description: 'Systematische umfangreiche Ueberwachung oeffentlicher Bereiche', triggerKeywords: ['videoueberwachung', 'kamera', 'oeffentlich', 'ueberwachung'] },
{ id: 'dsk-03', description: 'Scoring/Profiling mit rechtlicher Wirkung', triggerKeywords: ['scoring', 'profiling', 'bonitaet', 'kredit', 'automatisiert'] },
{ id: 'dsk-04', description: 'Verarbeitung von Daten Minderjaehriger fuer Marketing/Profiling', triggerKeywords: ['minderjaehrig', 'kinder', 'schueler', 'marketing'] },
{ id: 'dsk-05', description: 'Zusammenfuehrung von Daten aus verschiedenen Quellen', triggerKeywords: ['zusammenfuehrung', 'matching', 'datenfusion', 'big data'] },
{ id: 'dsk-06', description: 'Einsatz neuer Technologien (KI, Biometrie, IoT)', triggerKeywords: ['ki', 'kuenstliche intelligenz', 'biometrie', 'iot', 'gesichtserkennung'] },
{ id: 'dsk-07', description: 'Umfangreiche Verarbeitung von Standortdaten', triggerKeywords: ['standort', 'gps', 'tracking', 'bewegungsprofil'] },
{ id: 'dsk-08', description: 'Verarbeitung von Beschaeftigtendaten mit Ueberwachungscharakter', triggerKeywords: ['mitarbeiterueberwachung', 'keylogger', 'bildschirmaufnahme', 'leistungskontrolle'] },
{ id: 'dsk-09', description: 'Anonymisierung besonderer Kategorien', triggerKeywords: ['anonymisierung', 'art9', 'pseudonymisierung'] },
{ id: 'dsk-10', description: 'Verarbeitung von Kommunikationsinhalten oder -metadaten', triggerKeywords: ['kommunikation', 'email', 'telefon', 'metadaten', 'inhaltsdaten'] },
]
// Bundesland-spezifische Ergaenzungen
const BAYERN_EXTRA: BlacklistEntry[] = [
{ id: 'by-01', description: 'Betrieb von Whistleblower-Systemen mit Identifizierungsrisiko', triggerKeywords: ['whistleblower', 'hinweisgeber', 'meldesystem'] },
{ id: 'by-02', description: 'Einsatz von Drohnen mit Kamera in oeffentlichen Bereichen', triggerKeywords: ['drohne', 'uav', 'luftaufnahme'] },
]
const NRW_EXTRA: BlacklistEntry[] = [
{ id: 'nw-01', description: 'Social-Media-Monitoring von Mitarbeitern oder Bewerbern', triggerKeywords: ['social media', 'monitoring', 'bewerber', 'hintergrundcheck'] },
]
const BERLIN_EXTRA: BlacklistEntry[] = [
{ id: 'be-01', description: 'Automatisierte Mieterbonitaetspruefung', triggerKeywords: ['mieter', 'bonitaet', 'wohnung', 'schufa'] },
]
export const BUNDESLAND_BLACKLISTS: Record<string, BundeslandBlacklist> = {
BW: { state: 'Baden-Wuerttemberg', stateCode: 'BW', authority: 'LfDI BW', authorityUrl: 'https://www.baden-wuerttemberg.datenschutz.de', entries: [...DSK_COMMON] },
BY: { state: 'Bayern', stateCode: 'BY', authority: 'BayLDA', authorityUrl: 'https://www.lda.bayern.de', entries: [...DSK_COMMON, ...BAYERN_EXTRA] },
BE: { state: 'Berlin', stateCode: 'BE', authority: 'BlnBDI', authorityUrl: 'https://www.datenschutz-berlin.de', entries: [...DSK_COMMON, ...BERLIN_EXTRA] },
BB: { state: 'Brandenburg', stateCode: 'BB', authority: 'LDA BB', authorityUrl: 'https://www.lda.brandenburg.de', entries: [...DSK_COMMON] },
HB: { state: 'Bremen', stateCode: 'HB', authority: 'LfDI HB', authorityUrl: 'https://www.datenschutz.bremen.de', entries: [...DSK_COMMON] },
HH: { state: 'Hamburg', stateCode: 'HH', authority: 'HmbBfDI', authorityUrl: 'https://datenschutz-hamburg.de', entries: [...DSK_COMMON] },
HE: { state: 'Hessen', stateCode: 'HE', authority: 'HBDI', authorityUrl: 'https://datenschutz.hessen.de', entries: [...DSK_COMMON] },
MV: { state: 'Mecklenburg-Vorpommern', stateCode: 'MV', authority: 'LfDI MV', authorityUrl: 'https://www.datenschutz-mv.de', entries: [...DSK_COMMON] },
NI: { state: 'Niedersachsen', stateCode: 'NI', authority: 'LfD NI', authorityUrl: 'https://lfd.niedersachsen.de', entries: [...DSK_COMMON] },
NW: { state: 'Nordrhein-Westfalen', stateCode: 'NW', authority: 'LDI NRW', authorityUrl: 'https://www.ldi.nrw.de', entries: [...DSK_COMMON, ...NRW_EXTRA] },
RP: { state: 'Rheinland-Pfalz', stateCode: 'RP', authority: 'LfDI RP', authorityUrl: 'https://www.datenschutz.rlp.de', entries: [...DSK_COMMON] },
SL: { state: 'Saarland', stateCode: 'SL', authority: 'UDZ Saarland', authorityUrl: 'https://www.datenschutz.saarland.de', entries: [...DSK_COMMON] },
SN: { state: 'Sachsen', stateCode: 'SN', authority: 'SDB', authorityUrl: 'https://www.saechsdsb.de', entries: [...DSK_COMMON] },
ST: { state: 'Sachsen-Anhalt', stateCode: 'ST', authority: 'LfD LSA', authorityUrl: 'https://datenschutz.sachsen-anhalt.de', entries: [...DSK_COMMON] },
SH: { state: 'Schleswig-Holstein', stateCode: 'SH', authority: 'ULD SH', authorityUrl: 'https://www.datenschutzzentrum.de', entries: [...DSK_COMMON] },
TH: { state: 'Thueringen', stateCode: 'TH', authority: 'TLfDI', authorityUrl: 'https://www.tlfdi.de', entries: [...DSK_COMMON] },
}
/**
* Check scope answers against Bundesland blacklist.
* Returns matching entries that REQUIRE a DSFA.
*/
export function checkBlacklist(
stateCode: string,
scopeKeywords: string[],
): { matches: BlacklistEntry[]; authority: string; authorityUrl: string } {
const bl = BUNDESLAND_BLACKLISTS[stateCode]
if (!bl) return { matches: [], authority: 'BfDI', authorityUrl: 'https://www.bfdi.bund.de' }
const lowerKeywords = scopeKeywords.map(k => k.toLowerCase())
const matches = bl.entries.filter(entry =>
entry.triggerKeywords.some(tk => lowerKeywords.some(kw => kw.includes(tk) || tk.includes(kw)))
)
return { matches, authority: bl.authority, authorityUrl: bl.authorityUrl }
}
/**
* Derive keywords from scope answers for blacklist matching.
*/
export function scopeAnswersToKeywords(answers: Array<{ questionId: string; value: unknown }>): string[] {
const keywords: string[] = []
for (const a of answers) {
if (a.value === true || a.value === 'yes') {
keywords.push(a.questionId.replace(/_/g, ' '))
// Map specific question IDs to keywords
if (a.questionId === 'data_art9') keywords.push('art9', 'besondere kategorien')
if (a.questionId === 'data_minors') keywords.push('minderjaehrig', 'kinder')
if (a.questionId === 'proc_adm_scoring') keywords.push('scoring', 'profiling', 'automatisiert')
if (a.questionId === 'proc_video_surveillance') keywords.push('videoueberwachung', 'kamera')
if (a.questionId === 'proc_ai_usage') keywords.push('ki', 'kuenstliche intelligenz')
if (a.questionId === 'proc_employee_monitoring') keywords.push('mitarbeiterueberwachung')
}
if (Array.isArray(a.value)) {
for (const v of a.value) keywords.push(String(v).toLowerCase())
}
}
return keywords
}
@@ -0,0 +1,224 @@
/**
* DSFA Pre-Fill — derives initial DSFA data from Company Profile + Scope answers.
*
* Maps: Firmensitz → Bundesland, Scope-Antworten → Datenkategorien/Risiken,
* Use Cases → Verarbeitungstaetigkeiten, DPO → Beratungsinformationen.
*/
import type { ScopeProfilingAnswer } from '@/lib/sdk/compliance-scope-types'
interface CompanyProfileMinimal {
headquartersState?: string
industry?: string[]
businessModel?: string
dpoName?: string | null
dpoEmail?: string | null
companyName?: string
}
export interface DSFAPrefillResult {
title: string
description: string
processingActivity: string
dataCategories: string[]
dataSubjects: string[]
riskLevel: string
measures: string[]
federalState: string
involvesAi: boolean
legalBasis: string
processingPurpose: string
affectedRights: string[]
}
const ART9_CATEGORY_MAP: Record<string, string> = {
health: 'Gesundheitsdaten',
biometric: 'Biometrische Daten',
genetic: 'Genetische Daten',
ethnic: 'Ethnische Herkunft',
political: 'Politische Meinungen',
religious: 'Religioese Ueberzeugungen',
union: 'Gewerkschaftszugehoerigkeit',
sexual: 'Sexualleben/Orientierung',
}
const BUNDESLAND_LABELS: Record<string, string> = {
BW: 'Baden-Wuerttemberg', BY: 'Bayern', BE: 'Berlin', BB: 'Brandenburg',
HB: 'Bremen', HH: 'Hamburg', HE: 'Hessen', MV: 'Mecklenburg-Vorpommern',
NI: 'Niedersachsen', NW: 'Nordrhein-Westfalen', RP: 'Rheinland-Pfalz',
SL: 'Saarland', SN: 'Sachsen', ST: 'Sachsen-Anhalt',
SH: 'Schleswig-Holstein', TH: 'Thueringen',
}
function getAnswer(answers: ScopeProfilingAnswer[], questionId: string): string | string[] | boolean | undefined {
const a = answers.find(a => a.questionId === questionId)
return a?.value as string | string[] | boolean | undefined
}
export function prefillDSFAFromScope(
profile: CompanyProfileMinimal | null,
scopeAnswers: ScopeProfilingAnswer[],
): DSFAPrefillResult {
const result: DSFAPrefillResult = {
title: '',
description: '',
processingActivity: '',
dataCategories: [],
dataSubjects: [],
riskLevel: 'mittel',
measures: ['Zugriffskontrolle', 'Verschluesselung'],
federalState: '',
involvesAi: false,
legalBasis: '',
processingPurpose: '',
affectedRights: [],
}
// 1. Firmensitz → Bundesland
if (profile?.headquartersState) {
result.federalState = profile.headquartersState
}
// 2. Art. 9 Daten → Datenkategorien + Risikostufe
const art9 = getAnswer(scopeAnswers, 'data_art9')
if (art9 === true || art9 === 'yes') {
result.dataCategories.push('Besondere Kategorien (Art. 9)')
result.riskLevel = 'hoch'
result.title = 'DSFA — Verarbeitung besonderer Datenkategorien'
}
if (Array.isArray(art9)) {
for (const cat of art9) {
const label = ART9_CATEGORY_MAP[cat]
if (label) result.dataCategories.push(label)
}
if (art9.length > 0) result.riskLevel = 'hoch'
}
// 3. Minderjährige → Betroffene + Risiko
const minors = getAnswer(scopeAnswers, 'data_minors')
if (minors === true || minors === 'yes') {
result.dataSubjects.push('Minderjaehrige (unter 16 Jahre)')
result.riskLevel = 'hoch'
result.affectedRights.push('Besonderer Schutz Minderjaehriger (Art. 8 DSGVO)')
if (!result.title) result.title = 'DSFA — Verarbeitung von Daten Minderjaehriger'
}
// 4. Automatisierte Entscheidungen (Scoring)
const scoring = getAnswer(scopeAnswers, 'proc_adm_scoring')
if (scoring === true || scoring === 'yes') {
result.affectedRights.push('Recht auf nicht-automatisierte Entscheidung (Art. 22 DSGVO)')
result.riskLevel = 'hoch'
if (!result.title) result.title = 'DSFA — Automatisierte Einzelentscheidungen'
result.measures.push('Menschliche Pruefung')
}
// 5. KI-Einsatz
const aiUsage = getAnswer(scopeAnswers, 'proc_ai_usage')
if (aiUsage === true || aiUsage === 'yes' || (Array.isArray(aiUsage) && aiUsage.length > 0)) {
result.involvesAi = true
result.measures.push('KI-Transparenz', 'Human Oversight')
if (!result.title) result.title = 'DSFA — KI-gestuetzte Datenverarbeitung'
}
// 6. Videoueberwachung
const video = getAnswer(scopeAnswers, 'proc_video_surveillance')
if (video === true || video === 'yes') {
result.dataCategories.push('Videoaufnahmen / Bilddaten')
result.dataSubjects.push('Besucher', 'Mitarbeiter')
if (!result.title) result.title = 'DSFA — Videoueberwachung'
}
// 7. Datenvolumen
const volume = getAnswer(scopeAnswers, 'data_volume')
if (volume === '100000-1000000' || volume === '>1000000') {
result.riskLevel = 'hoch'
result.description += 'Grosse Datenmengen erhoehen das Risiko fuer Betroffene. '
}
// 8. Branche + Geschaeftsmodell → Verarbeitungszweck
if (profile?.industry?.length) {
const ind = profile.industry[0]
const purposeMap: Record<string, string> = {
healthcare: 'Patientenversorgung und Gesundheitsdatenverarbeitung',
finance: 'Finanzdienstleistungen und Bonitaetspruefung',
education: 'Bildungsverwaltung und Schuelerbetreuung',
tech: 'Software-Entwicklung und Cloud-Dienste',
retail: 'Handel und Kundenbeziehungsmanagement',
legal: 'Mandatsbearbeitung und Rechtsberatung',
}
result.processingPurpose = purposeMap[ind] || ''
result.processingActivity = purposeMap[ind] || ''
}
if (profile?.businessModel === 'b2c') {
result.dataSubjects.push('Endverbraucher')
result.legalBasis = 'Art. 6 Abs. 1 lit. b DSGVO (Vertragserfuellung)'
} else if (profile?.businessModel === 'b2b') {
result.dataSubjects.push('Geschaeftskunden', 'Ansprechpartner')
result.legalBasis = 'Art. 6 Abs. 1 lit. f DSGVO (Berechtigtes Interesse)'
}
// Deduplicate
result.dataCategories = [...new Set(result.dataCategories)]
result.dataSubjects = [...new Set(result.dataSubjects)]
result.measures = [...new Set(result.measures)]
result.affectedRights = [...new Set(result.affectedRights)]
// Default title if nothing triggered
if (!result.title) {
result.title = `DSFA — ${profile?.companyName || 'Datenverarbeitung'}`
}
return result
}
/**
* Check if DSFA is required based on scope answers (Art. 35 Abs. 3 triggers)
* AND Bundesland-specific blacklist (Art. 35 Abs. 4).
*/
export function isDSFARequired(
scopeAnswers: ScopeProfilingAnswer[],
headquartersState?: string,
): {
required: boolean
triggers: string[]
blacklistMatches: string[]
authority?: string
} {
const triggers: string[] = []
if (getAnswer(scopeAnswers, 'data_art9') === true || getAnswer(scopeAnswers, 'data_art9') === 'yes') {
triggers.push('Besondere Datenkategorien (Art. 9 DSGVO)')
}
if (getAnswer(scopeAnswers, 'data_minors') === true || getAnswer(scopeAnswers, 'data_minors') === 'yes') {
triggers.push('Daten Minderjaehriger (Art. 8 DSGVO)')
}
if (getAnswer(scopeAnswers, 'proc_adm_scoring') === true || getAnswer(scopeAnswers, 'proc_adm_scoring') === 'yes') {
triggers.push('Automatisierte Einzelentscheidungen (Art. 22 DSGVO)')
}
if (getAnswer(scopeAnswers, 'proc_video_surveillance') === true || getAnswer(scopeAnswers, 'proc_video_surveillance') === 'yes') {
triggers.push('Systematische Ueberwachung (Art. 35 Abs. 3 lit. c)')
}
const vol = getAnswer(scopeAnswers, 'data_volume')
if (vol === '100000-1000000' || vol === '>1000000') {
triggers.push('Umfangreiche Datenverarbeitung (Art. 35 Abs. 3 lit. b)')
}
// Bundesland-Blacklist (Art. 35 Abs. 4)
let blacklistMatches: string[] = []
let authority: string | undefined
if (headquartersState) {
const { checkBlacklist, scopeAnswersToKeywords } = require('./bundesland-blacklists')
const keywords = scopeAnswersToKeywords(scopeAnswers)
const result = checkBlacklist(headquartersState, keywords)
blacklistMatches = result.matches.map((m: { description: string }) => m.description)
authority = result.authority
}
return {
required: triggers.length > 0 || blacklistMatches.length > 0,
triggers,
blacklistMatches,
authority,
}
}
@@ -245,13 +245,16 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
const categoriesHTML = config.categories
.map((cat) => {
const isRequired = cat.isRequired
// COMPLIANCE: Only "required" categories may be pre-enabled (EuGH Planet49)
// Non-required categories must NEVER be defaultEnabled
const isEnabled = isRequired ? true : false
return `
<div class="cookie-banner-category" data-category="${cat.id}">
<div class="cookie-banner-category-info">
<div class="cookie-banner-category-name">${cat.name.de}</div>
<div class="cookie-banner-category-desc">${cat.description.de}</div>
</div>
<div class="cookie-banner-toggle ${cat.defaultEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
<div class="cookie-banner-toggle ${isEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
data-category="${cat.id}"
data-required="${isRequired}"></div>
</div>
@@ -286,10 +289,22 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
</div>
</div>
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
${config.texts.privacyPolicyLink.de}
</a>
<div class="cookie-banner-links">
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
${config.texts.privacyPolicyLink.de}
</a>
<a href="${config.impressumUrl || '/impressum'}" class="cookie-banner-link" target="_blank">
Impressum
</a>
</div>
</div>
<!-- Cookie Settings Re-Open (§7(3) DSGVO — Widerruf so einfach wie Einwilligung) -->
<a href="#" id="cookieBannerReopen" class="cookie-settings-footer-link"
onclick="document.getElementById('cookieBanner').style.display='block';document.getElementById('cookieBannerOverlay').classList.add('active');return false;"
style="position:fixed;bottom:8px;left:8px;z-index:9990;font-size:11px;color:#6b7280;text-decoration:none;background:rgba(255,255,255,0.9);padding:4px 8px;border-radius:4px;border:1px solid #e5e7eb;">
Cookie-Einstellungen
</a>
`.trim()
}
@@ -310,6 +325,29 @@ function generateJS(config: CookieBannerConfig): string {
const CATEGORIES = ${JSON.stringify(categoryIds)};
const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)};
// Google Consent Mode v2 — PFLICHT seit Maerz 2024 fuer Google Services in EEA
// Sets default consent state to "denied" BEFORE any Google tags fire
if (typeof gtag === 'function') {
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
functionality_storage: 'granted',
security_storage: 'granted',
});
}
function updateGoogleConsentMode(consent) {
if (typeof gtag !== 'function') return;
gtag('consent', 'update', {
analytics_storage: consent.statistics ? 'granted' : 'denied',
ad_storage: consent.marketing ? 'granted' : 'denied',
ad_user_data: consent.marketing ? 'granted' : 'denied',
ad_personalization: consent.marketing ? 'granted' : 'denied',
});
}
function getConsent() {
const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '='));
if (!cookie) return null;
@@ -327,6 +365,7 @@ function generateJS(config: CookieBannerConfig): string {
';expires=' + date.toUTCString() +
';path=/;SameSite=Lax';
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent }));
updateGoogleConsentMode(consent);
}
function hasConsent(category) {
@@ -397,6 +436,31 @@ function generateJS(config: CookieBannerConfig): string {
overlay?.classList.remove('active');
}
// Script-Blocking: activate scripts with data-cookie-category ONLY after consent
function activateConsentedScripts() {
const consent = getConsent();
if (!consent) return;
// Find all blocked scripts (type="text/plain" with data-cookie-category)
document.querySelectorAll('script[data-cookie-category][type="text/plain"]').forEach(script => {
const category = script.getAttribute('data-cookie-category');
if (consent[category] === true) {
// Replace type to activate the script
const newScript = document.createElement('script');
if (script.src) newScript.src = script.src;
else newScript.textContent = script.textContent;
newScript.type = 'text/javascript';
script.parentNode.replaceChild(newScript, script);
}
});
// Also fire custom event for programmatic listeners
window.dispatchEvent(new CustomEvent('cookieConsentActivated', { detail: consent }));
}
// Run script activation after consent is saved
window.addEventListener('cookieConsentUpdated', activateConsentedScripts);
window.CookieConsent = {
getConsent,
saveConsent,
@@ -405,14 +469,32 @@ function generateJS(config: CookieBannerConfig): string {
document.getElementById('cookieBanner')?.classList.add('active');
document.getElementById('cookieBannerOverlay')?.classList.add('active');
},
hide: closeBanner
hide: closeBanner,
activateScripts: activateConsentedScripts,
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBanner);
document.addEventListener('DOMContentLoaded', () => {
initBanner();
activateConsentedScripts();
});
} else {
initBanner();
activateConsentedScripts();
}
})();
/*
* USAGE: Script-Blocking
*
* Instead of:
* <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
*
* Use:
* <script type="text/plain" data-cookie-category="statistics"
* src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
*
* The script will only execute AFTER the user consents to "statistics".
*/
`.trim()
}
@@ -328,7 +328,8 @@ export function isPolicyOverdue(policy: LoeschfristPolicy): boolean {
}
export function getActiveLegalHolds(policy: LoeschfristPolicy): LegalHold[] {
return policy.legalHolds.filter(h => h.status === 'ACTIVE')
const holds = Array.isArray(policy.legalHolds) ? policy.legalHolds : []
return holds.filter(h => h.status === 'ACTIVE')
}
export function getEffectiveDeletionTrigger(policy: LoeschfristPolicy): DeletionTriggerLevel {
@@ -85,6 +85,8 @@ export type TemplateType =
| 'tom_documentation'
| 'loeschkonzept'
| 'pflichtenregister'
// SOP (Migration 112)
| 'standard_operating_procedure'
export type Jurisdiction = 'DE' | 'AT' | 'CH' | 'EU' | 'US' | 'INTL'
+54 -66
View File
@@ -57,6 +57,19 @@ export const SDK_STEPS: SDKStep[] = [
isOptional: true,
visibleWhen: (state) => state.customerType === 'existing',
},
{
id: 'ai-act',
seq: 350,
phase: 1,
package: 'vorbereitung',
order: 4,
name: 'AI Act Klassifizierung',
nameShort: 'AI Act',
description: 'KI-Risikostufe (nur bei KI-Einsatz)',
url: '/sdk/ai-act',
checkpointId: 'CP-AI',
prerequisiteSteps: ['use-case-assessment'],
isOptional: true },
{
id: 'screening',
seq: 500,
@@ -65,39 +78,27 @@ export const SDK_STEPS: SDKStep[] = [
order: 5,
name: 'System Screening',
nameShort: 'Screening',
description: 'SBOM + Security Check',
description: 'SBOM + Vulnerability Scan (OSV.dev)',
url: '/sdk/screening',
checkpointId: 'CP-SCAN',
prerequisiteSteps: ['use-case-assessment'],
isOptional: false },
{
id: 'modules',
seq: 600,
phase: 1,
package: 'vorbereitung',
order: 6,
name: 'Compliance Modules',
nameShort: 'Module',
description: 'Abgleich welche Regulierungen gelten',
url: '/sdk/modules',
checkpointId: 'CP-MOD',
prerequisiteSteps: ['screening'],
isOptional: false },
isOptional: true },
// Modules entfernt — Regulierungen werden im Scope-Decision-Tab + Dashboard angezeigt
{
id: 'source-policy',
seq: 700,
phase: 1,
package: 'vorbereitung',
order: 7,
name: 'Source Policy',
name: 'Quellen-Verwaltung',
nameShort: 'Quellen',
description: 'Datenquellen-Governance & Whitelist',
description: 'RAG Quellen-Whitelist (Enterprise)',
url: '/sdk/source-policy',
checkpointId: 'CP-SPOL',
prerequisiteSteps: ['modules'],
isOptional: false },
prerequisiteSteps: ['use-case-assessment'],
isOptional: true },
// PAKET 2: ANALYSE (Assessment)
// PAKET 2: ANALYSE (Assessment) — Requirements → Controls → Risks → Checklist → Report
{
id: 'requirements',
seq: 1000,
@@ -106,10 +107,10 @@ export const SDK_STEPS: SDKStep[] = [
order: 1,
name: 'Requirements',
nameShort: 'Anforderungen',
description: 'Pr\u00fcfaspekte aus Regulierungen ableiten',
description: 'Pruefaspekte aus Regulierungen ableiten',
url: '/sdk/requirements',
checkpointId: 'CP-REQ',
prerequisiteSteps: ['source-policy'],
prerequisiteSteps: ['compliance-scope'],
isOptional: false },
{
id: 'controls',
@@ -119,72 +120,46 @@ export const SDK_STEPS: SDKStep[] = [
order: 2,
name: 'Controls',
nameShort: 'Controls',
description: 'Erforderliche Ma\u00dfnahmen ermitteln',
description: 'Technische & organisatorische Massnahmen',
url: '/sdk/controls',
checkpointId: 'CP-CTRL',
prerequisiteSteps: ['requirements'],
isOptional: false },
{
id: 'evidence',
id: 'risks',
seq: 1200,
phase: 1,
package: 'analyse',
order: 3,
name: 'Evidence',
nameShort: 'Nachweise',
description: 'Nachweise dokumentieren',
url: '/sdk/evidence',
checkpointId: 'CP-EVI',
name: 'Risk Matrix',
nameShort: 'Risiken',
description: 'Risikobewertung — wo sind Luecken?',
url: '/sdk/risks',
checkpointId: 'CP-RISK',
prerequisiteSteps: ['controls'],
isOptional: false },
{
id: 'risks',
id: 'audit-checklist',
seq: 1300,
phase: 1,
package: 'analyse',
order: 4,
name: 'Risk Matrix',
nameShort: 'Risiken',
description: 'Risikobewertung & Residual Risk',
url: '/sdk/risks',
checkpointId: 'CP-RISK',
prerequisiteSteps: ['evidence'],
name: 'Audit Checklist',
nameShort: 'Checklist',
description: 'Pruefbare Checkliste generieren',
url: '/sdk/audit-checklist',
checkpointId: 'CP-CHK',
prerequisiteSteps: ['risks'],
isOptional: false },
{
id: 'ai-act',
id: 'audit-report',
seq: 1400,
phase: 1,
package: 'analyse',
order: 5,
name: 'AI Act Klassifizierung',
nameShort: 'AI Act',
description: 'Risikostufe nach EU AI Act',
url: '/sdk/ai-act',
checkpointId: 'CP-AI',
prerequisiteSteps: ['risks'],
isOptional: false },
{
id: 'audit-checklist',
seq: 1500,
phase: 1,
package: 'analyse',
order: 6,
name: 'Audit Checklist',
nameShort: 'Checklist',
description: 'Pr\u00fcfliste generieren',
url: '/sdk/audit-checklist',
checkpointId: 'CP-CHK',
prerequisiteSteps: ['ai-act'],
isOptional: false },
{
id: 'audit-report',
seq: 1600,
phase: 1,
package: 'analyse',
order: 7,
name: 'Audit Report',
nameShort: 'Report',
description: 'Audit-Sitzungen & PDF-Report',
description: 'Zusammenfassender Audit-Report (PDF)',
url: '/sdk/audit-report',
checkpointId: 'CP-AREP',
prerequisiteSteps: ['audit-checklist'],
@@ -271,8 +246,8 @@ export const SDK_STEPS: SDKStep[] = [
phase: 2,
package: 'rechtliche-texte',
order: 1,
name: 'Einwilligungen',
nameShort: 'Einwilligungen',
name: 'Consent-Records',
nameShort: 'Consent-Records',
description: 'Datenpunktkatalog & DSI-Generator',
url: '/sdk/einwilligungen',
checkpointId: 'CP-CONS',
@@ -334,6 +309,19 @@ export const SDK_STEPS: SDKStep[] = [
isOptional: false },
// PAKET 5: BETRIEB (Operations)
{
id: 'evidence',
seq: 3900,
phase: 2,
package: 'betrieb',
order: 0,
name: 'Evidence',
nameShort: 'Nachweise',
description: 'Nachweise laufend dokumentieren',
url: '/sdk/evidence',
checkpointId: 'CP-EVI',
prerequisiteSteps: ['audit-report'],
isOptional: false },
{
id: 'dsr',
seq: 4000,
@@ -0,0 +1,130 @@
/**
* EU-Angemessenheitsbeschluesse (Art. 45 DSGVO)
*
* Laender mit Angemessenheitsbeschluss benoetigen KEINE SCC und KEIN TIA
* fuer Datenuebermittlungen. Die Liste wird von der EU-Kommission gefuehrt.
*
* WICHTIG: USA hat Sonderstatus — Angemessenheit gilt NUR fuer Unternehmen,
* die nach dem EU-US Data Privacy Framework (DPF) zertifiziert sind.
* Nicht-zertifizierte US-Unternehmen brauchen weiterhin SCC + TIA.
*
* Quelle: https://commission.europa.eu/law/law-topic/data-protection/
* international-dimension-data-protection/adequacy-decisions_en
*/
export interface AdequacyDecision {
/** ISO 3166-1 alpha-2 Laendercode */
countryCode: string
/** Laendername (deutsch) */
countryName: string
/** Jahr des Angemessenheitsbeschlusses */
since: number
/** Einschraenkungen (z.B. nur bestimmte Sektoren) */
restriction?: string
/** Befristet? */
expires?: string
/** Sonderstatus (z.B. DPF-Zertifizierung erforderlich) */
requiresCertification?: boolean
/** Name der erforderlichen Zertifizierung */
certificationName?: string
/** Pruef-URL fuer die Zertifizierung */
certificationCheckUrl?: string
}
/**
* Vollstaendige Liste der Laender mit EU-Angemessenheitsbeschluss.
* Stand: Mai 2026
*/
export const ADEQUACY_DECISIONS: AdequacyDecision[] = [
{ countryCode: 'AD', countryName: 'Andorra', since: 2010 },
{ countryCode: 'AR', countryName: 'Argentinien', since: 2003 },
{ countryCode: 'FO', countryName: 'Faeroeer-Inseln', since: 2010 },
{ countryCode: 'GG', countryName: 'Guernsey', since: 2003 },
{ countryCode: 'IM', countryName: 'Isle of Man', since: 2004 },
{ countryCode: 'IL', countryName: 'Israel', since: 2011 },
{ countryCode: 'JP', countryName: 'Japan', since: 2019 },
{ countryCode: 'JE', countryName: 'Jersey', since: 2008 },
{
countryCode: 'CA', countryName: 'Kanada', since: 2001,
restriction: 'Nur Unternehmen, die dem Personal Information Protection and Electronic Documents Act (PIPEDA) unterliegen',
},
{ countryCode: 'NZ', countryName: 'Neuseeland', since: 2012 },
{ countryCode: 'KR', countryName: 'Republik Korea (Suedkorea)', since: 2022 },
{ countryCode: 'CH', countryName: 'Schweiz', since: 2000 },
{
countryCode: 'GB', countryName: 'Vereinigtes Koenigreich (UK)', since: 2021,
expires: 'Befristet, verlaengert bis 2029',
},
{ countryCode: 'UY', countryName: 'Uruguay', since: 2012 },
{
countryCode: 'US', countryName: 'Vereinigte Staaten (USA)', since: 2023,
restriction: 'Nur Unternehmen, die nach dem EU-US Data Privacy Framework (DPF) zertifiziert sind',
requiresCertification: true,
certificationName: 'EU-US Data Privacy Framework (DPF)',
certificationCheckUrl: 'https://www.dataprivacyframework.gov/list',
},
]
/** Set der EU/EWR-Laender (kein Angemessenheitsbeschluss noetig) */
export const EU_EEA_COUNTRIES = new Set([
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
// EWR (nicht EU, aber gleicher Datenschutzraum)
'IS', 'LI', 'NO',
])
/** Set der Laendercodes mit Angemessenheitsbeschluss */
export const ADEQUATE_COUNTRIES = new Set(
ADEQUACY_DECISIONS.map((d) => d.countryCode)
)
/**
* Prueft ob ein Land einen Angemessenheitsbeschluss hat.
* Gibt das Decision-Objekt zurueck oder null.
*/
export function getAdequacyDecision(countryCode: string): AdequacyDecision | null {
return ADEQUACY_DECISIONS.find((d) => d.countryCode === countryCode) || null
}
/**
* Bestimmt den Transfer-Status fuer ein Land.
*/
export function getTransferRequirement(countryCode: string): {
isEU: boolean
isAdequate: boolean
requiresSCC: boolean
requiresTIA: boolean
requiresCertification: boolean
explanation: string
} {
if (EU_EEA_COUNTRIES.has(countryCode)) {
return {
isEU: true, isAdequate: true,
requiresSCC: false, requiresTIA: false, requiresCertification: false,
explanation: 'EU-/EWR-Mitgliedstaat — keine zusaetzlichen Massnahmen erforderlich.',
}
}
const decision = getAdequacyDecision(countryCode)
if (decision) {
if (decision.requiresCertification) {
return {
isEU: false, isAdequate: true,
requiresSCC: false, requiresTIA: false, requiresCertification: true,
explanation: `Angemessenheitsbeschluss seit ${decision.since}. ${decision.restriction || ''} Pruefung der Zertifizierung unter: ${decision.certificationCheckUrl || ''}`,
}
}
return {
isEU: false, isAdequate: true,
requiresSCC: false, requiresTIA: false, requiresCertification: false,
explanation: `Angemessenheitsbeschluss der EU-Kommission seit ${decision.since}.${decision.restriction ? ` Einschraenkung: ${decision.restriction}` : ''}${decision.expires ? ` (${decision.expires})` : ''}`,
}
}
return {
isEU: false, isAdequate: false,
requiresSCC: true, requiresTIA: true, requiresCertification: false,
explanation: 'Kein Angemessenheitsbeschluss — EU-Standardvertragsklauseln (SCC) und Transfer Impact Assessment (TIA) erforderlich (Art. 46 Abs. 2 lit. c DSGVO, EuGH Schrems II).',
}
}
@@ -21,7 +21,8 @@ import {
// CONFIGURATION
// =============================================================================
const WB_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093'
// Use compliance backend proxy (Python) instead of Go SDK
const WB_API_BASE = '/api/sdk/v1/compliance'
const API_TIMEOUT = 30000
// =============================================================================
@@ -121,27 +122,27 @@ export async function fetchReports(filters?: ReportFilters): Promise<ReportListR
}
const queryString = params.toString()
const url = `${WB_API_BASE}/api/v1/admin/whistleblower/reports${queryString ? `?${queryString}` : ''}`
const url = `${WB_API_BASE}/whistleblower/reports${queryString ? `?${queryString}` : ''}`
return fetchWithTimeout<ReportListResponse>(url)
}
export async function fetchReport(id: string): Promise<WhistleblowerReport> {
return fetchWithTimeout<WhistleblowerReport>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`
`${WB_API_BASE}/whistleblower/reports/${id}`
)
}
export async function updateReport(id: string, update: ReportUpdateRequest): Promise<WhistleblowerReport> {
return fetchWithTimeout<WhistleblowerReport>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
`${WB_API_BASE}/whistleblower/reports/${id}`,
{ method: 'PUT', body: JSON.stringify(update) }
)
}
export async function deleteReport(id: string): Promise<void> {
await fetchWithTimeout<void>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
`${WB_API_BASE}/whistleblower/reports/${id}`,
{ method: 'DELETE' }
)
}
@@ -154,7 +155,7 @@ export async function submitPublicReport(
data: PublicReportSubmission
): Promise<{ report: WhistleblowerReport; accessKey: string }> {
const response = await fetch(
`${WB_API_BASE}/api/v1/public/whistleblower/submit`,
`${WB_API_BASE}/whistleblower/submit`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -173,7 +174,7 @@ export async function fetchReportByAccessKey(
accessKey: string
): Promise<WhistleblowerReport> {
const response = await fetch(
`${WB_API_BASE}/api/v1/public/whistleblower/report/${accessKey}`,
`${WB_API_BASE}/whistleblower/check/${accessKey}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
)
@@ -190,14 +191,14 @@ export async function fetchReportByAccessKey(
export async function acknowledgeReport(id: string): Promise<WhistleblowerReport> {
return fetchWithTimeout<WhistleblowerReport>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/acknowledge`,
`${WB_API_BASE}/whistleblower/reports/${id}/acknowledge`,
{ method: 'POST' }
)
}
export async function startInvestigation(id: string): Promise<WhistleblowerReport> {
return fetchWithTimeout<WhistleblowerReport>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/investigate`,
`${WB_API_BASE}/whistleblower/reports/${id}/investigate`,
{ method: 'POST' }
)
}
@@ -207,7 +208,7 @@ export async function addMeasure(
measure: Omit<WhistleblowerMeasure, 'id' | 'reportId' | 'completedAt'>
): Promise<WhistleblowerMeasure> {
return fetchWithTimeout<WhistleblowerMeasure>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/measures`,
`${WB_API_BASE}/whistleblower/reports/${id}/measures`,
{ method: 'POST', body: JSON.stringify(measure) }
)
}
@@ -217,7 +218,7 @@ export async function closeReport(
resolution: { reason: string; notes: string }
): Promise<WhistleblowerReport> {
return fetchWithTimeout<WhistleblowerReport>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/close`,
`${WB_API_BASE}/whistleblower/reports/${id}/close`,
{ method: 'POST', body: JSON.stringify(resolution) }
)
}
@@ -232,14 +233,14 @@ export async function sendMessage(
role: 'reporter' | 'ombudsperson'
): Promise<AnonymousMessage> {
return fetchWithTimeout<AnonymousMessage>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`,
`${WB_API_BASE}/whistleblower/reports/${reportId}/messages`,
{ method: 'POST', body: JSON.stringify({ senderRole: role, message }) }
)
}
export async function fetchMessages(reportId: string): Promise<AnonymousMessage[]> {
return fetchWithTimeout<AnonymousMessage[]>(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`
`${WB_API_BASE}/whistleblower/reports/${reportId}/messages`
)
}
@@ -269,7 +270,7 @@ export async function uploadAttachment(
}
const response = await fetch(
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/attachments`,
`${WB_API_BASE}/whistleblower/reports/${reportId}/attachments`,
{
method: 'POST',
headers,
@@ -290,7 +291,7 @@ export async function uploadAttachment(
export async function deleteAttachment(id: string): Promise<void> {
await fetchWithTimeout<void>(
`${WB_API_BASE}/api/v1/admin/whistleblower/attachments/${id}`,
`${WB_API_BASE}/whistleblower/attachments/${id}`,
{ method: 'DELETE' }
)
}
@@ -301,6 +302,6 @@ export async function deleteAttachment(id: string): Promise<void> {
export async function fetchWhistleblowerStatistics(): Promise<WhistleblowerStatistics> {
return fetchWithTimeout<WhistleblowerStatistics>(
`${WB_API_BASE}/api/v1/admin/whistleblower/statistics`
`${WB_API_BASE}/whistleblower/reports/stats`
)
}