Install any skill in seconds. Free to start, no credit card required.
Get Started Free →GDPR compliance - data subject rights, lawful basis, DPIA, privacy by design, breach notification, consent management, cross-border transfers, PII masking
.claude/skills/gdpr-compliance/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 200% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 236% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 166% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 172% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 208% | 0% |
| Right | Article | SLA | Implementation | |-------|---------|-----|----------------| | Right of Access | Art. 15 | 30 gun | Data export endpoint | | Right to Rectification | Art. 16 | 30 gun | Profile edit + audit trail | | Right to Erasure | Art. 17 | 30 gun | Cascading delete + anonymize | | Right to Restriction | Art. 18 | 30 gun | Processing flag on record | | Right to Portability | Art. 20 | 30 gun | Machine-readable export (JSON/CSV) | | Right to Object | Art. 21 | 30 gun | Opt-out mechanism | | Automated Decision-Making | Art. 22 | 30 gun | Human review override |
typescriptinterface DSRRequest { id: string; type: 'access' | 'rectification' | 'erasure' | 'restriction' | 'portability' | 'objection'; subjectId: string; verifiedIdentity: boolean; receivedAt: Date; deadline: Date; // receivedAt + 30 gun status: 'received' | 'verified' | 'processing' | 'completed' | 'rejected'; reason?: string; } async function handleDSR(request: DSRRequest): Promise<DSRResponse> { // Step 1: Identity verification ZORUNLU if (!request.verifiedIdentity) { return { status: 'rejected', reason: 'Identity not verified' }; } // Step 2: Check deadline const daysRemaining = differenceInDays(request.deadline, new Date()); if (daysRemaining <= 5) { await alertDPO({ type: 'dsr_deadline_approaching', request }); } // Step 3: Process by type switch (request.type) { case 'access': return await generateDataExport(request.subjectId); case 'erasure': return await executeErasure(request.subjectId); case 'portability': return await generatePortableExport(request.subjectId, 'json'); case 'rectification': return await updateSubjectData(request.subjectId, request.corrections); case 'restriction': return await restrictProcessing(request.subjectId); case 'objection': return await recordObjection(request.subjectId, request.reason); } }
typescriptasync function executeErasure(subjectId: string): Promise<ErasureResult> { const erasureLog: ErasureStep[] = []; await db.transaction(async (tx) => { // 1. Anonymize user record (yasal zorunluluklar haric) await tx.users.update({ where: { id: subjectId }, data: { email: `erased-${hash(subjectId)}@deleted.local`, name: 'Erased User', phone: null, address: null, dateOfBirth: null, deletedAt: new Date(), }, }); erasureLog.push({ table: 'users', action: 'anonymized' }); // 2. Delete personal messages const deletedMessages = await tx.messages.deleteMany({ where: { userId: subjectId }, }); erasureLog.push({ table: 'messages', action: 'deleted', count: deletedMessages.count }); // 3. Delete sessions and tokens await tx.sessions.deleteMany({ where: { userId: subjectId } }); await tx.refreshTokens.deleteMany({ where: { userId: subjectId } }); erasureLog.push({ table: 'sessions', action: 'deleted' }); // 4. Anonymize audit logs (log kaydi kalir, kisi bilgisi gider) await tx.auditLogs.updateMany({ where: { actorId: subjectId }, data: { actorId: 'erased', actorEmail: 'erased' }, }); erasureLog.push({ table: 'auditLogs', action: 'anonymized' }); // 5. Notify third-party processors await notifyProcessors(subjectId, 'erasure'); // 6. Record erasure for compliance await tx.erasureRecords.create({ data: { subjectHash: hash(subjectId), erasedAt: new Date(), systems: erasureLog, }, }); }); return { success: true, steps: erasureLog }; }
| Basis | Article | When to Use | Example | |-------|---------|-------------|---------| | Consent | Art. 6(1)(a) | Optional processing, marketing | Newsletter signup | | Contract | Art. 6(1)(b) | Necessary for service delivery | Order processing | | Legal obligation | Art. 6(1)(c) | Required by law | Tax records | | Vital interests | Art. 6(1)(d) | Life-threatening situations | Emergency contact | | Public interest | Art. 6(1)(e) | Public authority tasks | Government services | | Legitimate interest | Art. 6(1)(f) | Business need, balanced with rights | Fraud prevention |
markdown## Data Protection Impact Assessment **Project:** [proje adi] **Date:** [tarih] **DPO Review:** [evet/hayir] ### 1. Processing Description - What data: [veri turleri] - Why: [amac] - How: [islem yontemi] - Who: [erisim kimlerde] - How long: [saklama suresi] ### 2. Necessity & Proportionality - Lawful basis: [hukuki dayanak] - Data minimization: [minimum veri mi?] - Purpose limitation: [amac sinirli mi?] - Storage limitation: [saklama suresi uygun mu?] ### 3. Risk Assessment | Risk | Likelihood | Impact | Severity | Mitigation | |------|-----------|--------|----------|------------| | Unauthorized access | [L/M/H] | [L/M/H] | [L/M/H] | [onlem] | | Data breach | [L/M/H] | [L/M/H] | [L/M/H] | [onlem] | | Purpose creep | [L/M/H] | [L/M/H] | [L/M/H] | [onlem] | ### 4. Measures - [ ] Encryption at rest and in transit - [ ] Access controls (RBAC) - [ ] Audit logging - [ ] Data minimization applied - [ ] Retention policy configured - [ ] Pseudonymization where possible ### 5. DPO Sign-off Date: [tarih] | Approved: [evet/hayir]
typescript// YANLIS: Tum veriyi topla const user = await db.users.findUnique({ where: { id }, // Returns everything including SSN, DOB, etc. }); // DOGRU: Sadece gerekli alanlari sec const user = await db.users.findUnique({ where: { id }, select: { id: true, name: true, email: true, // SSN, DOB, etc. secilmez }, });
typescriptfunction maskPII(data: Record<string, unknown>): Record<string, unknown> { const piiFields: Record<string, (val: string) => string> = { email: (v) => v.replace(/^(.{2}).*(@.*)$/, '$1***$2'), phone: (v) => v.replace(/^(.{3}).*(.{2})$/, '$1*****$2'), ssn: (v) => `***-**-${v.slice(-4)}`, creditCard: (v) => `****-****-****-${v.slice(-4)}`, ip: (v) => v.replace(/\.\d+$/, '.xxx'), name: (v) => `${v.charAt(0)}${'*'.repeat(v.length - 1)}`, }; return Object.fromEntries( Object.entries(data).map(([key, value]) => { const masker = piiFields[key]; if (masker && typeof value === 'string') { return [key, masker(value)]; } return [key, value]; }) ); }
typescriptimport { createCipheriv, createDecipheriv, randomBytes } from 'crypto'; const ALGORITHM = 'aes-256-gcm'; function encryptPII(plaintext: string, key: Buffer): EncryptedData { const iv = randomBytes(16); const cipher = createCipheriv(ALGORITHM, key, iv); const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const authTag = cipher.getAuthTag(); return { ciphertext: encrypted.toString('base64'), iv: iv.toString('base64'), authTag: authTag.toString('base64'), }; } function decryptPII(data: EncryptedData, key: Buffer): string { const decipher = createDecipheriv( ALGORITHM, key, Buffer.from(data.iv, 'base64') ); decipher.setAuthTag(Buffer.from(data.authTag, 'base64')); return decipher.update(data.ciphertext, 'base64', 'utf8') + decipher.final('utf8'); }
0h → Breach detected
→ Incident response team activated
→ Containment measures started
24h → Impact assessment completed
→ Affected data subjects identified
→ Breach severity classified
48h → Notification to DPA prepared
→ Data subject notification prepared (if high risk)
72h → DEADLINE: DPA notification submitted (Art. 33)
→ Data subject notification sent if required (Art. 34)typescriptinterface BreachAssessment { detectedAt: Date; nature: string; // What happened categoriesAffected: string[]; // Data types exposed subjectsAffected: number; // How many people likelyConsequences: string[]; // Potential harm measuresTaken: string[]; // Containment actions riskLevel: 'low' | 'medium' | 'high'; notifyDPA: boolean; // Required unless low risk notifySubjects: boolean; // Required if high risk dpaNotificationDeadline: Date; // detectedAt + 72h } function assessBreachRisk(breach: BreachAssessment): string { const highRiskFactors = [ breach.categoriesAffected.includes('financial'), breach.categoriesAffected.includes('health'), breach.categoriesAffected.includes('credentials'), breach.subjectsAffected > 1000, ]; const highRiskCount = highRiskFactors.filter(Boolean).length; if (highRiskCount >= 2) return 'high'; if (highRiskCount >= 1) return 'medium'; return 'low'; }
typescriptinterface ConsentRecord { subjectId: string; purpose: string; // 'marketing_email', 'analytics', 'profiling' granted: boolean; grantedAt: Date | null; revokedAt: Date | null; method: 'explicit_opt_in' | 'form' | 'api'; version: string; // Privacy policy version ip?: string; evidence: string; // What they agreed to (exact text) } async function recordConsent(consent: ConsentRecord): Promise<void> { // Consent must be: // - Freely given (no pre-ticked boxes) // - Specific (per purpose) // - Informed (clear language) // - Unambiguous (affirmative action) await db.consents.create({ data: consent }); await auditLog({ action: `consent.${consent.granted ? 'granted' : 'revoked'}`, actor: consent.subjectId, resource: consent.purpose, details: { version: consent.version }, }); }
| Anti-Pattern | GDPR Violation | Dogru Yol | |-------------|---------------|-----------| | Pre-ticked checkboxes | Art. 7 - not freely given | Unchecked by default | | Bundled consent | Art. 7 - not specific | Separate consent per purpose | | Dark patterns (confusing UI) | Art. 7 - not informed | Clear, plain language | | No withdrawal mechanism | Art. 7(3) - easy withdrawal | One-click unsubscribe | | Consent wall (block access) | Art. 7 - not freely given | Allow access without consent | | Implicit consent | Art. 4(11) - not unambiguous | Explicit opt-in required |
| Mechanism | When to Use | Complexity | |-----------|-------------|------------| | Adequacy decision | EU-approved country (UK, Japan, etc.) | Low | | Standard Contractual Clauses (SCCs) | US, India, etc. | Medium | | Binding Corporate Rules (BCRs) | Intra-group transfers | High | | Explicit consent | One-off transfers | Low | | EU-US Data Privacy Framework | US companies certified | Medium |
markdown## Transfer Impact Assessment **From:** [EU entity] **To:** [Non-EU entity, country] **Mechanism:** [SCC / BCR / Adequacy / Consent] ### Data Transferred - Categories: [personal data types] - Volume: [approximate records/month] - Frequency: [continuous / batch / ad-hoc] ### Recipient Country Assessment - Privacy laws: [adequacy level] - Government access: [surveillance risk] - Legal remedies: [available to EU subjects?] ### Supplementary Measures - [ ] End-to-end encryption (keys retained in EU) - [ ] Pseudonymization before transfer - [ ] Access controls at destination - [ ] Contractual prohibition on government disclosure - [ ] Regular compliance audits
| Data Category | Retention Period | Legal Basis | After Expiry | |--------------|-----------------|-------------|-------------| | Active user data | Account lifetime | Contract | Anonymize | | Inactive user data | 2 yil inactivity | Legitimate interest | Delete | | Transaction records | 7 yil | Legal obligation (tax) | Archive encrypted | | Marketing consent | Until revoked | Consent | Delete | | Support tickets | 3 yil after resolution | Legitimate interest | Anonymize | | Access logs | 1 yil | Legitimate interest | Delete | | Analytics (aggregated) | Indefinite | Legitimate interest | N/A (no PII) |
typescriptasync function enforceRetentionPolicies(): Promise<RetentionReport> { const report: RetentionReport = { deletedCount: 0, anonymizedCount: 0 }; // Delete inactive accounts (2 yil) const inactiveThreshold = subYears(new Date(), 2); const inactiveUsers = await db.users.findMany({ where: { lastActiveAt: { lt: inactiveThreshold }, deletedAt: null }, }); for (const user of inactiveUsers) { await executeErasure(user.id); report.deletedCount++; } // Anonymize old support tickets (3 yil) const ticketThreshold = subYears(new Date(), 3); const oldTickets = await db.tickets.updateMany({ where: { resolvedAt: { lt: ticketThreshold }, anonymized: false }, data: { userEmail: 'anonymized', userName: 'anonymized', anonymized: true }, }); report.anonymizedCount += oldTickets.count; // Delete old access logs (1 yil) const logThreshold = subYears(new Date(), 1); await db.accessLogs.deleteMany({ where: { createdAt: { lt: logThreshold } }, }); return report; }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 16,126 | 16,168 | +0% | 1 | 1 | 0% | 3,609 | 8,054 | +123% | 0 | 0 | — |
case-02 | fail→pass | 12,048 | 15,383 | +28% | 1 | 1 | 0% | 2,565 | 7,683 | +200% | 0 | 0 | — |
case-03 | fail→pass | 9,656 | 11,519 | +19% | 1 | 1 | 0% | 2,035 | 6,833 | +236% | 0 | 0 | — |
case-04 | fail→fail | 21,379 | 16,892 | -21% | 1 | 1 | 0% | 4,532 | 8,349 | +84% | 0 | 0 | — |
case-05 | fail→fail | 15,713 | 16,824 | +7% | 1 | 1 | 0% | 3,168 | 7,673 | +142% | 0 | 0 | — |
case-06 | fail→pass | 13,040 | 12,260 | -6% | 1 | 1 | 0% | 2,473 | 6,576 | +166% | 0 | 0 | — |
case-07 | fail→fail | 16,799 | 16,530 | -2% | 1 | 1 | 0% | 3,484 | 7,978 | +129% | 0 | 0 | — |
case-08 | pass→pass | 11,337 | 12,720 | +12% | 1 | 1 | 0% | 1,985 | 6,155 | +210% | 0 | 0 | — |
case-09 | fail→pass | 16,592 | 18,998 | +15% | 1 | 1 | 0% | 2,800 | 7,615 | +172% | 0 | 0 | — |
case-10 | fail→pass | 10,369 | 9,184 | -11% | 1 | 1 | 0% | 1,906 | 5,867 | +208% | 0 | 0 | — |
case-11 | pass→pass | 13,441 | 13,939 | +4% | 1 | 1 | 0% | 2,547 | 7,104 | +179% | 0 | 0 | — |
case-12 | pass→pass | 14,081 | 12,423 | -12% | 1 | 1 | 0% | 2,784 | 6,466 | +132% | 0 | 0 | — |
case-13 | fail→pass | 19,200 | 17,981 | -6% | 1 | 1 | 0% | 3,384 | 7,499 | +122% | 0 | 0 | — |
case-14 | pass→pass | 18,354 | 18,643 | +2% | 1 | 1 | 0% | 3,043 | 7,629 | +151% | 0 | 0 | — |
case-15 | fail→pass | 19,207 | 15,031 | -22% | 1 | 1 | 0% | 3,960 | 7,425 | +88% | 0 | 0 | — |
case-16 | fail→fail | 20,163 | 18,391 | -9% | 1 | 1 | 0% | 3,311 | 7,994 | +141% | 0 | 0 | — |
case-17 | fail→fail | 16,604 | 12,989 | -22% | 1 | 1 | 0% | 2,986 | 6,648 | +123% | 0 | 0 | — |
case-18 | pass→pass | 14,821 | 20,119 | +36% | 1 | 1 | 0% | 2,636 | 7,775 | +195% | 0 | 0 | — |
case-19 | pass→pass | 16,812 | 22,410 | +33% | 1 | 1 | 0% | 2,998 | 8,079 | +169% | 0 | 0 | — |
case-20 | pass→pass | 19,088 | 21,914 | +15% | 1 | 1 | 0% | 3,496 | 8,816 | +152% | 0 | 0 | — |
case-21 | pass→pass | 17,509 | 20,862 | +19% | 1 | 1 | 0% | 3,339 | 8,290 | +148% | 0 | 0 | — |
case-22 | pass→pass | 19,921 | 25,307 | +27% | 1 | 1 | 0% | 3,899 | 9,276 | +138% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/29/2026 | +17% |
Other measured skills in the registry, with their headline benchmark lift.