Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* Contract Analyzer
|
||||
*
|
||||
* LLM-based contract review for GDPR compliance
|
||||
*/
|
||||
|
||||
import {
|
||||
Finding,
|
||||
Citation,
|
||||
FindingType,
|
||||
FindingCategory,
|
||||
FindingSeverity,
|
||||
DocumentType,
|
||||
LocalizedText,
|
||||
} from '../types'
|
||||
import { AVV_CHECKLIST, INCIDENT_CHECKLIST, TRANSFER_CHECKLIST } from './checklists'
|
||||
|
||||
// ==========================================
|
||||
// TYPES
|
||||
// ==========================================
|
||||
|
||||
export interface ContractAnalysisRequest {
|
||||
contractId: string
|
||||
vendorId: string
|
||||
tenantId: string
|
||||
documentText: string
|
||||
documentType?: DocumentType
|
||||
language?: 'de' | 'en'
|
||||
analysisScope?: AnalysisScope[]
|
||||
}
|
||||
|
||||
export interface ContractAnalysisResponse {
|
||||
documentType: DocumentType
|
||||
language: 'de' | 'en'
|
||||
parties: ContractPartyInfo[]
|
||||
findings: Finding[]
|
||||
complianceScore: number
|
||||
topRisks: LocalizedText[]
|
||||
requiredActions: LocalizedText[]
|
||||
metadata: ExtractedMetadata
|
||||
}
|
||||
|
||||
export interface ContractPartyInfo {
|
||||
role: 'CONTROLLER' | 'PROCESSOR' | 'PARTY'
|
||||
name: string
|
||||
address?: string
|
||||
}
|
||||
|
||||
export interface ExtractedMetadata {
|
||||
effectiveDate?: string
|
||||
expirationDate?: string
|
||||
autoRenewal?: boolean
|
||||
terminationNoticePeriod?: number
|
||||
governingLaw?: string
|
||||
jurisdiction?: string
|
||||
}
|
||||
|
||||
export type AnalysisScope =
|
||||
| 'AVV_COMPLIANCE'
|
||||
| 'SUBPROCESSOR'
|
||||
| 'INCIDENT_RESPONSE'
|
||||
| 'AUDIT_RIGHTS'
|
||||
| 'DELETION'
|
||||
| 'TOM'
|
||||
| 'TRANSFER'
|
||||
| 'LIABILITY'
|
||||
| 'SLA'
|
||||
|
||||
// ==========================================
|
||||
// SYSTEM PROMPTS
|
||||
// ==========================================
|
||||
|
||||
export const CONTRACT_REVIEW_SYSTEM_PROMPT = `Du bist ein Datenschutz-Rechtsexperte, der Verträge auf DSGVO-Konformität prüft.
|
||||
|
||||
WICHTIG:
|
||||
1. Jede Feststellung MUSS mit einer Textstelle belegt werden (Citation)
|
||||
2. Gib niemals Rechtsberatung - nur Compliance-Hinweise
|
||||
3. Markiere unklare Stellen als UNKNOWN, nicht als GAP
|
||||
4. Sei konservativ: im Zweifel RISK statt OK
|
||||
|
||||
PRÜFUNGSSCHEMA Art. 28 DSGVO AVV:
|
||||
${AVV_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
|
||||
|
||||
INCIDENT RESPONSE:
|
||||
${INCIDENT_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
|
||||
|
||||
DRITTLANDTRANSFER:
|
||||
${TRANSFER_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
|
||||
|
||||
AUSGABEFORMAT (JSON):
|
||||
{
|
||||
"document_type": "AVV|MSA|SLA|SCC|NDA|TOM_ANNEX|OTHER|UNKNOWN",
|
||||
"language": "de|en",
|
||||
"parties": [
|
||||
{
|
||||
"role": "CONTROLLER|PROCESSOR|PARTY",
|
||||
"name": "...",
|
||||
"address": "..."
|
||||
}
|
||||
],
|
||||
"findings": [
|
||||
{
|
||||
"category": "AVV_CONTENT|SUBPROCESSOR|INCIDENT|AUDIT_RIGHTS|DELETION|TOM|TRANSFER|LIABILITY|SLA|DATA_SUBJECT_RIGHTS|CONFIDENTIALITY|INSTRUCTION|GENERAL",
|
||||
"type": "OK|GAP|RISK|UNKNOWN",
|
||||
"severity": "LOW|MEDIUM|HIGH|CRITICAL",
|
||||
"title_de": "...",
|
||||
"title_en": "...",
|
||||
"description_de": "...",
|
||||
"description_en": "...",
|
||||
"recommendation_de": "...",
|
||||
"recommendation_en": "...",
|
||||
"citations": [
|
||||
{
|
||||
"page": 3,
|
||||
"quoted_text": "Der Auftragnehmer...",
|
||||
"start_char": 1234,
|
||||
"end_char": 1456
|
||||
}
|
||||
],
|
||||
"affected_requirement": "Art. 28 Abs. 3 lit. a DSGVO"
|
||||
}
|
||||
],
|
||||
"compliance_score": 72,
|
||||
"top_risks": [
|
||||
{"de": "...", "en": "..."}
|
||||
],
|
||||
"required_actions": [
|
||||
{"de": "...", "en": "..."}
|
||||
],
|
||||
"metadata": {
|
||||
"effective_date": "2024-01-01",
|
||||
"expiration_date": "2025-12-31",
|
||||
"auto_renewal": true,
|
||||
"termination_notice_period": 90,
|
||||
"governing_law": "Germany",
|
||||
"jurisdiction": "Frankfurt am Main"
|
||||
}
|
||||
}`
|
||||
|
||||
export const CONTRACT_CLASSIFICATION_PROMPT = `Analysiere den folgenden Vertragstext und klassifiziere ihn:
|
||||
|
||||
1. Dokumenttyp (AVV, MSA, SLA, SCC, NDA, TOM_ANNEX, OTHER)
|
||||
2. Sprache (de, en)
|
||||
3. Vertragsparteien mit Rollen
|
||||
|
||||
Antworte im JSON-Format:
|
||||
{
|
||||
"document_type": "...",
|
||||
"language": "...",
|
||||
"parties": [...]
|
||||
}`
|
||||
|
||||
export const METADATA_EXTRACTION_PROMPT = `Extrahiere die folgenden Metadaten aus dem Vertrag:
|
||||
|
||||
1. Inkrafttreten / Effective Date
|
||||
2. Laufzeit / Ablaufdatum
|
||||
3. Automatische Verlängerung
|
||||
4. Kündigungsfrist
|
||||
5. Anwendbares Recht
|
||||
6. Gerichtsstand
|
||||
|
||||
Antworte im JSON-Format.`
|
||||
|
||||
// ==========================================
|
||||
// ANALYSIS FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Analyze a contract for GDPR compliance
|
||||
*/
|
||||
export async function analyzeContract(
|
||||
request: ContractAnalysisRequest
|
||||
): Promise<ContractAnalysisResponse> {
|
||||
// This function would typically call an LLM API
|
||||
// For now, we provide the structure that would be used
|
||||
|
||||
const apiEndpoint = '/api/sdk/v1/vendor-compliance/contracts/analyze'
|
||||
|
||||
const response = await fetch(apiEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
contract_id: request.contractId,
|
||||
vendor_id: request.vendorId,
|
||||
tenant_id: request.tenantId,
|
||||
document_text: request.documentText,
|
||||
document_type: request.documentType,
|
||||
language: request.language || 'de',
|
||||
analysis_scope: request.analysisScope || [
|
||||
'AVV_COMPLIANCE',
|
||||
'SUBPROCESSOR',
|
||||
'INCIDENT_RESPONSE',
|
||||
'AUDIT_RIGHTS',
|
||||
'DELETION',
|
||||
'TOM',
|
||||
'TRANSFER',
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Contract analysis failed')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return transformAnalysisResponse(result, request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform LLM response to typed response
|
||||
*/
|
||||
function transformAnalysisResponse(
|
||||
llmResponse: Record<string, unknown>,
|
||||
request: ContractAnalysisRequest
|
||||
): ContractAnalysisResponse {
|
||||
const findings: Finding[] = (llmResponse.findings as Array<Record<string, unknown>> || []).map((f, idx) => ({
|
||||
id: `finding-${request.contractId}-${idx}`,
|
||||
tenantId: request.tenantId,
|
||||
contractId: request.contractId,
|
||||
vendorId: request.vendorId,
|
||||
type: (f.type as FindingType) || 'UNKNOWN',
|
||||
category: (f.category as FindingCategory) || 'GENERAL',
|
||||
severity: (f.severity as FindingSeverity) || 'MEDIUM',
|
||||
title: {
|
||||
de: (f.title_de as string) || '',
|
||||
en: (f.title_en as string) || '',
|
||||
},
|
||||
description: {
|
||||
de: (f.description_de as string) || '',
|
||||
en: (f.description_en as string) || '',
|
||||
},
|
||||
recommendation: f.recommendation_de ? {
|
||||
de: f.recommendation_de as string,
|
||||
en: (f.recommendation_en as string) || '',
|
||||
} : undefined,
|
||||
citations: ((f.citations as Array<Record<string, unknown>>) || []).map((c) => ({
|
||||
documentId: request.contractId,
|
||||
page: (c.page as number) || 1,
|
||||
startChar: (c.start_char as number) || 0,
|
||||
endChar: (c.end_char as number) || 0,
|
||||
quotedText: (c.quoted_text as string) || '',
|
||||
quoteHash: generateQuoteHash((c.quoted_text as string) || ''),
|
||||
})),
|
||||
affectedRequirement: f.affected_requirement as string | undefined,
|
||||
triggeredControls: [],
|
||||
status: 'OPEN',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}))
|
||||
|
||||
const metadata = llmResponse.metadata as Record<string, unknown> || {}
|
||||
|
||||
return {
|
||||
documentType: (llmResponse.document_type as DocumentType) || 'OTHER',
|
||||
language: (llmResponse.language as 'de' | 'en') || 'de',
|
||||
parties: ((llmResponse.parties as Array<Record<string, unknown>>) || []).map((p) => ({
|
||||
role: (p.role as 'CONTROLLER' | 'PROCESSOR' | 'PARTY') || 'PARTY',
|
||||
name: (p.name as string) || '',
|
||||
address: p.address as string | undefined,
|
||||
})),
|
||||
findings,
|
||||
complianceScore: (llmResponse.compliance_score as number) || 0,
|
||||
topRisks: ((llmResponse.top_risks as Array<Record<string, string>>) || []).map((r) => ({
|
||||
de: r.de || '',
|
||||
en: r.en || '',
|
||||
})),
|
||||
requiredActions: ((llmResponse.required_actions as Array<Record<string, string>>) || []).map((a) => ({
|
||||
de: a.de || '',
|
||||
en: a.en || '',
|
||||
})),
|
||||
metadata: {
|
||||
effectiveDate: metadata.effective_date as string | undefined,
|
||||
expirationDate: metadata.expiration_date as string | undefined,
|
||||
autoRenewal: metadata.auto_renewal as boolean | undefined,
|
||||
terminationNoticePeriod: metadata.termination_notice_period as number | undefined,
|
||||
governingLaw: metadata.governing_law as string | undefined,
|
||||
jurisdiction: metadata.jurisdiction as string | undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for quote verification
|
||||
*/
|
||||
function generateQuoteHash(text: string): string {
|
||||
// Simple hash for demo - in production use crypto.subtle.digest
|
||||
let hash = 0
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charCodeAt(i)
|
||||
hash = ((hash << 5) - hash) + char
|
||||
hash = hash & hash
|
||||
}
|
||||
return Math.abs(hash).toString(16).padStart(16, '0')
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CITATION UTILITIES
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Verify citation integrity
|
||||
*/
|
||||
export function verifyCitation(
|
||||
citation: Citation,
|
||||
documentText: string
|
||||
): boolean {
|
||||
const extractedText = documentText.substring(citation.startChar, citation.endChar)
|
||||
const expectedHash = generateQuoteHash(extractedText)
|
||||
return citation.quoteHash === expectedHash
|
||||
}
|
||||
|
||||
/**
|
||||
* Find citation context in document
|
||||
*/
|
||||
export function getCitationContext(
|
||||
citation: Citation,
|
||||
documentText: string,
|
||||
contextChars: number = 100
|
||||
): {
|
||||
before: string
|
||||
quoted: string
|
||||
after: string
|
||||
} {
|
||||
const start = Math.max(0, citation.startChar - contextChars)
|
||||
const end = Math.min(documentText.length, citation.endChar + contextChars)
|
||||
|
||||
return {
|
||||
before: documentText.substring(start, citation.startChar),
|
||||
quoted: documentText.substring(citation.startChar, citation.endChar),
|
||||
after: documentText.substring(citation.endChar, end),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight citations in text
|
||||
*/
|
||||
export function highlightCitations(
|
||||
documentText: string,
|
||||
citations: Citation[]
|
||||
): string {
|
||||
// Sort citations by start position (reverse to avoid offset issues)
|
||||
const sortedCitations = [...citations].sort((a, b) => b.startChar - a.startChar)
|
||||
|
||||
let result = documentText
|
||||
|
||||
for (const citation of sortedCitations) {
|
||||
const before = result.substring(0, citation.startChar)
|
||||
const quoted = result.substring(citation.startChar, citation.endChar)
|
||||
const after = result.substring(citation.endChar)
|
||||
|
||||
result = `${before}<mark data-citation-id="${citation.documentId}">${quoted}</mark>${after}`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COMPLIANCE SCORE CALCULATION
|
||||
// ==========================================
|
||||
|
||||
export interface ComplianceScoreBreakdown {
|
||||
totalScore: number
|
||||
categoryScores: Record<FindingCategory, number>
|
||||
severityCounts: Record<FindingSeverity, number>
|
||||
findingCounts: {
|
||||
total: number
|
||||
gaps: number
|
||||
risks: number
|
||||
ok: number
|
||||
unknown: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate detailed compliance score
|
||||
*/
|
||||
export function calculateComplianceScore(findings: Finding[]): ComplianceScoreBreakdown {
|
||||
const severityWeights: Record<FindingSeverity, number> = {
|
||||
CRITICAL: 25,
|
||||
HIGH: 15,
|
||||
MEDIUM: 8,
|
||||
LOW: 3,
|
||||
}
|
||||
|
||||
const categoryWeights: Partial<Record<FindingCategory, number>> = {
|
||||
AVV_CONTENT: 1.5,
|
||||
SUBPROCESSOR: 1.3,
|
||||
INCIDENT: 1.3,
|
||||
DELETION: 1.2,
|
||||
AUDIT_RIGHTS: 1.1,
|
||||
TOM: 1.2,
|
||||
TRANSFER: 1.4,
|
||||
}
|
||||
|
||||
let totalDeductions = 0
|
||||
const maxPossibleDeductions = 100
|
||||
|
||||
const categoryScores: Partial<Record<FindingCategory, number>> = {}
|
||||
const severityCounts: Record<FindingSeverity, number> = {
|
||||
LOW: 0,
|
||||
MEDIUM: 0,
|
||||
HIGH: 0,
|
||||
CRITICAL: 0,
|
||||
}
|
||||
|
||||
let gaps = 0
|
||||
let risks = 0
|
||||
let ok = 0
|
||||
let unknown = 0
|
||||
|
||||
for (const finding of findings) {
|
||||
severityCounts[finding.severity]++
|
||||
|
||||
switch (finding.type) {
|
||||
case 'GAP':
|
||||
gaps++
|
||||
totalDeductions += severityWeights[finding.severity] * (categoryWeights[finding.category] || 1)
|
||||
break
|
||||
case 'RISK':
|
||||
risks++
|
||||
totalDeductions += severityWeights[finding.severity] * 0.7 * (categoryWeights[finding.category] || 1)
|
||||
break
|
||||
case 'OK':
|
||||
ok++
|
||||
break
|
||||
case 'UNKNOWN':
|
||||
unknown++
|
||||
totalDeductions += severityWeights[finding.severity] * 0.3 * (categoryWeights[finding.category] || 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate category scores
|
||||
const categories = new Set(findings.map((f) => f.category))
|
||||
for (const category of categories) {
|
||||
const categoryFindings = findings.filter((f) => f.category === category)
|
||||
const categoryOk = categoryFindings.filter((f) => f.type === 'OK').length
|
||||
const categoryTotal = categoryFindings.length
|
||||
categoryScores[category] = categoryTotal > 0 ? Math.round((categoryOk / categoryTotal) * 100) : 100
|
||||
}
|
||||
|
||||
const totalScore = Math.max(0, Math.round(100 - (totalDeductions / maxPossibleDeductions) * 100))
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
categoryScores: categoryScores as Record<FindingCategory, number>,
|
||||
severityCounts,
|
||||
findingCounts: {
|
||||
total: findings.length,
|
||||
gaps,
|
||||
risks,
|
||||
ok,
|
||||
unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* Contract Review Checklists
|
||||
*
|
||||
* DSGVO Art. 28 compliance checklists for contract reviews
|
||||
*/
|
||||
|
||||
import { LocalizedText, FindingCategory } from '../types'
|
||||
|
||||
export interface ChecklistItem {
|
||||
id: string
|
||||
category: FindingCategory
|
||||
requirement: LocalizedText
|
||||
article: string
|
||||
description: LocalizedText
|
||||
checkPoints: LocalizedText[]
|
||||
isRequired: boolean
|
||||
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||||
}
|
||||
|
||||
export interface ChecklistGroup {
|
||||
id: string
|
||||
name: LocalizedText
|
||||
description: LocalizedText
|
||||
items: ChecklistItem[]
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ART. 28 DSGVO CHECKLIST
|
||||
// ==========================================
|
||||
|
||||
export const AVV_CHECKLIST: ChecklistItem[] = [
|
||||
// Art. 28 Abs. 3 lit. a - Weisungsgebundenheit
|
||||
{
|
||||
id: 'art28_3_a',
|
||||
category: 'INSTRUCTION',
|
||||
requirement: {
|
||||
de: 'Weisungsgebundenheit',
|
||||
en: 'Instruction binding',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. a DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter verarbeitet personenbezogene Daten nur auf dokumentierte Weisung des Verantwortlichen.',
|
||||
en: 'The processor processes personal data only on documented instructions from the controller.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Weisungsgebundenheit explizit vereinbart', en: 'Instruction binding explicitly agreed' },
|
||||
{ de: 'Dokumentierte Weisungen vorgesehen', en: 'Documented instructions provided for' },
|
||||
{ de: 'Hinweispflicht bei rechtswidrigen Weisungen', en: 'Obligation to notify of unlawful instructions' },
|
||||
{ de: 'Keine eigenständige Verarbeitung erlaubt', en: 'No independent processing allowed' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. b - Vertraulichkeit
|
||||
{
|
||||
id: 'art28_3_b',
|
||||
category: 'CONFIDENTIALITY',
|
||||
requirement: {
|
||||
de: 'Vertraulichkeitsverpflichtung',
|
||||
en: 'Confidentiality obligation',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. b DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter gewährleistet, dass sich die zur Verarbeitung befugten Personen zur Vertraulichkeit verpflichtet haben.',
|
||||
en: 'The processor ensures that persons authorised to process personal data have committed themselves to confidentiality.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Vertraulichkeitsverpflichtung der Mitarbeiter', en: 'Confidentiality obligation of employees' },
|
||||
{ de: 'Gesetzliche Verschwiegenheitspflicht oder', en: 'Statutory confidentiality obligation or' },
|
||||
{ de: 'Vertragliche Verpflichtung', en: 'Contractual obligation' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. c - Technische und organisatorische Maßnahmen
|
||||
{
|
||||
id: 'art28_3_c',
|
||||
category: 'TOM',
|
||||
requirement: {
|
||||
de: 'Technische und organisatorische Maßnahmen',
|
||||
en: 'Technical and organisational measures',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. c DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter trifft alle gemäß Art. 32 erforderlichen Maßnahmen.',
|
||||
en: 'The processor takes all measures required pursuant to Art. 32.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'TOM-Anlage vorhanden', en: 'TOM annex present' },
|
||||
{ de: 'TOM konkret und aktuell', en: 'TOM specific and current' },
|
||||
{ de: 'Bezug zu Art. 32 DSGVO', en: 'Reference to Art. 32 GDPR' },
|
||||
{ de: 'Aktualisierungspflicht vereinbart', en: 'Update obligation agreed' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. d - Unterauftragsverarbeitung
|
||||
{
|
||||
id: 'art28_3_d',
|
||||
category: 'SUBPROCESSOR',
|
||||
requirement: {
|
||||
de: 'Unterauftragsverarbeitung',
|
||||
en: 'Sub-processing',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. d DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter nimmt keinen weiteren Auftragsverarbeiter ohne vorherige Genehmigung in Anspruch.',
|
||||
en: 'The processor does not engage another processor without prior authorisation.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Genehmigungserfordernis (allgemein oder spezifisch)', en: 'Authorisation requirement (general or specific)' },
|
||||
{ de: 'Bei allgemeiner Genehmigung: Informationspflicht', en: 'With general authorisation: notification obligation' },
|
||||
{ de: 'Einspruchsrecht des Verantwortlichen', en: 'Right of objection for controller' },
|
||||
{ de: 'Liste aktueller Unterauftragnehmer', en: 'List of current sub-processors' },
|
||||
{ de: 'Weitergabe der Pflichten an Unterauftragnehmer', en: 'Transfer of obligations to sub-processors' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. e - Unterstützung bei Betroffenenrechten
|
||||
{
|
||||
id: 'art28_3_e',
|
||||
category: 'DATA_SUBJECT_RIGHTS',
|
||||
requirement: {
|
||||
de: 'Unterstützung bei Betroffenenrechten',
|
||||
en: 'Assistance with data subject rights',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. e DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter unterstützt den Verantwortlichen bei der Erfüllung der Betroffenenrechte.',
|
||||
en: 'The processor assists the controller in fulfilling data subject rights obligations.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Unterstützungspflicht vereinbart', en: 'Assistance obligation agreed' },
|
||||
{ de: 'Verfahren zur Weiterleitung von Anfragen', en: 'Procedure for forwarding requests' },
|
||||
{ de: 'Fristen für Unterstützung', en: 'Deadlines for assistance' },
|
||||
{ de: 'Kostenregelung', en: 'Cost arrangement' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. f - Unterstützung bei DSFA
|
||||
{
|
||||
id: 'art28_3_f',
|
||||
category: 'GENERAL',
|
||||
requirement: {
|
||||
de: 'Unterstützung bei DSFA und Konsultation',
|
||||
en: 'Assistance with DPIA and consultation',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. f DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter unterstützt den Verantwortlichen bei der Einhaltung der Pflichten gemäß Art. 32-36.',
|
||||
en: 'The processor assists the controller in ensuring compliance with obligations pursuant to Art. 32-36.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Unterstützung bei DSFA', en: 'Assistance with DPIA' },
|
||||
{ de: 'Unterstützung bei vorheriger Konsultation', en: 'Assistance with prior consultation' },
|
||||
{ de: 'Bereitstellung notwendiger Informationen', en: 'Provision of necessary information' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'MEDIUM',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. g - Löschung/Rückgabe
|
||||
{
|
||||
id: 'art28_3_g',
|
||||
category: 'DELETION',
|
||||
requirement: {
|
||||
de: 'Löschung/Rückgabe nach Vertragsende',
|
||||
en: 'Deletion/return after contract end',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. g DSGVO',
|
||||
description: {
|
||||
de: 'Nach Abschluss der Verarbeitung werden alle personenbezogenen Daten gelöscht oder zurückgegeben.',
|
||||
en: 'After the end of processing, all personal data is deleted or returned.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Löschung oder Rückgabe nach Wahl des Verantwortlichen', en: 'Deletion or return at controller choice' },
|
||||
{ de: 'Frist für Löschung/Rückgabe (max. 30 Tage empfohlen)', en: 'Deadline for deletion/return (max. 30 days recommended)' },
|
||||
{ de: 'Löschung auch bei Unterauftragnehmern', en: 'Deletion also at sub-processors' },
|
||||
{ de: 'Löschbestätigung/Nachweis', en: 'Deletion confirmation/proof' },
|
||||
{ de: 'Ausnahme nur bei gesetzlicher Aufbewahrungspflicht', en: 'Exception only for legal retention obligation' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
|
||||
// Art. 28 Abs. 3 lit. h - Audit/Inspektion
|
||||
{
|
||||
id: 'art28_3_h',
|
||||
category: 'AUDIT_RIGHTS',
|
||||
requirement: {
|
||||
de: 'Audit- und Inspektionsrechte',
|
||||
en: 'Audit and inspection rights',
|
||||
},
|
||||
article: 'Art. 28 Abs. 3 lit. h DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter ermöglicht und unterstützt Überprüfungen durch den Verantwortlichen.',
|
||||
en: 'The processor enables and contributes to audits and inspections by the controller.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Auditrecht ausdrücklich vereinbart', en: 'Audit right explicitly agreed' },
|
||||
{ de: 'Vor-Ort-Inspektionen möglich', en: 'On-site inspections possible' },
|
||||
{ de: 'Angemessene Vorlaufzeit (max. 30 Tage)', en: 'Reasonable notice period (max. 30 days)' },
|
||||
{ de: 'Keine unangemessenen Einschränkungen', en: 'No unreasonable restrictions' },
|
||||
{ de: 'Bereitstellung aller relevanten Informationen', en: 'Provision of all relevant information' },
|
||||
{ de: 'Akzeptanz unabhängiger Prüfer', en: 'Acceptance of independent auditors' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// INCIDENT RESPONSE CHECKLIST
|
||||
// ==========================================
|
||||
|
||||
export const INCIDENT_CHECKLIST: ChecklistItem[] = [
|
||||
{
|
||||
id: 'incident_notification',
|
||||
category: 'INCIDENT',
|
||||
requirement: {
|
||||
de: 'Meldung von Datenschutzverletzungen',
|
||||
en: 'Notification of data breaches',
|
||||
},
|
||||
article: 'Art. 33 Abs. 2 DSGVO',
|
||||
description: {
|
||||
de: 'Der Auftragsverarbeiter meldet dem Verantwortlichen unverzüglich jede Datenschutzverletzung.',
|
||||
en: 'The processor notifies the controller without undue delay of any personal data breach.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Meldepflicht vereinbart', en: 'Notification obligation agreed' },
|
||||
{ de: 'Frist: Unverzüglich (max. 24-48h empfohlen)', en: 'Deadline: Without undue delay (max. 24-48h recommended)' },
|
||||
{ de: 'Mindestinhalt der Meldung definiert', en: 'Minimum content of notification defined' },
|
||||
{ de: 'Kontaktstelle für Meldungen', en: 'Contact point for notifications' },
|
||||
{ de: 'Unterstützung bei Dokumentation', en: 'Assistance with documentation' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
{
|
||||
id: 'incident_content',
|
||||
category: 'INCIDENT',
|
||||
requirement: {
|
||||
de: 'Inhalt der Incident-Meldung',
|
||||
en: 'Content of incident notification',
|
||||
},
|
||||
article: 'Art. 33 Abs. 3 DSGVO',
|
||||
description: {
|
||||
de: 'Die Meldung muss bestimmte Mindestinformationen enthalten.',
|
||||
en: 'The notification must contain certain minimum information.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Art der Verletzung', en: 'Nature of the breach' },
|
||||
{ de: 'Betroffene Datenkategorien', en: 'Affected data categories' },
|
||||
{ de: 'Ungefähre Anzahl betroffener Personen', en: 'Approximate number of affected persons' },
|
||||
{ de: 'Wahrscheinliche Folgen', en: 'Likely consequences' },
|
||||
{ de: 'Ergriffene Maßnahmen', en: 'Measures taken' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// THIRD COUNTRY TRANSFER CHECKLIST
|
||||
// ==========================================
|
||||
|
||||
export const TRANSFER_CHECKLIST: ChecklistItem[] = [
|
||||
{
|
||||
id: 'transfer_basis',
|
||||
category: 'TRANSFER',
|
||||
requirement: {
|
||||
de: 'Rechtsgrundlage für Drittlandtransfer',
|
||||
en: 'Legal basis for third country transfer',
|
||||
},
|
||||
article: 'Art. 44-49 DSGVO',
|
||||
description: {
|
||||
de: 'Drittlandtransfers nur auf Basis geeigneter Garantien.',
|
||||
en: 'Third country transfers only on the basis of appropriate safeguards.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Angemessenheitsbeschluss oder', en: 'Adequacy decision or' },
|
||||
{ de: 'Standardvertragsklauseln (SCC) oder', en: 'Standard contractual clauses (SCC) or' },
|
||||
{ de: 'Binding Corporate Rules (BCR) oder', en: 'Binding Corporate Rules (BCR) or' },
|
||||
{ de: 'Sonstige Ausnahme Art. 49', en: 'Other derogation Art. 49' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'CRITICAL',
|
||||
},
|
||||
{
|
||||
id: 'transfer_scc',
|
||||
category: 'TRANSFER',
|
||||
requirement: {
|
||||
de: 'Standardvertragsklauseln',
|
||||
en: 'Standard Contractual Clauses',
|
||||
},
|
||||
article: 'Art. 46 Abs. 2 lit. c DSGVO',
|
||||
description: {
|
||||
de: 'Bei SCC: Verwendung der aktuellen EU-Kommission-Klauseln.',
|
||||
en: 'With SCC: Use of current EU Commission clauses.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'SCC 2021 verwendet', en: 'SCC 2021 used' },
|
||||
{ de: 'Korrektes Modul gewählt', en: 'Correct module selected' },
|
||||
{ de: 'Anhänge vollständig ausgefüllt', en: 'Annexes completely filled out' },
|
||||
{ de: 'TIA durchgeführt (bei Risiko)', en: 'TIA conducted (if risk)' },
|
||||
{ de: 'Zusätzliche Maßnahmen dokumentiert', en: 'Additional measures documented' },
|
||||
],
|
||||
isRequired: false,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
{
|
||||
id: 'transfer_tia',
|
||||
category: 'TRANSFER',
|
||||
requirement: {
|
||||
de: 'Transfer Impact Assessment',
|
||||
en: 'Transfer Impact Assessment',
|
||||
},
|
||||
article: 'Schrems II, EDSA 01/2020',
|
||||
description: {
|
||||
de: 'Bewertung der Risiken im Drittland.',
|
||||
en: 'Assessment of risks in the third country.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Rechtslage im Drittland analysiert', en: 'Legal situation in third country analyzed' },
|
||||
{ de: 'Zugriff durch Behörden bewertet', en: 'Access by authorities assessed' },
|
||||
{ de: 'Zusätzliche technische Maßnahmen', en: 'Additional technical measures' },
|
||||
{ de: 'Dokumentation der Bewertung', en: 'Documentation of assessment' },
|
||||
],
|
||||
isRequired: true,
|
||||
severity: 'HIGH',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// SLA & LIABILITY CHECKLIST
|
||||
// ==========================================
|
||||
|
||||
export const SLA_LIABILITY_CHECKLIST: ChecklistItem[] = [
|
||||
{
|
||||
id: 'sla_availability',
|
||||
category: 'SLA',
|
||||
requirement: {
|
||||
de: 'Verfügbarkeit',
|
||||
en: 'Availability',
|
||||
},
|
||||
article: 'Vertragliche Vereinbarung',
|
||||
description: {
|
||||
de: 'Service Level Agreement für Verfügbarkeit des Dienstes.',
|
||||
en: 'Service Level Agreement for service availability.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Verfügbarkeit definiert (z.B. 99,9%)', en: 'Availability defined (e.g., 99.9%)' },
|
||||
{ de: 'Messzeitraum festgelegt', en: 'Measurement period defined' },
|
||||
{ de: 'Ausnahmen klar definiert', en: 'Exceptions clearly defined' },
|
||||
{ de: 'Konsequenzen bei Nichteinhaltung', en: 'Consequences of non-compliance' },
|
||||
],
|
||||
isRequired: false,
|
||||
severity: 'MEDIUM',
|
||||
},
|
||||
{
|
||||
id: 'liability_cap',
|
||||
category: 'LIABILITY',
|
||||
requirement: {
|
||||
de: 'Haftungsbegrenzung',
|
||||
en: 'Liability cap',
|
||||
},
|
||||
article: 'Vertragliche Vereinbarung',
|
||||
description: {
|
||||
de: 'Prüfung von Haftungsbegrenzungen und deren Auswirkungen.',
|
||||
en: 'Review of liability caps and their implications.',
|
||||
},
|
||||
checkPoints: [
|
||||
{ de: 'Haftungshöchstgrenze prüfen', en: 'Check liability cap' },
|
||||
{ de: 'Ausschluss von Vorsatz/grober Fahrlässigkeit', en: 'Exclusion of intent/gross negligence' },
|
||||
{ de: 'Freistellungsklauseln (Indemnity)', en: 'Indemnification clauses' },
|
||||
{ de: 'Versicherungsnachweis', en: 'Insurance proof' },
|
||||
],
|
||||
isRequired: false,
|
||||
severity: 'MEDIUM',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// GROUPED CHECKLISTS
|
||||
// ==========================================
|
||||
|
||||
export const CHECKLIST_GROUPS: ChecklistGroup[] = [
|
||||
{
|
||||
id: 'avv',
|
||||
name: { de: 'Art. 28 DSGVO - AVV Pflichtinhalte', en: 'Art. 28 GDPR - DPA Mandatory Content' },
|
||||
description: {
|
||||
de: 'Prüfung der Pflichtinhalte eines Auftragsverarbeitungsvertrags',
|
||||
en: 'Review of mandatory content of a Data Processing Agreement',
|
||||
},
|
||||
items: AVV_CHECKLIST,
|
||||
},
|
||||
{
|
||||
id: 'incident',
|
||||
name: { de: 'Incident Response', en: 'Incident Response' },
|
||||
description: {
|
||||
de: 'Prüfung der Regelungen zu Datenschutzverletzungen',
|
||||
en: 'Review of data breach provisions',
|
||||
},
|
||||
items: INCIDENT_CHECKLIST,
|
||||
},
|
||||
{
|
||||
id: 'transfer',
|
||||
name: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
|
||||
description: {
|
||||
de: 'Prüfung der Regelungen zu Drittlandtransfers',
|
||||
en: 'Review of third country transfer provisions',
|
||||
},
|
||||
items: TRANSFER_CHECKLIST,
|
||||
},
|
||||
{
|
||||
id: 'sla_liability',
|
||||
name: { de: 'SLA & Haftung', en: 'SLA & Liability' },
|
||||
description: {
|
||||
de: 'Prüfung von Service Levels und Haftungsregelungen',
|
||||
en: 'Review of service levels and liability provisions',
|
||||
},
|
||||
items: SLA_LIABILITY_CHECKLIST,
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get all required checklist items
|
||||
*/
|
||||
export function getRequiredChecklistItems(): ChecklistItem[] {
|
||||
return [
|
||||
...AVV_CHECKLIST,
|
||||
...INCIDENT_CHECKLIST,
|
||||
...TRANSFER_CHECKLIST,
|
||||
].filter((item) => item.isRequired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checklist items by category
|
||||
*/
|
||||
export function getChecklistItemsByCategory(category: FindingCategory): ChecklistItem[] {
|
||||
return [
|
||||
...AVV_CHECKLIST,
|
||||
...INCIDENT_CHECKLIST,
|
||||
...TRANSFER_CHECKLIST,
|
||||
...SLA_LIABILITY_CHECKLIST,
|
||||
].filter((item) => item.category === category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checklist item by ID
|
||||
*/
|
||||
export function getChecklistItemById(id: string): ChecklistItem | undefined {
|
||||
return [
|
||||
...AVV_CHECKLIST,
|
||||
...INCIDENT_CHECKLIST,
|
||||
...TRANSFER_CHECKLIST,
|
||||
...SLA_LIABILITY_CHECKLIST,
|
||||
].find((item) => item.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate compliance score based on checklist results
|
||||
*/
|
||||
export function calculateChecklistComplianceScore(
|
||||
results: Map<string, 'PASS' | 'PARTIAL' | 'FAIL' | 'NOT_CHECKED'>
|
||||
): number {
|
||||
const allItems = [
|
||||
...AVV_CHECKLIST,
|
||||
...INCIDENT_CHECKLIST,
|
||||
...TRANSFER_CHECKLIST,
|
||||
]
|
||||
|
||||
let totalWeight = 0
|
||||
let earnedScore = 0
|
||||
|
||||
for (const item of allItems) {
|
||||
const weight = item.severity === 'CRITICAL' ? 3 : item.severity === 'HIGH' ? 2 : 1
|
||||
const result = results.get(item.id) || 'NOT_CHECKED'
|
||||
|
||||
totalWeight += weight
|
||||
|
||||
switch (result) {
|
||||
case 'PASS':
|
||||
earnedScore += weight
|
||||
break
|
||||
case 'PARTIAL':
|
||||
earnedScore += weight * 0.5
|
||||
break
|
||||
case 'FAIL':
|
||||
case 'NOT_CHECKED':
|
||||
// No score
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return totalWeight > 0 ? Math.round((earnedScore / totalWeight) * 100) : 0
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
/**
|
||||
* Finding Types and Templates
|
||||
*
|
||||
* Pre-defined finding templates for contract reviews
|
||||
*/
|
||||
|
||||
import {
|
||||
FindingType,
|
||||
FindingCategory,
|
||||
FindingSeverity,
|
||||
LocalizedText,
|
||||
} from '../types'
|
||||
|
||||
export interface FindingTemplate {
|
||||
id: string
|
||||
type: FindingType
|
||||
category: FindingCategory
|
||||
severity: FindingSeverity
|
||||
title: LocalizedText
|
||||
description: LocalizedText
|
||||
recommendation: LocalizedText
|
||||
affectedRequirement: string
|
||||
triggerControls: string[]
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FINDING SEVERITY DEFINITIONS
|
||||
// ==========================================
|
||||
|
||||
export const SEVERITY_DEFINITIONS: Record<FindingSeverity, {
|
||||
label: LocalizedText
|
||||
description: LocalizedText
|
||||
responseTime: LocalizedText
|
||||
color: string
|
||||
}> = {
|
||||
LOW: {
|
||||
label: { de: 'Niedrig', en: 'Low' },
|
||||
description: {
|
||||
de: 'Geringfügige Abweichung ohne wesentliche Auswirkungen',
|
||||
en: 'Minor deviation without significant impact',
|
||||
},
|
||||
responseTime: {
|
||||
de: 'Bei nächster Vertragserneuerung',
|
||||
en: 'At next contract renewal',
|
||||
},
|
||||
color: 'blue',
|
||||
},
|
||||
MEDIUM: {
|
||||
label: { de: 'Mittel', en: 'Medium' },
|
||||
description: {
|
||||
de: 'Abweichung mit potenziellen Auswirkungen auf Compliance',
|
||||
en: 'Deviation with potential impact on compliance',
|
||||
},
|
||||
responseTime: {
|
||||
de: 'Innerhalb von 90 Tagen',
|
||||
en: 'Within 90 days',
|
||||
},
|
||||
color: 'yellow',
|
||||
},
|
||||
HIGH: {
|
||||
label: { de: 'Hoch', en: 'High' },
|
||||
description: {
|
||||
de: 'Erhebliche Abweichung mit Auswirkungen auf Datenschutz-Compliance',
|
||||
en: 'Significant deviation with impact on data protection compliance',
|
||||
},
|
||||
responseTime: {
|
||||
de: 'Innerhalb von 30 Tagen',
|
||||
en: 'Within 30 days',
|
||||
},
|
||||
color: 'orange',
|
||||
},
|
||||
CRITICAL: {
|
||||
label: { de: 'Kritisch', en: 'Critical' },
|
||||
description: {
|
||||
de: 'Schwerwiegende Abweichung - unmittelbarer Handlungsbedarf',
|
||||
en: 'Serious deviation - immediate action required',
|
||||
},
|
||||
responseTime: {
|
||||
de: 'Sofort / vor Vertragsabschluss',
|
||||
en: 'Immediately / before contract signing',
|
||||
},
|
||||
color: 'red',
|
||||
},
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FINDING TYPE DEFINITIONS
|
||||
// ==========================================
|
||||
|
||||
export const FINDING_TYPE_DEFINITIONS: Record<FindingType, {
|
||||
label: LocalizedText
|
||||
description: LocalizedText
|
||||
icon: string
|
||||
}> = {
|
||||
OK: {
|
||||
label: { de: 'Erfüllt', en: 'Fulfilled' },
|
||||
description: {
|
||||
de: 'Anforderung ist vollständig erfüllt',
|
||||
en: 'Requirement is fully met',
|
||||
},
|
||||
icon: 'check-circle',
|
||||
},
|
||||
GAP: {
|
||||
label: { de: 'Lücke', en: 'Gap' },
|
||||
description: {
|
||||
de: 'Anforderung fehlt oder ist unvollständig',
|
||||
en: 'Requirement is missing or incomplete',
|
||||
},
|
||||
icon: 'alert-circle',
|
||||
},
|
||||
RISK: {
|
||||
label: { de: 'Risiko', en: 'Risk' },
|
||||
description: {
|
||||
de: 'Potenzielles Risiko identifiziert',
|
||||
en: 'Potential risk identified',
|
||||
},
|
||||
icon: 'alert-triangle',
|
||||
},
|
||||
UNKNOWN: {
|
||||
label: { de: 'Unklar', en: 'Unknown' },
|
||||
description: {
|
||||
de: 'Nicht eindeutig bestimmbar',
|
||||
en: 'Cannot be clearly determined',
|
||||
},
|
||||
icon: 'help-circle',
|
||||
},
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FINDING TEMPLATES
|
||||
// ==========================================
|
||||
|
||||
export const FINDING_TEMPLATES: FindingTemplate[] = [
|
||||
// AVV_CONTENT - Weisungsgebundenheit
|
||||
{
|
||||
id: 'tpl-avv-instruction-missing',
|
||||
type: 'GAP',
|
||||
category: 'AVV_CONTENT',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'Weisungsgebundenheit fehlt',
|
||||
en: 'Instruction binding missing',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält keine Regelung zur Weisungsgebundenheit des Auftragsverarbeiters.',
|
||||
en: 'The contract does not contain a provision on the processor\'s instruction binding.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel, die den Auftragsverarbeiter verpflichtet, personenbezogene Daten nur auf dokumentierte Weisung des Verantwortlichen zu verarbeiten.',
|
||||
en: 'Add a clause obligating the processor to process personal data only on documented instructions from the controller.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. a DSGVO',
|
||||
triggerControls: ['VND-CON-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-avv-instruction-weak',
|
||||
type: 'RISK',
|
||||
category: 'AVV_CONTENT',
|
||||
severity: 'MEDIUM',
|
||||
title: {
|
||||
de: 'Weisungsgebundenheit unvollständig',
|
||||
en: 'Instruction binding incomplete',
|
||||
},
|
||||
description: {
|
||||
de: 'Die Regelung zur Weisungsgebundenheit ist vorhanden, aber es fehlt die Hinweispflicht bei rechtswidrigen Weisungen.',
|
||||
en: 'The instruction binding provision exists, but the obligation to notify of unlawful instructions is missing.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Pflicht des Auftragsverarbeiters, den Verantwortlichen unverzüglich zu informieren, wenn eine Weisung nach seiner Auffassung gegen Datenschutzrecht verstößt.',
|
||||
en: 'Add an obligation for the processor to immediately inform the controller if an instruction, in their opinion, violates data protection law.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. a DSGVO',
|
||||
triggerControls: ['VND-CON-01'],
|
||||
},
|
||||
|
||||
// AVV_CONTENT - TOM
|
||||
{
|
||||
id: 'tpl-avv-tom-missing',
|
||||
type: 'GAP',
|
||||
category: 'TOM',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'TOM-Anlage fehlt',
|
||||
en: 'TOM annex missing',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält keine technischen und organisatorischen Maßnahmen (TOM) als Anlage.',
|
||||
en: 'The contract does not contain technical and organizational measures (TOM) as an annex.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Fordern Sie eine detaillierte TOM-Anlage an, die die Maßnahmen gemäß Art. 32 DSGVO beschreibt.',
|
||||
en: 'Request a detailed TOM annex describing the measures according to Art. 32 GDPR.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. c DSGVO',
|
||||
triggerControls: ['VND-TOM-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-avv-tom-generic',
|
||||
type: 'RISK',
|
||||
category: 'TOM',
|
||||
severity: 'MEDIUM',
|
||||
title: {
|
||||
de: 'TOM zu unspezifisch',
|
||||
en: 'TOM too generic',
|
||||
},
|
||||
description: {
|
||||
de: 'Die TOM-Anlage enthält nur allgemeine Aussagen ohne konkrete Maßnahmen.',
|
||||
en: 'The TOM annex contains only general statements without specific measures.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Fordern Sie eine konkretere Beschreibung der Maßnahmen mit Bezug zum spezifischen Verarbeitungskontext an.',
|
||||
en: 'Request a more specific description of measures with reference to the specific processing context.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. c DSGVO',
|
||||
triggerControls: ['VND-TOM-01'],
|
||||
},
|
||||
|
||||
// SUBPROCESSOR
|
||||
{
|
||||
id: 'tpl-subprocessor-no-approval',
|
||||
type: 'GAP',
|
||||
category: 'SUBPROCESSOR',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'Keine Genehmigungspflicht für Unterauftragnehmer',
|
||||
en: 'No approval requirement for sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag regelt nicht, ob und wie der Einsatz von Unterauftragnehmern zu genehmigen ist.',
|
||||
en: 'The contract does not regulate whether and how the use of sub-processors must be approved.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel, die entweder eine spezifische oder allgemeine Genehmigung für Unterauftragnehmer vorsieht, einschließlich Informations- und Einspruchsrechten.',
|
||||
en: 'Add a clause providing either specific or general authorization for sub-processors, including information and objection rights.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. d DSGVO',
|
||||
triggerControls: ['VND-SUB-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-subprocessor-no-list',
|
||||
type: 'RISK',
|
||||
category: 'SUBPROCESSOR',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Keine Liste der Unterauftragnehmer',
|
||||
en: 'No list of sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Es liegt keine aktuelle Liste der eingesetzten Unterauftragnehmer vor.',
|
||||
en: 'There is no current list of sub-processors used.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Fordern Sie eine vollständige Liste aller Unterauftragnehmer mit Name, Sitz und Verarbeitungszweck an.',
|
||||
en: 'Request a complete list of all sub-processors with name, location, and processing purpose.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. d DSGVO',
|
||||
triggerControls: ['VND-SUB-02'],
|
||||
},
|
||||
|
||||
// INCIDENT
|
||||
{
|
||||
id: 'tpl-incident-no-notification',
|
||||
type: 'GAP',
|
||||
category: 'INCIDENT',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'Keine Meldepflicht bei Datenpannen',
|
||||
en: 'No notification obligation for data breaches',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält keine Regelung zur Meldung von Datenschutzverletzungen.',
|
||||
en: 'The contract does not contain a provision for reporting data breaches.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel, die den Auftragsverarbeiter verpflichtet, Datenschutzverletzungen unverzüglich (innerhalb von 24-48h) zu melden.',
|
||||
en: 'Add a clause obligating the processor to report data breaches without undue delay (within 24-48h).',
|
||||
},
|
||||
affectedRequirement: 'Art. 33 Abs. 2 DSGVO',
|
||||
triggerControls: ['VND-INC-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-incident-long-deadline',
|
||||
type: 'RISK',
|
||||
category: 'INCIDENT',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Zu lange Meldefrist',
|
||||
en: 'Notification deadline too long',
|
||||
},
|
||||
description: {
|
||||
de: 'Die vereinbarte Meldefrist für Datenschutzverletzungen ist zu lang (>72h), um die eigene Meldepflicht einhalten zu können.',
|
||||
en: 'The agreed notification deadline for data breaches is too long (>72h) to meet own notification obligations.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Verkürzen Sie die Meldefrist auf maximal 24-48 Stunden, um ausreichend Zeit für die eigene Meldung an die Aufsichtsbehörde zu haben.',
|
||||
en: 'Reduce the notification deadline to a maximum of 24-48 hours to have sufficient time for own notification to the supervisory authority.',
|
||||
},
|
||||
affectedRequirement: 'Art. 33 DSGVO',
|
||||
triggerControls: ['VND-INC-01'],
|
||||
},
|
||||
|
||||
// AUDIT_RIGHTS
|
||||
{
|
||||
id: 'tpl-audit-no-right',
|
||||
type: 'GAP',
|
||||
category: 'AUDIT_RIGHTS',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Kein Auditrecht vereinbart',
|
||||
en: 'No audit right agreed',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält kein Recht des Verantwortlichen auf Prüfungen und Inspektionen.',
|
||||
en: 'The contract does not contain a right of the controller to audits and inspections.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie ein ausdrückliches Recht auf Vor-Ort-Inspektionen und die Einsicht in relevante Unterlagen.',
|
||||
en: 'Add an explicit right to on-site inspections and access to relevant documents.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. h DSGVO',
|
||||
triggerControls: ['VND-AUD-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-audit-restricted',
|
||||
type: 'RISK',
|
||||
category: 'AUDIT_RIGHTS',
|
||||
severity: 'MEDIUM',
|
||||
title: {
|
||||
de: 'Auditrecht eingeschränkt',
|
||||
en: 'Audit right restricted',
|
||||
},
|
||||
description: {
|
||||
de: 'Das Auditrecht ist durch unangemessene Einschränkungen (z.B. sehr lange Vorlaufzeit, Ausschluss von Vor-Ort-Inspektionen) begrenzt.',
|
||||
en: 'The audit right is limited by unreasonable restrictions (e.g., very long notice period, exclusion of on-site inspections).',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Verhandeln Sie angemessene Bedingungen für Audits (max. 30 Tage Vorlaufzeit, Möglichkeit zur Vor-Ort-Inspektion).',
|
||||
en: 'Negotiate reasonable audit conditions (max. 30 days notice, possibility for on-site inspection).',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. h DSGVO',
|
||||
triggerControls: ['VND-AUD-01'],
|
||||
},
|
||||
|
||||
// DELETION
|
||||
{
|
||||
id: 'tpl-deletion-no-clause',
|
||||
type: 'GAP',
|
||||
category: 'DELETION',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'Keine Lösch-/Rückgaberegelung',
|
||||
en: 'No deletion/return clause',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag regelt nicht, was mit den Daten nach Vertragsende geschieht.',
|
||||
en: 'The contract does not regulate what happens to the data after contract termination.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel zur Löschung oder Rückgabe aller personenbezogenen Daten nach Vertragsende (max. 30 Tage).',
|
||||
en: 'Add a clause for deletion or return of all personal data after contract end (max. 30 days).',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. g DSGVO',
|
||||
triggerControls: ['VND-DEL-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-deletion-no-confirmation',
|
||||
type: 'RISK',
|
||||
category: 'DELETION',
|
||||
severity: 'MEDIUM',
|
||||
title: {
|
||||
de: 'Keine Löschbestätigung vorgesehen',
|
||||
en: 'No deletion confirmation provided',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag sieht keine Bestätigung der Löschung durch den Auftragsverarbeiter vor.',
|
||||
en: 'The contract does not provide for confirmation of deletion by the processor.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Pflicht zur schriftlichen Bestätigung der vollständigen Löschung.',
|
||||
en: 'Add an obligation for written confirmation of complete deletion.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. g DSGVO',
|
||||
triggerControls: ['VND-DEL-01'],
|
||||
},
|
||||
|
||||
// TRANSFER
|
||||
{
|
||||
id: 'tpl-transfer-no-basis',
|
||||
type: 'GAP',
|
||||
category: 'TRANSFER',
|
||||
severity: 'CRITICAL',
|
||||
title: {
|
||||
de: 'Drittlandtransfer ohne Rechtsgrundlage',
|
||||
en: 'Third country transfer without legal basis',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag erlaubt oder impliziert Transfers in Drittländer ohne geeignete Garantien.',
|
||||
en: 'The contract allows or implies transfers to third countries without appropriate safeguards.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Vereinbaren Sie geeignete Garantien (SCC, BCR) oder beschränken Sie die Verarbeitung auf EU/EWR.',
|
||||
en: 'Agree on appropriate safeguards (SCC, BCR) or restrict processing to EU/EEA.',
|
||||
},
|
||||
affectedRequirement: 'Art. 44-49 DSGVO',
|
||||
triggerControls: ['VND-TRF-01'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-transfer-old-scc',
|
||||
type: 'RISK',
|
||||
category: 'TRANSFER',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Veraltete Standardvertragsklauseln',
|
||||
en: 'Outdated Standard Contractual Clauses',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag verwendet die alten SCC (vor 2021), die nicht mehr gültig sind.',
|
||||
en: 'The contract uses old SCC (pre-2021) that are no longer valid.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Aktualisieren Sie auf die SCC 2021 (Durchführungsbeschluss (EU) 2021/914).',
|
||||
en: 'Update to SCC 2021 (Implementing Decision (EU) 2021/914).',
|
||||
},
|
||||
affectedRequirement: 'Art. 46 Abs. 2 lit. c DSGVO',
|
||||
triggerControls: ['VND-TRF-02'],
|
||||
},
|
||||
|
||||
// LIABILITY
|
||||
{
|
||||
id: 'tpl-liability-excessive-cap',
|
||||
type: 'RISK',
|
||||
category: 'LIABILITY',
|
||||
severity: 'MEDIUM',
|
||||
title: {
|
||||
de: 'Unangemessene Haftungsbegrenzung',
|
||||
en: 'Inappropriate liability cap',
|
||||
},
|
||||
description: {
|
||||
de: 'Die Haftungsbegrenzung ist sehr niedrig und könnte bei Datenschutzverletzungen problematisch sein.',
|
||||
en: 'The liability cap is very low and could be problematic in case of data protection violations.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Prüfen Sie, ob die Haftungsbegrenzung angemessen ist. Erwägen Sie eine Ausnahme für Datenschutzverletzungen oder eine höhere Obergrenze.',
|
||||
en: 'Check if the liability cap is appropriate. Consider an exception for data protection violations or a higher limit.',
|
||||
},
|
||||
affectedRequirement: 'Vertragliche Vereinbarung',
|
||||
triggerControls: [],
|
||||
},
|
||||
|
||||
// DATA_SUBJECT_RIGHTS
|
||||
{
|
||||
id: 'tpl-dsr-no-support',
|
||||
type: 'GAP',
|
||||
category: 'DATA_SUBJECT_RIGHTS',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Keine Unterstützung bei Betroffenenrechten',
|
||||
en: 'No support for data subject rights',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält keine Regelung zur Unterstützung bei der Erfüllung von Betroffenenrechten.',
|
||||
en: 'The contract does not contain a provision for support in fulfilling data subject rights.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel zur Unterstützung bei Auskunft, Berichtigung, Löschung und anderen Betroffenenrechten.',
|
||||
en: 'Add a clause for support with access, rectification, deletion, and other data subject rights.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. e DSGVO',
|
||||
triggerControls: ['VND-DSR-01'],
|
||||
},
|
||||
|
||||
// CONFIDENTIALITY
|
||||
{
|
||||
id: 'tpl-confidentiality-missing',
|
||||
type: 'GAP',
|
||||
category: 'CONFIDENTIALITY',
|
||||
severity: 'HIGH',
|
||||
title: {
|
||||
de: 'Keine Vertraulichkeitsverpflichtung',
|
||||
en: 'No confidentiality obligation',
|
||||
},
|
||||
description: {
|
||||
de: 'Der Vertrag enthält keine Verpflichtung zur Vertraulichkeit der Mitarbeiter.',
|
||||
en: 'The contract does not contain an obligation for employee confidentiality.',
|
||||
},
|
||||
recommendation: {
|
||||
de: 'Ergänzen Sie eine Klausel, die die Verpflichtung der Mitarbeiter zur Vertraulichkeit sicherstellt.',
|
||||
en: 'Add a clause ensuring the obligation of employees to confidentiality.',
|
||||
},
|
||||
affectedRequirement: 'Art. 28 Abs. 3 lit. b DSGVO',
|
||||
triggerControls: ['VND-CON-02'],
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get finding template by ID
|
||||
*/
|
||||
export function getFindingTemplateById(id: string): FindingTemplate | undefined {
|
||||
return FINDING_TEMPLATES.find((t) => t.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get finding templates by category
|
||||
*/
|
||||
export function getFindingTemplatesByCategory(category: FindingCategory): FindingTemplate[] {
|
||||
return FINDING_TEMPLATES.filter((t) => t.category === category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get finding templates by type
|
||||
*/
|
||||
export function getFindingTemplatesByType(type: FindingType): FindingTemplate[] {
|
||||
return FINDING_TEMPLATES.filter((t) => t.type === type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get finding templates by severity
|
||||
*/
|
||||
export function getFindingTemplatesBySeverity(severity: FindingSeverity): FindingTemplate[] {
|
||||
return FINDING_TEMPLATES.filter((t) => t.severity === severity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity color class
|
||||
*/
|
||||
export function getSeverityColorClass(severity: FindingSeverity): string {
|
||||
return SEVERITY_DEFINITIONS[severity].color
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort findings by severity (critical first)
|
||||
*/
|
||||
export function sortFindingsBySeverity<T extends { severity: FindingSeverity }>(
|
||||
findings: T[]
|
||||
): T[] {
|
||||
const order: Record<FindingSeverity, number> = {
|
||||
CRITICAL: 0,
|
||||
HIGH: 1,
|
||||
MEDIUM: 2,
|
||||
LOW: 3,
|
||||
}
|
||||
|
||||
return [...findings].sort((a, b) => order[a.severity] - order[b.severity])
|
||||
}
|
||||
|
||||
/**
|
||||
* Count findings by severity
|
||||
*/
|
||||
export function countFindingsBySeverity<T extends { severity: FindingSeverity }>(
|
||||
findings: T[]
|
||||
): Record<FindingSeverity, number> {
|
||||
return findings.reduce(
|
||||
(acc, f) => {
|
||||
acc[f.severity] = (acc[f.severity] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{ LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 } as Record<FindingSeverity, number>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get overall severity from list of findings
|
||||
*/
|
||||
export function getOverallSeverity(findings: { severity: FindingSeverity }[]): FindingSeverity {
|
||||
if (findings.some((f) => f.severity === 'CRITICAL')) return 'CRITICAL'
|
||||
if (findings.some((f) => f.severity === 'HIGH')) return 'HIGH'
|
||||
if (findings.some((f) => f.severity === 'MEDIUM')) return 'MEDIUM'
|
||||
return 'LOW'
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Contract Review exports
|
||||
*/
|
||||
|
||||
export * from './analyzer'
|
||||
export * from './checklists'
|
||||
export * from './findings'
|
||||
Reference in New Issue
Block a user