출시 전 EU 사용자가 가입할 때 /hr-legal-compliance-gdpr-data-handling은 동의, DSAR, 보존, 침해 흐름을 구축합니다. — Claude Skill
Claude Code용 Claude 스킬 · 제공: Seth Hobson · 실행: /hr-legal-compliance-gdpr-data-handling (Claude 내)·업데이트: 2026년 6월 14일·v1.0.0
GDPR 동의, DSAR, 보존, 침해 통지 흐름을 구축합니다
- 동의 관리: 선택 동의 UI, 감사 로그, 버전 추적, Article 6 하 철회 흐름
- 정보주체 접근 요청: 접근, 삭제, 정정, 이동권을 위한 30일 워크플로
- 보존 정책: 데이터 유형별 규칙과 익명화 또는 삭제 트리거
- 설계 단계의 개인정보 보호: PII 분리, 저장 시 암호화, 가명화된 분석, IP 일반화
- 침해 통지: 72시간 감독기관 워크플로와 영향받은 개인 통지 기준
대상
기능
출시까지 2주 남았고 동의 배너가 없습니다. /hr-legal-compliance-gdpr-data-handling은 쿠키 환경설정 UI와 감사 로그, 버전 추적, 목적별 세분성이 있는 ConsentManager 서비스를 생성합니다.
DSAR가 월요일에 들어왔습니다. /hr-legal-compliance-gdpr-data-handling은 모든 데이터 출처를 순회하고 법적 보존 예외를 확인하며 구조화된 삭제 보고서를 반환하는 DSARHandler 워크플로를 만듭니다.
제품팀은 GDPR을 깨지 않고 행동 분석을 원합니다. /hr-legal-compliance-gdpr-data-handling은 가명화된 세션 스키마, 일반화된 위치정보, 1년 익명화 트리거를 지정합니다.
보안팀이 사용자 레코드 40K에 대한 무단 접근을 표시했습니다. /hr-legal-compliance-gdpr-data-handling은 감독기관 보고서 템플릿과 심각도 분류기가 있는 BreachNotificationHandler를 생성합니다.
작동 방식
데이터 모델과 EU 개인정보가 흐르는 위치를 설명합니다
Article 6별 법적 근거 맵과 필요한 정보주체 권리 워크플로 목록을 받습니다
감사 로그, 버전 추적, 목적별 세분성이 있는 동의 관리 코드를 받습니다
30일 및 72시간 SLA가 있는 DSAR, 보존, 침해 통지 핸들러를 받습니다
출시 승인 전 GDPR 구현 체크리스트를 실행합니다
예시
사용자 계정: 이메일, 이름, 해시된 비밀번호 사용 이벤트: session_id, 페이지 조회, 클릭 이벤트 마케팅: 동의한 이메일 구독자 지원 티켓: 고객 메시지 지역: 출시 시 EU만
사용자 계정: 계약(Art. 6.1.b) 사용 이벤트: 동의(Art. 6.1.a) 마케팅: 동의 + Art. 7 withdrawal 지원 티켓: 정당한 이익(Art. 6.1.f)
목적별 토글 + 감사 로그가 있는 동의 배너 DSAR 엔드포인트: 접근, 삭제, 이동권, 정정 보존: 사용 이벤트 12개월(익명화), 티켓 24개월(삭제) 침해: 72시간 감독기관 통지, 영향받은 사용자 템플릿
ConsentManager(Python): 기록/확인/이력 DSARHandler: 30일 SLA, 4가지 요청 유형 모두 DataRetentionPolicy: 유형별 기준일과 cron 트리거 BreachNotificationHandler: 심각도 분류기
개인정보 처리방침이 모든 처리 목적을 나열 ROPA(Article 30) 문서화 분석 점수화에는 DPIA 필요 각 공급업체와 데이터 처리 부속 계약
개선되는 지표
지원 도구
GDPR 데이터 처리을(를) 사용해 보시겠어요?
시작 방법을 선택하세요.
이 스킬을 컴퓨터에 로컬로 설치하고 실행합니다.
컴퓨터에서 터미널을 열고 이 명령을 붙여넣으세요:
이 명령은 스킬과 모든 파일을 컴퓨터에 다운로드합니다:
모든 프로젝트에서 사용하려면 끝에 -g를 추가하세요.
Claude Code를 시작한 다음 명령을 입력하세요:
GDPR 데이터 처리
GDPR을 준수하는 데이터 처리, 동의 관리, 개인정보 보호 통제를 구현하기 위한 실무 가이드입니다.
이 스킬을 사용할 때
- EU 개인정보를 처리하는 시스템을 만들 때
- 동의 관리를 구현할 때
- 정보주체 요청(DSR)을 처리할 때
- GDPR 준수 검토를 수행할 때
- 개인정보 보호 우선 아키텍처를 설계할 때
- 데이터 처리 계약을 만들 때
핵심 개념
1. 개인정보 범주
| 범주 | 예시 | 보호 수준 |
|---|---|---|
| 기본 | 이름, 이메일, 전화 | 표준 |
| 민감정보(Art. 9) | 건강, 종교, 민족 | 명시적 동의 |
| 범죄정보(Art. 10) | 유죄 판결, 범죄 | 공식 권한 |
| 아동 정보 | 16세 미만 데이터 | 부모 동의 |
2. 처리의 법적 근거
Article 6 - 적법한 근거:
├── 동의: 자유롭게 제공되고, 구체적이며, 충분히 고지됨
├── 계약: 계약 이행에 필요
├── 법적 의무: 법률상 요구
├── 중대한 이익: 누군가의 생명 보호
├── 공익: 공적 기능 수행
└── 정당한 이익: 권리와 균형 검토
3. 정보주체 권리
접근권(Art. 15) ─┐
정정권(Art. 16) │
삭제권(Art. 17) │ 1개월 안에
처리 제한권(Art. 18) │ 응답해야 함
이동권(Art. 20) │
반대권(Art. 21) ─┘
구현 패턴
패턴 1: 동의 관리
// 동의 데이터 모델
const consentSchema = {
userId: String,
consents: [
{
purpose: String, // 'marketing', 'analytics' 등
granted: Boolean,
timestamp: Date,
source: String, // 'web_form', 'api' 등
version: String, // 개인정보 처리방침 버전
ipAddress: String, // 증빙용
userAgent: String, // 증빙용
},
],
auditLog: [
{
action: String, // 'granted', 'withdrawn', 'updated'
purpose: String,
timestamp: Date,
source: String,
},
],
};
// 동의 서비스
class ConsentManager {
async recordConsent(userId, purpose, granted, metadata) {
const consent = {
purpose,
granted,
timestamp: new Date(),
source: metadata.source,
version: await this.getCurrentPolicyVersion(),
ipAddress: metadata.ipAddress,
userAgent: metadata.userAgent,
};
// 동의 저장
await this.db.consents.updateOne(
{ userId },
{
$push: {
consents: consent,
auditLog: {
action: granted ? "granted" : "withdrawn",
purpose,
timestamp: consent.timestamp,
source: metadata.source,
},
},
},
{ upsert: true },
);
// 하위 시스템에 이벤트 발행
await this.eventBus.emit("consent.changed", {
userId,
purpose,
granted,
timestamp: consent.timestamp,
});
}
async hasConsent(userId, purpose) {
const record = await this.db.consents.findOne({ userId });
if (!record) return false;
const latestConsent = record.consents
.filter((c) => c.purpose === purpose)
.sort((a, b) => b.timestamp - a.timestamp)[0];
return latestConsent?.granted === true;
}
async getConsentHistory(userId) {
const record = await this.db.consents.findOne({ userId });
return record?.auditLog || [];
}
}
<!-- GDPR 준수 동의 UI -->
<div class="consent-banner" role="dialog" aria-labelledby="consent-title">
<h2 id="consent-title">쿠키 환경설정</h2>
<p>
더 나은 경험을 제공하기 위해 쿠키를 사용합니다. 아래에서 환경설정을 선택하세요.
</p>
<form id="consent-form">
<!-- 필수 - 항상 켜짐, 동의 불필요 -->
<div class="consent-category">
<input type="checkbox" id="necessary" checked disabled />
<label for="necessary">
<strong>필수</strong>
<span>웹사이트 작동에 필요합니다. 비활성화할 수 없습니다.</span>
</label>
</div>
<!-- 분석 - 동의 필요 -->
<div class="consent-category">
<input type="checkbox" id="analytics" name="analytics" />
<label for="analytics">
<strong>분석</strong>
<span>사이트 사용 방식을 이해하는 데 도움이 됩니다.</span>
</label>
</div>
<!-- 마케팅 - 동의 필요 -->
<div class="consent-category">
<input type="checkbox" id="marketing" name="marketing" />
<label for="marketing">
<strong>마케팅</strong>
<span>관심사에 기반한 맞춤 광고입니다.</span>
</label>
</div>
<div class="consent-actions">
<button type="button" id="accept-all">모두 수락</button>
<button type="button" id="reject-all">모두 거부</button>
<button type="submit">환경설정 저장</button>
</div>
<p class="consent-links">
<a href="/privacy-policy">개인정보 처리방침</a> |
<a href="/cookie-policy">쿠키 정책</a>
</p>
</form>
</div>
패턴 2: 정보주체 접근 요청(DSAR)
from datetime import datetime, timedelta
from typing import Dict, List, Optional
import json
class DSARHandler:
"""정보주체 접근 요청을 처리합니다."""
RESPONSE_DEADLINE_DAYS = 30
EXTENSION_ALLOWED_DAYS = 60 # 복잡한 요청용
def __init__(self, data_sources: List['DataSource']):
self.data_sources = data_sources
async def submit_request(
self,
request_type: str, # 'access', 'erasure', 'rectification', 'portability'
user_id: str,
verified: bool,
details: Optional[Dict] = None
) -> str:
"""새 DSAR을 제출합니다."""
request = {
'id': self.generate_request_id(),
'type': request_type,
'user_id': user_id,
'status': 'pending_verification' if not verified else 'processing',
'submitted_at': datetime.utcnow(),
'deadline': datetime.utcnow() + timedelta(days=self.RESPONSE_DEADLINE_DAYS),
'details': details or {},
'audit_log': [{
'action': 'submitted',
'timestamp': datetime.utcnow(),
'details': '요청 접수'
}]
}
await self.db.dsar_requests.insert_one(request)
await self.notify_dpo(request)
return request['id']
async def process_access_request(self, request_id: str) -> Dict:
"""데이터 접근 요청을 처리합니다."""
request = await self.get_request(request_id)
if request['type'] != 'access':
raise ValueError("접근 요청이 아닙니다")
# 모든 출처에서 데이터 수집
user_data = {}
for source in self.data_sources:
try:
data = await source.get_user_data(request['user_id'])
user_data[source.name] = data
except Exception as e:
user_data[source.name] = {'error': str(e)}
# 응답 형식화
response = {
'request_id': request_id,
'generated_at': datetime.utcnow().isoformat(),
'data_categories': list(user_data.keys()),
'data': user_data,
'retention_info': await self.get_retention_info(),
'processing_purposes': await self.get_processing_purposes(),
'third_party_recipients': await self.get_recipients()
}
# 요청 상태 업데이트
await self.update_request(request_id, 'completed', response)
return response
async def process_erasure_request(self, request_id: str) -> Dict:
"""삭제권 요청을 처리합니다."""
request = await self.get_request(request_id)
if request['type'] != 'erasure':
raise ValueError("삭제 요청이 아닙니다")
results = {}
exceptions = []
for source in self.data_sources:
try:
# 법적 예외 확인
can_delete, reason = await source.can_delete(request['user_id'])
if can_delete:
await source.delete_user_data(request['user_id'])
results[source.name] = 'deleted'
else:
exceptions.append({
'source': source.name,
'reason': reason # 예: '법적 보존 요건'
})
results[source.name] = f'retained: {reason}'
except Exception as e:
results[source.name] = f'error: {str(e)}'
response = {
'request_id': request_id,
'completed_at': datetime.utcnow().isoformat(),
'results': results,
'exceptions': exceptions
}
await self.update_request(request_id, 'completed', response)
return response
async def process_portability_request(self, request_id: str) -> bytes:
"""이동 가능한 데이터 내보내기를 생성합니다."""
request = await self.get_request(request_id)
user_data = await self.process_access_request(request_id)
# 기계가 읽을 수 있는 형식(JSON)으로 변환
portable_data = {
'export_date': datetime.utcnow().isoformat(),
'format_version': '1.0',
'data': user_data['data']
}
return json.dumps(portable_data, indent=2, default=str).encode()
패턴 3: 데이터 보존
from datetime import datetime, timedelta
from enum import Enum
class RetentionBasis(Enum):
CONSENT = "consent"
CONTRACT = "contract"
LEGAL_OBLIGATION = "legal_obligation"
LEGITIMATE_INTEREST = "legitimate_interest"
class DataRetentionPolicy:
"""데이터 보존 정책을 정의하고 집행합니다."""
POLICIES = {
'user_account': {
'retention_period_days': 365 * 3, # 마지막 활동 후 3년
'basis': RetentionBasis.CONTRACT,
'trigger': 'last_activity_date',
'archive_before_delete': True
},
'transaction_records': {
'retention_period_days': 365 * 7, # 세무 목적 7년
'basis': RetentionBasis.LEGAL_OBLIGATION,
'trigger': 'transaction_date',
'archive_before_delete': True,
'legal_reference': '세법상 7년 보존 필요'
},
'marketing_consent': {
'retention_period_days': 365 * 2, # 2년
'basis': RetentionBasis.CONSENT,
'trigger': 'consent_date',
'archive_before_delete': False
},
'support_tickets': {
'retention_period_days': 365 * 2,
'basis': RetentionBasis.LEGITIMATE_INTEREST,
'trigger': 'ticket_closed_date',
'archive_before_delete': True
},
'analytics_data': {
'retention_period_days': 365, # 1년
'basis': RetentionBasis.CONSENT,
'trigger': 'collection_date',
'archive_before_delete': False,
'anonymize_instead': True
}
}
async def apply_retention_policies(self):
"""보존 정책 집행을 실행합니다."""
for data_type, policy in self.POLICIES.items():
cutoff_date = datetime.utcnow() - timedelta(
days=policy['retention_period_days']
)
if policy.get('anonymize_instead'):
await self.anonymize_old_data(data_type, cutoff_date)
else:
if policy.get('archive_before_delete'):
await self.archive_data(data_type, cutoff_date)
await self.delete_old_data(data_type, cutoff_date)
await self.log_retention_action(data_type, cutoff_date)
async def anonymize_old_data(self, data_type: str, before_date: datetime):
"""삭제 대신 데이터를 익명화합니다."""
# 예: 식별 필드를 해시로 대체
if data_type == 'analytics_data':
await self.db.analytics.update_many(
{'collection_date': {'$lt': before_date}},
{'$set': {
'user_id': None,
'ip_address': None,
'device_id': None,
'anonymized': True,
'anonymized_date': datetime.utcnow()
}}
)
패턴 4: 설계 단계의 개인정보 보호
class PrivacyFirstDataModel:
"""설계 단계 개인정보 보호 데이터 모델의 예시입니다."""
# PII를 행동 데이터와 분리
user_profile_schema = {
'user_id': str, # UUID, 순차 ID 아님
'email_hash': str, # 조회용 해시
'created_at': datetime,
# 최소 데이터 수집
'preferences': {
'language': str,
'timezone': str
}
}
# 저장 시 암호화
user_pii_schema = {
'user_id': str,
'email': str, # 암호화
'name': str, # 암호화
'phone': str, # 암호화(선택)
'address': dict, # 암호화(선택)
'encryption_key_id': str
}
# 가명화된 행동 데이터
analytics_schema = {
'session_id': str, # user_id와 연결하지 않음
'pseudonym_id': str, # 회전하는 가명
'events': list,
'device_category': str, # 일반화, 구체적이지 않음
'country': str, # 도시 수준 아님
}
class DataMinimization:
"""데이터 최소화 원칙을 구현합니다."""
@staticmethod
def collect_only_needed(form_data: dict, purpose: str) -> dict:
"""목적에 필요한 필드만 남기도록 양식 데이터를 필터링합니다."""
REQUIRED_FIELDS = {
'account_creation': ['email', 'password'],
'newsletter': ['email'],
'purchase': ['email', 'name', 'address', 'payment'],
'support': ['email', 'message']
}
allowed = REQUIRED_FIELDS.get(purpose, [])
return {k: v for k, v in form_data.items() if k in allowed}
@staticmethod
def generalize_location(ip_address: str) -> str:
"""IP를 국가 수준까지만 일반화합니다."""
import geoip2.database
reader = geoip2.database.Reader('GeoLite2-Country.mmdb')
try:
response = reader.country(ip_address)
return response.country.iso_code
except:
return 'UNKNOWN'
패턴 5: 침해 통지
from datetime import datetime
from enum import Enum
class BreachSeverity(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class BreachNotificationHandler:
"""GDPR 침해 통지 요건을 처리합니다."""
AUTHORITY_NOTIFICATION_HOURS = 72
AFFECTED_NOTIFICATION_REQUIRED_SEVERITY = BreachSeverity.HIGH
async def report_breach(
self,
description: str,
data_types: List[str],
affected_count: int,
severity: BreachSeverity
) -> dict:
"""데이터 침해를 보고하고 처리합니다."""
breach = {
'id': self.generate_breach_id(),
'reported_at': datetime.utcnow(),
'description': description,
'data_types_affected': data_types,
'affected_individuals_count': affected_count,
'severity': severity.value,
'status': 'investigating',
'timeline': [{
'event': 'breach_reported',
'timestamp': datetime.utcnow(),
'details': description
}]
}
await self.db.breaches.insert_one(breach)
# 즉시 통지
await self.notify_dpo(breach)
await self.notify_security_team(breach)
# 감독기관 통지는 72시간 이내 필요
if self.requires_authority_notification(severity, data_types):
breach['authority_notification_deadline'] = (
datetime.utcnow() + timedelta(hours=self.AUTHORITY_NOTIFICATION_HOURS)
)
await self.schedule_authority_notification(breach)
# 영향받은 개인 통지
if severity.value in [BreachSeverity.HIGH.value, BreachSeverity.CRITICAL.value]:
await self.schedule_individual_notifications(breach)
return breach
def requires_authority_notification(
self,
severity: BreachSeverity,
data_types: List[str]
) -> bool:
"""감독기관에 통지해야 하는지 판단합니다."""
# 민감정보는 항상 통지
sensitive_types = ['health', 'financial', 'credentials', 'biometric']
if any(t in sensitive_types for t in data_types):
return True
# 중간 이상 심각도는 통지
return severity in [BreachSeverity.MEDIUM, BreachSeverity.HIGH, BreachSeverity.CRITICAL]
async def generate_authority_report(self, breach_id: str) -> dict:
"""감독기관 제출용 보고서를 생성합니다."""
breach = await self.get_breach(breach_id)
return {
'organization': {
'name': self.config.org_name,
'contact': self.config.dpo_contact,
'registration': self.config.registration_number
},
'breach': {
'nature': breach['description'],
'categories_affected': breach['data_types_affected'],
'approximate_number_affected': breach['affected_individuals_count'],
'likely_consequences': self.assess_consequences(breach),
'measures_taken': await self.get_remediation_measures(breach_id),
'measures_proposed': await self.get_proposed_measures(breach_id)
},
'timeline': breach['timeline'],
'submitted_at': datetime.utcnow().isoformat()
}
준수 체크리스트
## GDPR 구현 체크리스트
### 법적 근거
- [ ] 각 처리 활동의 법적 근거 문서화
- [ ] 동의 메커니즘이 GDPR 요건을 충족
- [ ] 정당한 이익 평가 완료
### 투명성
- [ ] 개인정보 처리방침이 명확하고 접근 가능
- [ ] 처리 목적이 명확히 명시됨
- [ ] 데이터 보존 기간 문서화
### 정보주체 권리
- [ ] 접근 요청 절차 구현
- [ ] 삭제 요청 절차 구현
- [ ] 이동권 내보내기 제공
- [ ] 정정 절차 제공
- [ ] 30일 기한 내 응답
### 보안
- [ ] 저장 시 암호화 구현
- [ ] 전송 중 암호화(TLS)
- [ ] 접근 통제 마련
- [ ] 감사 로깅 활성화
### 침해 대응
- [ ] 침해 탐지 메커니즘
- [ ] 72시간 통지 절차
- [ ] 침해 문서화 시스템
### 문서화
- [ ] 처리 활동 기록(Art. 30)
- [ ] 데이터 보호 영향 평가
- [ ] 공급업체와의 데이터 처리 계약
모범 사례
해야 할 것
- 데이터 수집 최소화 - 필요한 것만 수집합니다
- 모든 것을 문서화 - 처리 활동과 법적 근거를 기록합니다
- PII 암호화 - 저장 시와 전송 중 모두 암호화합니다
- 접근 통제 구현 - 알아야 할 사람만 접근하게 합니다
- 정기 감사 - 준수를 지속적으로 검증합니다
하지 말 것
- 동의 상자를 미리 체크하지 않기 - 반드시 opt-in이어야 합니다
- 동의를 묶지 않기 - 목적별로 분리합니다
- 무기한 보존하지 않기 - 보존 기간을 정의하고 집행합니다
- DSAR을 무시하지 않기 - 30일 내 응답이 필요합니다
- 보호 장치 없이 이전하지 않기 - SCC 또는 적정성 결정이 필요합니다