mukul975/privacy-data-protection-skills

consent-receipt-spec

- Implement the Kantara Initiative consent receipt specification including machine-readable receipt structure, JWT-based verification mechanisms, receipt lifecycle management, and integration patterns for consent management platforms.

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/mukul975/privacy-data-protection-skills --skill consent-receipt-spec

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

Kantara Initiative Consent Receipt Specification

Overview

A consent receipt is a record of a consent transaction provided to the individual (data subject) as evidence that consent was given. The Kantara Initiative Consent Receipt Specification v1.1 defines a standard, machine-readable format that enables individuals to track and manage their consent across multiple organizations. This skill covers the implementation of consent receipts aligned with the Kantara specification and ISO/IEC 27560:2023.

Consent Receipt Structure

Core Fields (Kantara v1.1)

FieldTypeRequiredDescription
versionStringYesSpecification version (e.g., "KI-CR-v1.1.0")
jurisdictionStringYesLegal jurisdiction (ISO 3166-1 alpha-2)
consentTimestampDateTimeYesUTC timestamp of consent grant
collectionMethodStringYesHow consent was collected (web form, verbal, paper)
consentReceiptIDUUIDYesUnique identifier for this receipt
publicKeyStringNoPublic key for receipt verification
languageStringYesLanguage of the consent interaction (BCP 47)
piiPrincipalIdStringYesIdentifier for the data subject
piiControllersArrayYesList of data controllers
policyUrlURLYesLink to the privacy policy
servicesArrayYesServices for which consent is given
sensitiveBooleanYesWhether special category data is involved
spiCatArrayNoSpecial categories of data processed

PII Controller Object

FieldTypeRequiredDescription
piiControllerStringYesName of the controller organization
onBehalfBooleanNoWhether acting on behalf of another controller
contactStringYesContact information for the controller
addressObjectYesPhysical address of the controller
emailStringYesContact email address
phoneStringNoContact phone number
piiControllerUrlURLNoURL of the controller's website

Service Object

FieldTypeRequiredDescription
serviceStringYesName of the service
purposesArrayYesList of processing purposes

Purpose Object

FieldTypeRequiredDescription
purposeStringYesDescription of the processing purpose
purposeCategoryArrayYesCategory codes for the purpose
consentTypeStringYes"explicit" or "implicit"
piiCategoryArrayYesCategories of PII processed
primaryPurposeBooleanYesWhether this is the primary purpose
terminationStringYesHow consent can be withdrawn
thirdPartyDisclosureBooleanYesWhether data is shared with third parties
thirdPartyNameStringNoName of third party (if applicable)

Machine-Readable Receipt Format

JSON Consent Receipt

json
{
  "version": "KI-CR-v1.1.0",
  "jurisdiction": "EU",
  "consentTimestamp": "2026-03-14T10:30:00.000Z",
  "collectionMethod": "web_form",
  "consentReceiptID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "language": "en",
  "piiPrincipalId": "user-98765",
  "piiControllers": [
    {
      "piiController": "Cipher Engineering Labs",
      "onBehalf": false,
      "contact": "Data Protection Officer",
      "address": {
        "streetAddress": "100 Technology Drive",
        "locality": "London",
        "region": "Greater London",
        "postalCode": "EC2A 1NT",
        "country": "GB"
      },
      "email": "dpo@cipherengineeringlabs.com",
      "phone": "+44-20-7946-0958",
      "piiControllerUrl": "https://www.cipherengineeringlabs.com"
    }
  ],
  "policyUrl": "https://www.cipherengineeringlabs.com/privacy-policy",
  "services": [
    {
      "service": "Privacy Analytics Platform",
      "purposes": [
        {
          "purpose": "Provide personalized privacy compliance recommendations",
          "purposeCategory": ["core_service"],
          "consentType": "explicit",
          "piiCategory": ["contact_information", "professional_information"],
          "primaryPurpose": true,
          "termination": "Account settings > Privacy > Withdraw consent",
          "thirdPartyDisclosure": false
        },
        {
          "purpose": "Send product updates and feature announcements",
          "purposeCategory": ["marketing"],
          "consentType": "explicit",
          "piiCategory": ["contact_information"],
          "primaryPurpose": false,
          "termination": "Unsubscribe link in email or Account settings",
          "thirdPartyDisclosure": false
        },
        {
          "purpose": "Aggregate usage analytics to improve platform features",
          "purposeCategory": ["analytics"],
          "consentType": "explicit",
          "piiCategory": ["usage_data", "device_information"],
          "primaryPurpose": false,
          "termination": "Account settings > Privacy > Analytics opt-out",
          "thirdPartyDisclosure": true,
          "thirdPartyName": "Cipher Analytics Processing Ltd"
        }
      ]
    }
  ],
  "sensitive": false,
  "spiCat": []
}

JWT-Based Verification Mechanism

Signed Consent Receipt Implementation

python
"""
Consent receipt generation and verification using JWT (JSON Web Tokens).
Implements Kantara Initiative Consent Receipt Specification v1.1
with cryptographic verification.
"""

import json
import uuid
from datetime import datetime, timezone
from typing import Optional
import jwt  # PyJWT
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend


class ConsentReceiptIssuer:
    """
    Issue and sign consent receipts as JWTs.
    Uses RS256 (RSA with SHA-256) for signing.
    """

    def __init__(self, private_key_pem: bytes, issuer_name: str):
        """
        Args:
            private_key_pem: PEM-encoded RSA private key
            issuer_name: Name of the issuing organization
        """
        self.private_key = serialization.load_pem_private_key(
            private_key_pem, password=None, backend=default_backend()
        )
        self.public_key = self.private_key.public_key()
        self.issuer_name = issuer_name

    def issue_receipt(
        self,
        principal_id: str,
        services: list[dict],
        jurisdiction: str,
        collection_method: str,
        policy_url: str,
        sensitive: bool = False,
        language: str = "en",
        controller_info: dict = None
    ) -> tuple[str, str]:
        """
        Generate a signed consent receipt.

        Args:
            principal_id: Data subject identifier
            services: List of service/purpose objects
            jurisdiction: Legal jurisdiction code
            collection_method: How consent was collected
            policy_url: URL of the privacy policy
            sensitive: Whether special categories are involved
            language: Language of consent interaction
            controller_info: PII controller details

        Returns:
            Tuple of (receipt_id, signed_jwt_string)
        """
        receipt_id = str(uuid.uuid4())

        if controller_info is None:
            controller_info = {
                "piiController": self.issuer_name,
                "onBehalf": False,
                "contact": "Data Protection Officer",
                "email": f"dpo@{self.issuer_name.lower().replace(' ', '')}.com"
            }

        receipt_payload = {
            "version": "KI-CR-v1.1.0",
            "jurisdiction": jurisdiction,
            "consentTimestamp": datetime.now(timezone.utc).isoformat(),
            "collectionMethod": collection_method,
            "consentReceiptID": receipt_id,
            "language": language,
            "piiPrincipalId": principal_id,
            "piiControllers": [controller_info],
            "policyUrl": policy_url,
            "services": services,
            "sensitive": sensitive,
            "spiCat": [],
            # JWT standard claims
            "iss": self.issuer_name,
            "sub": principal_id,
            "iat": int(datetime.now(timezone.utc).timestamp()),
            "jti": receipt_id,
        }

        # Sign the receipt as a JWT
        signed_token = jwt.encode(
            receipt_payload,
            self.private_key,
            algorithm="RS256",
            headers={"kid": f"{self.issuer_name}-consent-key-1"}
        )

        return receipt_id, signed_token

    def get_public_key_pem(self) -> str:
        """Export the public key for verification by data subjects."""
        return self.public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        ).decode("utf-8")


class ConsentReceiptVerifier:
    """
    Verify signed consent receipts.
    Used by data subjects or auditors to confirm receipt authenticity.
    """

    def __init__(self):
        self.trusted_keys: dict[str, bytes] = {}

    def register_issuer_key(self, issuer_name: str, public_key_pem: str):
        """Register a trusted issuer's public key."""
        self.trusted_keys[issuer_name] = public_key_pem.encode("utf-8")

    def verify_receipt(self, token: str) -> tuple[bool, Optional[dict], Optional[str]]:
        """
        Verify a signed consent receipt JWT.

        Returns:
            Tuple of (is_valid, decoded_payload_or_None, error_message_or_None)
        """
        try:
            # Decode without verification first to get the issuer
            unverified = jwt.decode(token, options={"verify_signature": False})
            issuer = unverified.get("iss")

            if issuer not in self.trusted_keys:
                return (False, None, f"Unknown issuer: {issuer}")

            public_key = serialization.load_pem_public_key(
                self.trusted_keys[issuer],
                backend=default_backend()
            )

            # Verify the signature
            decoded = jwt.decode(
                token,
                public_key,
                algorithms=["RS256"],
                issuer=issuer
            )

            # Validate required Kantara fields
            required_fields = [
                "version", "jurisdiction", "consentTimestamp",
                "collectionMethod", "consentReceiptID", "piiPrincipalId",
                "piiControllers", "policyUrl", "services", "sensitive"
            ]

            missing = [f for f in required_fields if f not in decoded]
            if missing:
                return (False, decoded, f"Missing required fields: {missing}")

            return (True, decoded, None)

        except jwt.ExpiredSignatureError:
            return (False, None, "Receipt JWT has expired")
        except jwt.InvalidSignatureError:
            return (False, None, "Invalid signature — receipt may have been tampered with")
        except jwt.DecodeError as e:
            return (False, None, f"Failed to decode JWT: {str(e)}")

Receipt Lifecycle Management

Lifecycle States

ISSUED --> ACTIVE --> WITHDRAWN
  |          |           |
  |          v           v
  |       UPDATED    ARCHIVED
  |          |
  v          v
EXPIRED   ACTIVE (new version)

Lifecycle Manager

python
"""
Manage the lifecycle of consent receipts including
updates, withdrawals, and expiration tracking.
"""

from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum


class ReceiptStatus(Enum):
    ISSUED = "issued"
    ACTIVE = "active"
    UPDATED = "updated"
    WITHDRAWN = "withdrawn"
    EXPIRED = "expired"
    ARCHIVED = "archived"


@dataclass
class ReceiptRecord:
    receipt_id: str
    principal_id: str
    status: ReceiptStatus
    issued_at: datetime
    last_updated: datetime
    withdrawn_at: datetime | None
    jwt_token: str
    version: int
    superseded_by: str | None
    purposes: list[str]


class ConsentReceiptLifecycle:
    """
    Manage consent receipt state transitions and history.
    """

    def __init__(self, receipt_store, issuer: ConsentReceiptIssuer):
        self.store = receipt_store
        self.issuer = issuer

    def activate_receipt(self, receipt_id: str) -> bool:
        """Transition a receipt from ISSUED to ACTIVE."""
        record = self.store.get(receipt_id)
        if record and record.status == ReceiptStatus.ISSUED:
            record.status = ReceiptStatus.ACTIVE
            record.last_updated = datetime.now(timezone.utc)
            self.store.update(record)
            return True
        return False

    def update_receipt(
        self,
        receipt_id: str,
        updated_services: list[dict],
        jurisdiction: str,
        policy_url: str
    ) -> str | None:
        """
        Update an existing receipt by issuing a new version.
        The old receipt is marked as UPDATED and linked to the new one.

        Returns the new receipt ID or None if update failed.
        """
        old_record = self.store.get(receipt_id)
        if not old_record or old_record.status not in [
            ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
        ]:
            return None

        # Issue new receipt
        new_receipt_id, new_jwt = self.issuer.issue_receipt(
            principal_id=old_record.principal_id,
            services=updated_services,
            jurisdiction=jurisdiction,
            collection_method="consent_update",
            policy_url=policy_url
        )

        # Create new record
        new_record = ReceiptRecord(
            receipt_id=new_receipt_id,
            principal_id=old_record.principal_id,
            status=ReceiptStatus.ACTIVE,
            issued_at=datetime.now(timezone.utc),
            last_updated=datetime.now(timezone.utc),
            withdrawn_at=None,
            jwt_token=new_jwt,
            version=old_record.version + 1,
            superseded_by=None,
            purposes=[p["purpose"] for s in updated_services for p in s.get("purposes", [])]
        )
        self.store.save(new_record)

        # Mark old receipt as updated
        old_record.status = ReceiptStatus.UPDATED
        old_record.superseded_by = new_receipt_id
        old_record.last_updated = datetime.now(timezone.utc)
        self.store.update(old_record)

        return new_receipt_id

    def withdraw_consent(self, receipt_id: str, reason: str = "") -> bool:
        """
        Withdraw consent and mark the receipt accordingly.

        Returns True if withdrawal was successful.
        """
        record = self.store.get(receipt_id)
        if not record or record.status not in [
            ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
        ]:
            return False

        record.status = ReceiptStatus.WITHDRAWN
        record.withdrawn_at = datetime.now(timezone.utc)
        record.last_updated = datetime.now(timezone.utc)
        self.store.update(record)

        return True

    def get_active_receipts(self, principal_id: str) -> list[ReceiptRecord]:
        """Get all active consent receipts for a data subject."""
        return self.store.find_by_principal(
            principal_id, status=ReceiptStatus.ACTIVE
        )

    def get_receipt_history(self, principal_id: str) -> list[ReceiptRecord]:
        """Get the complete consent receipt history for a data subject."""
        return self.store.find_by_principal(principal_id)

ISO/IEC 27560:2023 Alignment

Kantara CR FieldISO 27560 EquivalentNotes
consentReceiptIDConsent Record IdentifierUnique identifier for the record
consentTimestampDate and time of consentWhen consent was collected
piiPrincipalIdPII Principal IdentifierData subject identifier
piiControllersPII ControllerOrganization processing data
services.purposesPurpose(s)Processing purposes
services.purposes.piiCategoryPII CategoriesTypes of personal data
sensitiveSensitive PII indicatorSpecial category flag
jurisdictionJurisdictionApplicable legal framework
collectionMethodMechanism of consentHow consent was obtained
policyUrlPrivacy notice referenceLink to privacy notice

References

  • Kantara Initiative Consent Receipt Specification v1.1.0 (2018)
  • ISO/IEC 27560:2023 — Privacy Technologies — Consent Record Information Structure
  • ISO/IEC 29184:2020 — Guidelines for Online Privacy Notices and Consent
  • RFC 7519 — JSON Web Token (JWT)
  • W3C Data Privacy Vocabularies and Controls Community Group
  • IEEE P7012 — Standard for Machine Readable Personal Privacy Terms

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills
mukul975
커뮤니티

applying-privacy-design-patterns

- Systematic application of the eight privacy design patterns per Hoepman: minimize, hide, separate, abstract, inform, control, enforce, and demonstrate. Covers pattern selection methodology per processing activity, mapping to GDPR principles, and practical implementation guidance for privacy-by-design system architecture.

설치 수
1
GitHub Stars
315
업데이트
3월 16일
mukul975
커뮤니티

brazil-lgpd

- Guides compliance with Brazil's Lei Geral de Proteção de Dados (LGPD, Lei 13.709/2018). Covers the 10 lawful bases under Art. 7, DPO appointment, ANPD enforcement, data subject rights under Arts. 17-22, and international transfer mechanisms. Keywords: LGPD, Brazil data protection, ANPD, lawful bases, data subject rights, international transfers.

설치 수
1
GitHub Stars
315
업데이트
3월 16일
mukul975
커뮤니티

adequacy-assessment

- Guides assessment of third-country adequacy decisions under GDPR Article 45 for international data transfers. Covers the current EC adequacy decisions list, adequacy assessment criteria, partial adequacy handling, and monitoring of adequacy decision reviews. Keywords: adequacy decision, Article 45, third country, adequate protection, EC adequacy list.

설치 수
1
GitHub Stars
295
업데이트
3월 16일
mukul975
커뮤니티

age-gating-services

- Implements age-gating mechanisms for online services to restrict access based on user age. Covers hard gates versus soft gates, neutral age prompts, re-verification triggers, circumvention prevention, and regulatory requirements under GDPR, COPPA, UK Online Safety Act, and DSA. Keywords: age gate, age restriction, neutral prompt, children, online services, access control.

설치 수
1
GitHub Stars
295
업데이트
3월 16일