Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Implement GDPR-compliant data handling: consent management, data subject rights, and PII controls.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: gdpr-data-handling3description: Implement GDPR-compliant data handling with consent management, data subject rights, and privacy by design. Use when building systems that process EU personal data, implementing privacy controls, or conducting GDPR compliance reviews.4---56# GDPR Data Handling78Practical implementation guide for GDPR-compliant data processing, consent management, and privacy controls.910## When to Use This Skill1112- Building systems that process EU personal data13- Implementing consent management14- Handling data subject requests (DSRs)15- Conducting GDPR compliance reviews16- Designing privacy-first architectures17- Creating data processing agreements1819## Core Concepts2021### 1. Personal Data Categories2223| Category | Examples | Protection Level |24| ---------------------- | --------------------------- | ------------------ |25| **Basic** | Name, email, phone | Standard |26| **Sensitive (Art. 9)** | Health, religion, ethnicity | Explicit consent |27| **Criminal (Art. 10)** | Convictions, offenses | Official authority |28| **Children's** | Under 16 data | Parental consent |2930### 2. Legal Bases for Processing3132```33Article 6 - Lawful Bases:34├── Consent: Freely given, specific, informed35├── Contract: Necessary for contract performance36├── Legal Obligation: Required by law37├── Vital Interests: Protecting someone's life38├── Public Interest: Official functions39└── Legitimate Interest: Balanced against rights40```4142### 3. Data Subject Rights4344```45Right to Access (Art. 15) ─┐46Right to Rectification (Art. 16) │47Right to Erasure (Art. 17) │ Must respond48Right to Restrict (Art. 18) │ within 1 month49Right to Portability (Art. 20) │50Right to Object (Art. 21) ─┘51```5253## Implementation Patterns5455### Pattern 1: Consent Management5657```javascript58// Consent data model59const consentSchema = {60userId: String,61consents: [62{63purpose: String, // 'marketing', 'analytics', etc.64granted: Boolean,65timestamp: Date,66source: String, // 'web_form', 'api', etc.67version: String, // Privacy policy version68ipAddress: String, // For proof69userAgent: String, // For proof70},71],72auditLog: [73{74action: String, // 'granted', 'withdrawn', 'updated'75purpose: String,76timestamp: Date,77source: String,78},79],80};8182// Consent service83class ConsentManager {84async recordConsent(userId, purpose, granted, metadata) {85const consent = {86purpose,87granted,88timestamp: new Date(),89source: metadata.source,90version: await this.getCurrentPolicyVersion(),91ipAddress: metadata.ipAddress,92userAgent: metadata.userAgent,93};9495// Store consent96await this.db.consents.updateOne(97{ userId },98{99$push: {100consents: consent,101auditLog: {102action: granted ? "granted" : "withdrawn",103purpose,104timestamp: consent.timestamp,105source: metadata.source,106},107},108},109{ upsert: true },110);111112// Emit event for downstream systems113await this.eventBus.emit("consent.changed", {114userId,115purpose,116granted,117timestamp: consent.timestamp,118});119}120121async hasConsent(userId, purpose) {122const record = await this.db.consents.findOne({ userId });123if (!record) return false;124125const latestConsent = record.consents126.filter((c) => c.purpose === purpose)127.sort((a, b) => b.timestamp - a.timestamp)[0];128129return latestConsent?.granted === true;130}131132async getConsentHistory(userId) {133const record = await this.db.consents.findOne({ userId });134return record?.auditLog || [];135}136}137```138139```html140<!-- GDPR-compliant consent UI -->141<div class="consent-banner" role="dialog" aria-labelledby="consent-title">142<h2 id="consent-title">Cookie Preferences</h2>143144<p>145We use cookies to improve your experience. Select your preferences below.146</p>147148<form id="consent-form">149<!-- Necessary - always on, no consent needed -->150<div class="consent-category">151<input type="checkbox" id="necessary" checked disabled />152<label for="necessary">153<strong>Necessary</strong>154<span>Required for the website to function. Cannot be disabled.</span>155</label>156</div>157158<!-- Analytics - requires consent -->159<div class="consent-category">160<input type="checkbox" id="analytics" name="analytics" />161<label for="analytics">162<strong>Analytics</strong>163<span>Help us understand how you use our site.</span>164</label>165</div>166167<!-- Marketing - requires consent -->168<div class="consent-category">169<input type="checkbox" id="marketing" name="marketing" />170<label for="marketing">171<strong>Marketing</strong>172<span>Personalized ads based on your interests.</span>173</label>174</div>175176<div class="consent-actions">177<button type="button" id="accept-all">Accept All</button>178<button type="button" id="reject-all">Reject All</button>179<button type="submit">Save Preferences</button>180</div>181182<p class="consent-links">183<a href="/privacy-policy">Privacy Policy</a> |184<a href="/cookie-policy">Cookie Policy</a>185</p>186</form>187</div>188```189190### Pattern 2: Data Subject Access Request (DSAR)191192```python193from datetime import datetime, timedelta194from typing import Dict, List, Optional195import json196197class DSARHandler:198"""Handle Data Subject Access Requests."""199200RESPONSE_DEADLINE_DAYS = 30201EXTENSION_ALLOWED_DAYS = 60 # For complex requests202203def __init__(self, data_sources: List['DataSource']):204self.data_sources = data_sources205206async def submit_request(207self,208request_type: str, # 'access', 'erasure', 'rectification', 'portability'209user_id: str,210verified: bool,211details: Optional[Dict] = None212) -> str:213"""Submit a new DSAR."""214request = {215'id': self.generate_request_id(),216'type': request_type,217'user_id': user_id,218'status': 'pending_verification' if not verified else 'processing',219'submitted_at': datetime.utcnow(),220'deadline': datetime.utcnow() + timedelta(days=self.RESPONSE_DEADLINE_DAYS),221'details': details or {},222'audit_log': [{223'action': 'submitted',224'timestamp': datetime.utcnow(),225'details': 'Request received'226}]227}228229await self.db.dsar_requests.insert_one(request)230await self.notify_dpo(request)231232return request['id']233234async def process_access_request(self, request_id: str) -> Dict:235"""Process a data access request."""236request = await self.get_request(request_id)237238if request['type'] != 'access':239raise ValueError("Not an access request")240241# Collect data from all sources242user_data = {}243for source in self.data_sources:244try:245data = await source.get_user_data(request['user_id'])246user_data[source.name] = data247except Exception as e:248user_data[source.name] = {'error': str(e)}249250# Format response251response = {252'request_id': request_id,253'generated_at': datetime.utcnow().isoformat(),254'data_categories': list(user_data.keys()),255'data': user_data,256'retention_info': await self.get_retention_info(),257'processing_purposes': await self.get_processing_purposes(),258'third_party_recipients': await self.get_recipients()259}260261# Update request status262await self.update_request(request_id, 'completed', response)263264return response265266async def process_erasure_request(self, request_id: str) -> Dict:267"""Process a right to erasure request."""268request = await self.get_request(request_id)269270if request['type'] != 'erasure':271raise ValueError("Not an erasure request")272273results = {}274exceptions = []275276for source in self.data_sources:277try:278# Check for legal exceptions279can_delete, reason = await source.can_delete(request['user_id'])280281if can_delete:282await source.delete_user_data(request['user_id'])283results[source.name] = 'deleted'284else:285exceptions.append({286'source': source.name,287'reason': reason # e.g., 'legal retention requirement'288})289results[source.name] = f'retained: {reason}'290except Exception as e:291results[source.name] = f'error: {str(e)}'292293response = {294'request_id': request_id,295'completed_at': datetime.utcnow().isoformat(),296'results': results,297'exceptions': exceptions298}299300await self.update_request(request_id, 'completed', response)301302return response303304async def process_portability_request(self, request_id: str) -> bytes:305"""Generate portable data export."""306request = await self.get_request(request_id)307user_data = await self.process_access_request(request_id)308309# Convert to machine-readable format (JSON)310portable_data = {311'export_date': datetime.utcnow().isoformat(),312'format_version': '1.0',313'data': user_data['data']314}315316return json.dumps(portable_data, indent=2, default=str).encode()317```318319### Pattern 3: Data Retention320321```python322from datetime import datetime, timedelta323from enum import Enum324325class RetentionBasis(Enum):326CONSENT = "consent"327CONTRACT = "contract"328LEGAL_OBLIGATION = "legal_obligation"329LEGITIMATE_INTEREST = "legitimate_interest"330331class DataRetentionPolicy:332"""Define and enforce data retention policies."""333334POLICIES = {335'user_account': {336'retention_period_days': 365 * 3, # 3 years after last activity337'basis': RetentionBasis.CONTRACT,338'trigger': 'last_activity_date',339'archive_before_delete': True340},341'transaction_records': {342'retention_period_days': 365 * 7, # 7 years for tax343'basis': RetentionBasis.LEGAL_OBLIGATION,344'trigger': 'transaction_date',345'archive_before_delete': True,346'legal_reference': 'Tax regulations require 7 year retention'347},348'marketing_consent': {349'retention_period_days': 365 * 2, # 2 years350'basis': RetentionBasis.CONSENT,351'trigger': 'consent_date',352'archive_before_delete': False353},354'support_tickets': {355'retention_period_days': 365 * 2,356'basis': RetentionBasis.LEGITIMATE_INTEREST,357'trigger': 'ticket_closed_date',358'archive_before_delete': True359},360'analytics_data': {361'retention_period_days': 365, # 1 year362'basis': RetentionBasis.CONSENT,363'trigger': 'collection_date',364'archive_before_delete': False,365'anonymize_instead': True366}367}368369async def apply_retention_policies(self):370"""Run retention policy enforcement."""371for data_type, policy in self.POLICIES.items():372cutoff_date = datetime.utcnow() - timedelta(373days=policy['retention_period_days']374)375376if policy.get('anonymize_instead'):377await self.anonymize_old_data(data_type, cutoff_date)378else:379if policy.get('archive_before_delete'):380await self.archive_data(data_type, cutoff_date)381await self.delete_old_data(data_type, cutoff_date)382383await self.log_retention_action(data_type, cutoff_date)384385async def anonymize_old_data(self, data_type: str, before_date: datetime):386"""Anonymize data instead of deleting."""387# Example: Replace identifying fields with hashes388if data_type == 'analytics_data':389await self.db.analytics.update_many(390{'collection_date': {'$lt': before_date}},391{'$set': {392'user_id': None,393'ip_address': None,394'device_id': None,395'anonymized': True,396'anonymized_date': datetime.utcnow()397}}398)399```400401### Pattern 4: Privacy by Design402403```python404class PrivacyFirstDataModel:405"""Example of privacy-by-design data model."""406407# Separate PII from behavioral data408user_profile_schema = {409'user_id': str, # UUID, not sequential410'email_hash': str, # Hashed for lookups411'created_at': datetime,412# Minimal data collection413'preferences': {414'language': str,415'timezone': str416}417}418419# Encrypted at rest420user_pii_schema = {421'user_id': str,422'email': str, # Encrypted423'name': str, # Encrypted424'phone': str, # Encrypted (optional)425'address': dict, # Encrypted (optional)426'encryption_key_id': str427}428429# Pseudonymized behavioral data430analytics_schema = {431'session_id': str, # Not linked to user_id432'pseudonym_id': str, # Rotating pseudonym433'events': list,434'device_category': str, # Generalized, not specific435'country': str, # Not city-level436}437438class DataMinimization:439"""Implement data minimization principles."""440441@staticmethod442def collect_only_needed(form_data: dict, purpose: str) -> dict:443"""Filter form data to only fields needed for purpose."""444REQUIRED_FIELDS = {445'account_creation': ['email', 'password'],446'newsletter': ['email'],447'purchase': ['email', 'name', 'address', 'payment'],448'support': ['email', 'message']449}450451allowed = REQUIRED_FIELDS.get(purpose, [])452return {k: v for k, v in form_data.items() if k in allowed}453454@staticmethod455def generalize_location(ip_address: str) -> str:456"""Generalize IP to country level only."""457import geoip2.database458reader = geoip2.database.Reader('GeoLite2-Country.mmdb')459try:460response = reader.country(ip_address)461return response.country.iso_code462except:463return 'UNKNOWN'464```465466### Pattern 5: Breach Notification467468```python469from datetime import datetime470from enum import Enum471472class BreachSeverity(Enum):473LOW = "low"474MEDIUM = "medium"475HIGH = "high"476CRITICAL = "critical"477478class BreachNotificationHandler:479"""Handle GDPR breach notification requirements."""480481AUTHORITY_NOTIFICATION_HOURS = 72482AFFECTED_NOTIFICATION_REQUIRED_SEVERITY = BreachSeverity.HIGH483484async def report_breach(485self,486description: str,487data_types: List[str],488affected_count: int,489severity: BreachSeverity490) -> dict:491"""Report and handle a data breach."""492breach = {493'id': self.generate_breach_id(),494'reported_at': datetime.utcnow(),495'description': description,496'data_types_affected': data_types,497'affected_individuals_count': affected_count,498'severity': severity.value,499'status': 'investigating',500'timeline': [{501'event': 'breach_reported',502'timestamp': datetime.utcnow(),503'details': description504}]505}506507await self.db.breaches.insert_one(breach)508509# Immediate notifications510await self.notify_dpo(breach)511await self.notify_security_team(breach)512513# Authority notification required within 72 hours514if self.requires_authority_notification(severity, data_types):515breach['authority_notification_deadline'] = (516datetime.utcnow() + timedelta(hours=self.AUTHORITY_NOTIFICATION_HOURS)517)518await self.schedule_authority_notification(breach)519520# Affected individuals notification521if severity.value in [BreachSeverity.HIGH.value, BreachSeverity.CRITICAL.value]:522await self.schedule_individual_notifications(breach)523524return breach525526def requires_authority_notification(527self,528severity: BreachSeverity,529data_types: List[str]530) -> bool:531"""Determine if supervisory authority must be notified."""532# Always notify for sensitive data533sensitive_types = ['health', 'financial', 'credentials', 'biometric']534if any(t in sensitive_types for t in data_types):535return True536537# Notify for medium+ severity538return severity in [BreachSeverity.MEDIUM, BreachSeverity.HIGH, BreachSeverity.CRITICAL]539540async def generate_authority_report(self, breach_id: str) -> dict:541"""Generate report for supervisory authority."""542breach = await self.get_breach(breach_id)543544return {545'organization': {546'name': self.config.org_name,547'contact': self.config.dpo_contact,548'registration': self.config.registration_number549},550'breach': {551'nature': breach['description'],552'categories_affected': breach['data_types_affected'],553'approximate_number_affected': breach['affected_individuals_count'],554'likely_consequences': self.assess_consequences(breach),555'measures_taken': await self.get_remediation_measures(breach_id),556'measures_proposed': await self.get_proposed_measures(breach_id)557},558'timeline': breach['timeline'],559'submitted_at': datetime.utcnow().isoformat()560}561```562563## Compliance Checklist564565```markdown566## GDPR Implementation Checklist567568### Legal Basis569570- [ ] Documented legal basis for each processing activity571- [ ] Consent mechanisms meet GDPR requirements572- [ ] Legitimate interest assessments completed573574### Transparency575576- [ ] Privacy policy is clear and accessible577- [ ] Processing purposes clearly stated578- [ ] Data retention periods documented579580### Data Subject Rights581582- [ ] Access request process implemented583- [ ] Erasure request process implemented584- [ ] Portability export available585- [ ] Rectification process available586- [ ] Response within 30-day deadline587588### Security589590- [ ] Encryption at rest implemented591- [ ] Encryption in transit (TLS)592- [ ] Access controls in place593- [ ] Audit logging enabled594595### Breach Response596597- [ ] Breach detection mechanisms598- [ ] 72-hour notification process599- [ ] Breach documentation system600601### Documentation602603- [ ] Records of processing activities (Art. 30)604- [ ] Data protection impact assessments605- [ ] Data processing agreements with vendors606```607608## Best Practices609610### Do's611612- **Minimize data collection** - Only collect what's needed613- **Document everything** - Processing activities, legal bases614- **Encrypt PII** - At rest and in transit615- **Implement access controls** - Need-to-know basis616- **Regular audits** - Verify compliance continuously617618### Don'ts619620- **Don't pre-check consent boxes** - Must be opt-in621- **Don't bundle consent** - Separate purposes separately622- **Don't retain indefinitely** - Define and enforce retention623- **Don't ignore DSARs** - 30-day response required624- **Don't transfer without safeguards** - SCCs or adequacy decisions625