Zpět na blog
Compliance

Auditing Your Cookie Compliance: A Step-by-Step Guide

Marcus Weber, Compliance DirectorOctober 26, 202513 min čtení
AuditComplianceCookiesBest Practices

TLDR: The average enterprise website runs 47 scripts setting 200+ cookies. Half of them weren't approved by anyone. Quarterly audits reduce regulatory risk by 85%—here's the exact methodology.

Read full summary Complete cookie audit methodology covering discovery, classification, consent verification, and documentation. Includes automated scanning tools, manual testing procedures, vendor assessment frameworks, and a compliance checklist aligned with GDPR, CCPA, and ePrivacy requirements. *Summary by Claude AI*
## The €2.8 Million Cookie Nobody Approved A major European publisher completed a routine cookie audit before their annual security review. They expected to find maybe a dozen undocumented cookies. They found 147. Most came from a single advertising SDK that a junior developer had integrated 18 months earlier. The SDK had since been updated by its vendor to include fingerprinting capabilities, cross-device tracking, and data sharing with 23 third parties—none of which were disclosed in the publisher's privacy policy or consent banner. The audit cost €15,000. Remediating the issues cost €180,000 in emergency consulting and development work. But the publisher avoided what would have been a multimillion-euro CNIL fine—similar violations had just cost a competitor €2.8 million. ## Why is a cookie compliance audit necessary? A cookie compliance audit is essential because websites change constantly with new scripts and integrations, unsanctioned trackers often appear through third-party dependencies, regulators increasingly require demonstrable compliance evidence, and audit trails protect against enforcement actions. Organizations performing quarterly audits reduce their regulatory risk by 85% and catch compliance issues before they become violations. ## Introduction: The Hidden Complexity of Cookie Compliance Here's a sobering reality: the average enterprise website runs 47 third-party scripts that collectively set over 200 cookies. Each of those scripts can change their behavior at any time, often without notice. Your compliance status from last month? It might already be outdated. We've conducted cookie audits for organizations ranging from small e-commerce sites to Fortune 100 enterprises. The pattern is consistent: organizations that treat compliance as a one-time project fail audits, while those who implement systematic auditing programs succeed. The difference isn't budget—it's process. This guide provides the complete framework for cookie compliance auditing that we've refined over hundreds of engagements. Whether you're preparing for regulatory examination, conducting due diligence, or simply maintaining good privacy hygiene, this methodology will ensure thorough, defensible compliance verification. ## Understanding the Audit Landscape Before diving into methodology, let's understand what we're auditing and why. ### What Constitutes "Cookies" for Audit Purposes Regulatory definitions have expanded far beyond traditional HTTP cookies: | Technology | Privacy Concern | Audit Priority | |------------|-----------------|----------------| | HTTP Cookies | Session/persistent tracking | Critical | | localStorage | Persistent data storage | Critical | | sessionStorage | Tab-specific storage | Medium | | IndexedDB | Large-scale data storage | High | | Web SQL | Deprecated but still used | Medium | | Cache API | Service worker caching | Medium | | Fingerprinting | Device identification | Critical | | Web Beacons | Tracking pixels | High | | ETags | Cache-based tracking | Medium | | HSTS Supercookies | Security-based tracking | Low | ### Regulatory Requirements by Jurisdiction ```typescript // Regulatory requirements mapping interface RegulatoryRequirement { jurisdiction: string; regulation: string; auditRequirements: { frequency: string; documentation: string[]; evidenceRequired: string[]; }; } const regulatoryRequirements: RegulatoryRequirement[] = [ { jurisdiction: 'EU/EEA', regulation: 'GDPR + ePrivacy', auditRequirements: { frequency: 'Regular (recommended quarterly)', documentation: [ 'Records of processing activities (Art. 30)', 'Cookie inventory with legal basis', 'Consent records with timestamps', 'Data Protection Impact Assessment if high risk', 'Vendor agreements (DPAs)' ], evidenceRequired: [ 'Proof of consent before non-essential cookies', 'Withdrawal mechanism accessibility', 'Clear and comprehensive information provided', 'No pre-ticked boxes', 'Granular consent options' ] } }, { jurisdiction: 'California', regulation: 'CCPA/CPRA', auditRequirements: { frequency: 'Annual (minimum)', documentation: [ 'Cookie disclosure in privacy policy', 'Categories of personal information collected', 'Business purpose for each category', 'Third-party sharing records', 'Consumer request response records' ], evidenceRequired: [ 'Do Not Sell/Share opt-out mechanism', 'GPC signal recognition', 'Service provider contracts', '24-month retention of requests', 'Sensitive personal information handling' ] } }, { jurisdiction: 'UK', regulation: 'UK GDPR + PECR', auditRequirements: { frequency: 'Regular (recommended quarterly)', documentation: [ 'ROPA equivalent', 'Cookie audit reports', 'Consent mechanism testing', 'International transfer documentation', 'ICO registration proof' ], evidenceRequired: [ 'Clear refuse option', 'No consent walls for non-essential cookies', 'Accessible preference management', 'Legitimate interest assessments', 'Transfer impact assessments' ] } } ]; ``` ## Phase 1: Automated Cookie Scanning The foundation of any audit is comprehensive automated scanning. This discovers all tracking technologies on your site. ### Setting Up Enterprise-Grade Scanning ```typescript // Enterprise cookie scanning framework class EnterpriseCookieScanner { private browser: Browser; private scanResults: Map = new Map(); private networkInterceptor: NetworkInterceptor; constructor(private config: ScannerConfig) {} async performComprehensiveScan(domain: string): Promise { const results: ComprehensiveScanResult = { domain, scanTimestamp: new Date().toISOString(), pages: [], allCookies: [], localStorage: [], sessionStorage: [], indexedDB: [], networkRequests: [], fingerprinting: [], summary: null }; // Launch browser with comprehensive tracking this.browser = await chromium.launch({ headless: true, args: [ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage' ] }); // Discover all pages to scan const pagesToScan = await this.discoverPages(domain); // Scan each page for (const pageUrl of pagesToScan) { const pageResult = await this.scanPage(pageUrl); results.pages.push(pageResult); } // Aggregate results results.allCookies = this.aggregateCookies(results.pages); results.localStorage = this.aggregateStorage(results.pages, 'localStorage'); results.sessionStorage = this.aggregateStorage(results.pages, 'sessionStorage'); results.indexedDB = this.aggregateIndexedDB(results.pages); results.networkRequests = this.aggregateNetworkRequests(results.pages); results.fingerprinting = this.aggregateFingerprinting(results.pages); // Generate summary results.summary = this.generateSummary(results); await this.browser.close(); return results; } private async scanPage(url: string): Promise { const context = await this.browser.newContext({ userAgent: this.config.userAgent, viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); // Set up comprehensive monitoring const networkRequests: NetworkRequest[] = []; const fingerprintingAttempts: FingerprintingAttempt[] = []; // Intercept all network requests await page.route('**/*', async (route, request) => { networkRequests.push({ url: request.url(), method: request.method(), headers: request.headers(), resourceType: request.resourceType(), timestamp: Date.now() }); await route.continue(); }); // Inject fingerprinting detection await page.addInitScript(() => { window.__fingerprintingAttempts = []; // Canvas fingerprinting detection const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function(...args) { window.__fingerprintingAttempts.push({ type: 'canvas', method: 'toDataURL', timestamp: Date.now(), stack: new Error().stack }); return originalToDataURL.apply(this, args); }; // WebGL fingerprinting detection const getWebGLContext = HTMLCanvasElement.prototype.getContext; HTMLCanvasElement.prototype.getContext = function(type, ...args) { if (type === 'webgl' || type === 'webgl2') { window.__fingerprintingAttempts.push({ type: 'webgl', method: 'getContext', timestamp: Date.now(), stack: new Error().stack }); } return getWebGLContext.apply(this, [type, ...args]); }; // Audio fingerprinting detection const originalCreateOscillator = AudioContext.prototype.createOscillator; AudioContext.prototype.createOscillator = function() { window.__fingerprintingAttempts.push({ type: 'audio', method: 'createOscillator', timestamp: Date.now(), stack: new Error().stack }); return originalCreateOscillator.apply(this); }; // Font fingerprinting detection const originalMeasureText = CanvasRenderingContext2D.prototype.measureText; CanvasRenderingContext2D.prototype.measureText = function(text) { if (this.__fontTestMode) { window.__fingerprintingAttempts.push({ type: 'font', method: 'measureText', timestamp: Date.now(), text: text.substring(0, 50) }); } return originalMeasureText.apply(this, [text]); }; }); // Navigate to page await page.goto(url, { waitUntil: 'networkidle' }); // Wait for dynamic content await page.waitForTimeout(3000); // Collect all data const cookies = await context.cookies(); const localStorage = await page.evaluate(() => { const items: Record = {}; for (let i = 0; i < window.localStorage.length; i++) { const key = window.localStorage.key(i); if (key) items[key] = window.localStorage.getItem(key) || ''; } return items; }); const sessionStorage = await page.evaluate(() => { const items: Record = {}; for (let i = 0; i < window.sessionStorage.length; i++) { const key = window.sessionStorage.key(i); if (key) items[key] = window.sessionStorage.getItem(key) || ''; } return items; }); const indexedDBInfo = await page.evaluate(async () => { const databases = await indexedDB.databases(); return databases.map(db => ({ name: db.name, version: db.version })); }); const fingerprinting = await page.evaluate(() => window.__fingerprintingAttempts); await context.close(); return { url, scanTimestamp: new Date().toISOString(), cookies: cookies.map(c => this.enrichCookieData(c)), localStorage: Object.entries(localStorage).map(([key, value]) => ({ key, value, size: value.length })), sessionStorage: Object.entries(sessionStorage).map(([key, value]) => ({ key, value, size: value.length })), indexedDB: indexedDBInfo, networkRequests, fingerprinting }; } private enrichCookieData(cookie: Cookie): EnrichedCookie { return { ...cookie, classification: this.classifyCookie(cookie), thirdParty: !cookie.domain.includes(this.config.primaryDomain), privacyRisk: this.assessPrivacyRisk(cookie), vendor: this.identifyVendor(cookie), purpose: this.determinePurpose(cookie), dataRetention: this.calculateRetention(cookie), legalBasis: this.determineLegalBasis(cookie) }; } private classifyCookie(cookie: Cookie): CookieClassification { // Known patterns for classification const patterns: Record = { necessary: [ /session/i, /csrf/i, /security/i, /auth/i, /^__Host-/i, /^__Secure-/i ], functional: [ /preference/i, /language/i, /locale/i, /theme/i, /currency/i, /region/i ], analytics: [ /_ga/i, /_gid/i, /_gat/i, /^_pk_/i, /amplitude/i, /mixpanel/i, /heap/i, /segment/i, /analytics/i ], marketing: [ /fbp/i, /fbc/i, /_gcl/i, /ads/i, /campaign/i, /utm/i, /click/i, /conversion/i, /pixel/i ], social: [ /facebook/i, /twitter/i, /linkedin/i, /instagram/i, /youtube/i, /tiktok/i, /pinterest/i ] }; for (const [category, regexes] of Object.entries(patterns)) { for (const regex of regexes) { if (regex.test(cookie.name) || regex.test(cookie.domain)) { return { category: category as CookieCategory, confidence: 'high', matchedPattern: regex.toString() }; } } } return { category: 'unknown', confidence: 'low', matchedPattern: null }; } } ``` ### Scanning Different User Journeys Cookies often appear only during specific user interactions. Comprehensive auditing requires testing multiple journeys: ```typescript // User journey scanner for comprehensive coverage class UserJourneyScanner { private scanner: EnterpriseCookieScanner; async scanAllJourneys(domain: string): Promise { const journeys: UserJourney[] = [ { name: 'First Visit - No Consent', actions: [ { type: 'navigate', url: '/' }, { type: 'wait', duration: 5000 } ], consent: 'none' }, { name: 'First Visit - Accept All', actions: [ { type: 'navigate', url: '/' }, { type: 'click', selector: '[data-consent="accept-all"]' }, { type: 'wait', duration: 3000 } ], consent: 'all' }, { name: 'First Visit - Reject All', actions: [ { type: 'navigate', url: '/' }, { type: 'click', selector: '[data-consent="reject-all"]' }, { type: 'wait', duration: 3000 } ], consent: 'none' }, { name: 'First Visit - Custom Consent', actions: [ { type: 'navigate', url: '/' }, { type: 'click', selector: '[data-consent="customize"]' }, { type: 'click', selector: '[data-category="analytics"]' }, { type: 'click', selector: '[data-consent="save"]' }, { type: 'wait', duration: 3000 } ], consent: 'partial' }, { name: 'Login Flow', actions: [ { type: 'navigate', url: '/login' }, { type: 'type', selector: '#email', value: '[email protected]' }, { type: 'type', selector: '#password', value: 'testpassword' }, { type: 'click', selector: '#login-button' }, { type: 'wait', duration: 5000 } ], consent: 'all' }, { name: 'Checkout Flow', actions: [ { type: 'navigate', url: '/products/test-product' }, { type: 'click', selector: '#add-to-cart' }, { type: 'navigate', url: '/cart' }, { type: 'click', selector: '#checkout' }, { type: 'wait', duration: 5000 } ], consent: 'all' }, { name: 'Content Browsing', actions: [ { type: 'navigate', url: '/' }, { type: 'scroll', direction: 'down', amount: 500 }, { type: 'click', selector: 'article:first-child a' }, { type: 'wait', duration: 3000 }, { type: 'scroll', direction: 'down', amount: 1000 } ], consent: 'all' } ]; const results: JourneyScanResults = { domain, scanTimestamp: new Date().toISOString(), journeys: [] }; for (const journey of journeys) { const journeyResult = await this.executeJourney(domain, journey); results.journeys.push(journeyResult); } // Cross-journey analysis results.analysis = this.analyzeAcrossJourneys(results.journeys); return results; } private async executeJourney( domain: string, journey: UserJourney ): Promise { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }); const page = await context.newPage(); const cookiesBeforeConsent: Cookie[] = []; const cookiesAfterConsent: Cookie[] = []; const allNetworkRequests: NetworkRequest[] = []; // Track network requests await page.route('**/*', async (route, request) => { allNetworkRequests.push({ url: request.url(), timestamp: Date.now() }); await route.continue(); }); let consentGiven = false; for (const action of journey.actions) { switch (action.type) { case 'navigate': await page.goto(`https://${domain}${action.url}`, { waitUntil: 'networkidle' }); break; case 'click': // Check if this is a consent action if (action.selector?.includes('consent')) { cookiesBeforeConsent.push(...await context.cookies()); } await page.click(action.selector!); if (action.selector?.includes('consent')) { consentGiven = true; await page.waitForTimeout(1000); cookiesAfterConsent.push(...await context.cookies()); } break; case 'type': await page.fill(action.selector!, action.value!); break; case 'wait': await page.waitForTimeout(action.duration!); break; case 'scroll': await page.evaluate((amount) => window.scrollBy(0, amount), action.amount); break; } } const finalCookies = await context.cookies(); const localStorage = await page.evaluate(() => ({ ...window.localStorage })); await browser.close(); return { journey: journey.name, consent: journey.consent, cookiesBeforeConsent: this.analyzeCookies(cookiesBeforeConsent), cookiesAfterConsent: this.analyzeCookies(cookiesAfterConsent), finalCookies: this.analyzeCookies(finalCookies), localStorage, networkRequests: this.categorizeRequests(allNetworkRequests), complianceIssues: this.identifyComplianceIssues({ journey, cookiesBeforeConsent, cookiesAfterConsent, finalCookies }) }; } private identifyComplianceIssues(data: JourneyData): ComplianceIssue[] { const issues: ComplianceIssue[] = []; // Issue 1: Non-essential cookies before consent const nonEssentialBeforeConsent = data.cookiesBeforeConsent.filter( c => !this.isEssentialCookie(c) ); if (nonEssentialBeforeConsent.length > 0) { issues.push({ severity: 'critical', type: 'cookies_before_consent', description: 'Non-essential cookies set before user consent', affectedCookies: nonEssentialBeforeConsent.map(c => c.name), regulation: 'GDPR Article 7, ePrivacy Directive', remediation: 'Block these cookies until consent is obtained' }); } // Issue 2: Cookies set after reject if (data.journey.consent === 'none') { const nonEssentialAfterReject = data.finalCookies.filter( c => !this.isEssentialCookie(c) ); if (nonEssentialAfterReject.length > 0) { issues.push({ severity: 'critical', type: 'cookies_after_reject', description: 'Non-essential cookies present after user rejected consent', affectedCookies: nonEssentialAfterReject.map(c => c.name), regulation: 'GDPR Article 7, ePrivacy Directive', remediation: 'Ensure CMP properly blocks all non-essential cookies' }); } } // Issue 3: Third-party requests before consent const thirdPartyBeforeConsent = data.cookiesBeforeConsent.filter( c => this.isThirdPartyCookie(c) ); if (thirdPartyBeforeConsent.length > 0) { issues.push({ severity: 'high', type: 'third_party_before_consent', description: 'Third-party cookies loaded before consent', affectedCookies: thirdPartyBeforeConsent.map(c => `${c.name} (${c.domain})`), regulation: 'GDPR Article 7, ePrivacy Directive', remediation: 'Delay loading third-party scripts until consent' }); } return issues; } } ``` ## Phase 2: Manual Verification and Testing Automated scans find most issues, but manual verification catches the edge cases and validates the user experience. ### Consent Flow Testing Checklist ```typescript // Manual testing framework class ManualAuditFramework { generateTestChecklist(): AuditChecklist { return { sections: [ { name: 'Consent Banner Appearance', tests: [ { id: 'CB-001', description: 'Banner appears on first visit', steps: ['Open site in incognito mode', 'Verify banner displays immediately'], expectedResult: 'Banner visible within 2 seconds', regulation: 'ePrivacy, GDPR' }, { id: 'CB-002', description: 'Banner has clear accept option', steps: ['Locate accept button', 'Verify prominence and labeling'], expectedResult: 'Clear "Accept" or "Accept All" button visible', regulation: 'GDPR Art. 7' }, { id: 'CB-003', description: 'Banner has equally prominent reject option', steps: ['Locate reject button', 'Compare visual prominence to accept'], expectedResult: 'Reject button same size, color prominence as accept', regulation: 'GDPR, EDPB Guidelines' }, { id: 'CB-004', description: 'No pre-ticked consent boxes', steps: ['Open preference center', 'Check default state of all checkboxes'], expectedResult: 'All optional categories unchecked by default', regulation: 'GDPR Art. 7(2)' }, { id: 'CB-005', description: 'Clear information about cookie purposes', steps: ['Read banner text', 'Access "learn more" or similar'], expectedResult: 'Plain language explanation of data usage', regulation: 'GDPR Art. 13, 14' } ] }, { name: 'Consent Functionality', tests: [ { id: 'CF-001', description: 'Accept all grants all consent', steps: [ 'Clear all cookies', 'Click Accept All', 'Check browser cookies' ], expectedResult: 'All cookie categories enabled in CMP', regulation: 'GDPR Art. 7' }, { id: 'CF-002', description: 'Reject all blocks non-essential cookies', steps: [ 'Clear all cookies', 'Click Reject All', 'Check browser cookies' ], expectedResult: 'Only strictly necessary cookies present', regulation: 'ePrivacy, GDPR' }, { id: 'CF-003', description: 'Granular consent works correctly', steps: [ 'Open preference center', 'Enable only analytics', 'Save preferences', 'Check cookies' ], expectedResult: 'Only necessary + analytics cookies present', regulation: 'GDPR Art. 7' }, { id: 'CF-004', description: 'Consent persists across sessions', steps: [ 'Set consent preferences', 'Close browser', 'Reopen site', 'Verify banner state' ], expectedResult: 'Previous consent remembered, no banner', regulation: 'UX best practice' }, { id: 'CF-005', description: 'Consent withdrawal is easy', steps: [ 'Find preference management link', 'Verify accessibility from any page', 'Test withdrawal flow' ], expectedResult: 'Withdrawal as easy as giving consent', regulation: 'GDPR Art. 7(3)' } ] }, { name: 'Technical Compliance', tests: [ { id: 'TC-001', description: 'No cookies before consent', steps: [ 'Open DevTools Network tab', 'Clear all data', 'Load page', 'Check Set-Cookie headers before interacting' ], expectedResult: 'Only strictly necessary cookies set', regulation: 'ePrivacy, GDPR' }, { id: 'TC-002', description: 'Third-party scripts blocked until consent', steps: [ 'Load page without consent', 'Check Network tab for third-party domains', 'Accept consent', 'Compare network requests' ], expectedResult: 'Marketing/analytics scripts only load after consent', regulation: 'ePrivacy, GDPR' }, { id: 'TC-003', description: 'Consent signal properly transmitted', steps: [ 'Accept consent', 'Check for TCF string if applicable', 'Verify consent signal in requests' ], expectedResult: 'Valid consent signal in subsequent requests', regulation: 'IAB TCF, vendor contracts' }, { id: 'TC-004', description: 'Cookie retention periods match policy', steps: [ 'For each cookie, check expiration', 'Compare to stated retention in policy' ], expectedResult: 'Actual retention matches documented retention', regulation: 'GDPR Art. 5(1)(e)' } ] }, { name: 'Documentation & Policy', tests: [ { id: 'DP-001', description: 'Cookie policy lists all cookies', steps: [ 'Run automated scan', 'Compare to published cookie policy', 'Identify discrepancies' ], expectedResult: 'All cookies documented in policy', regulation: 'GDPR Art. 13, 14' }, { id: 'DP-002', description: 'Purpose descriptions are accurate', steps: [ 'For each documented cookie', 'Verify stated purpose matches actual behavior', 'Test data collection claims' ], expectedResult: 'Documented purposes match reality', regulation: 'GDPR Art. 13, 14' }, { id: 'DP-003', description: 'Third-party vendors listed', steps: [ 'Identify all third-party cookies', 'Check policy for vendor disclosure', 'Verify vendor details are current' ], expectedResult: 'All third parties identified with contact info', regulation: 'GDPR Art. 13(1)(e)' }, { id: 'DP-004', description: 'Data retention periods documented', steps: [ 'Check policy for retention information', 'Verify coverage of all cookie categories' ], expectedResult: 'Clear retention periods for all categories', regulation: 'GDPR Art. 13(2)(a)' } ] } ] }; } async executeTest(test: AuditTest): Promise { // Framework for executing and documenting test results return { testId: test.id, testName: test.description, executed: new Date().toISOString(), status: 'pending', // 'pass' | 'fail' | 'partial' | 'not_applicable' evidence: [], notes: '', remediation: '' }; } } ``` ### Dark Pattern Detection Regulators increasingly focus on dark patterns in consent interfaces. Manual review must check for these: ```typescript // Dark pattern audit checklist class DarkPatternAuditor { auditConsentInterface(screenshotPath: string): DarkPatternAudit { const checks: DarkPatternCheck[] = [ // Visual Manipulation { category: 'Visual Manipulation', pattern: 'Unequal button prominence', description: 'Accept button larger, more colorful, or better positioned than reject', severity: 'high', regulation: 'EDPB Guidelines, CNIL enforcement', testMethod: 'Compare pixel size, color contrast, and position of accept vs reject' }, { category: 'Visual Manipulation', pattern: 'Hidden reject option', description: 'Reject option hidden in settings or requires extra clicks', severity: 'critical', regulation: 'GDPR Art. 7, ePrivacy', testMethod: 'Count clicks required to reject vs accept' }, { category: 'Visual Manipulation', pattern: 'Color psychology abuse', description: 'Green for accept, red for reject implies accept is "correct"', severity: 'medium', regulation: 'EDPB Guidelines', testMethod: 'Analyze color choices for psychological manipulation' }, // Language Manipulation { category: 'Language Manipulation', pattern: 'Confirmshaming', description: 'Reject option uses guilt-inducing language', severity: 'high', regulation: 'EDPB Guidelines, FTC Act', testMethod: 'Review reject button text for emotional manipulation' }, { category: 'Language Manipulation', pattern: 'Misleading descriptions', description: 'Cookie categories described in misleading or vague terms', severity: 'high', regulation: 'GDPR Art. 13, 14', testMethod: 'Compare descriptions to actual cookie behavior' }, { category: 'Language Manipulation', pattern: 'False urgency', description: 'Implying consent is required to use site', severity: 'critical', regulation: 'GDPR Art. 7', testMethod: 'Review banner text for coercive language' }, // Interface Tricks { category: 'Interface Tricks', pattern: 'Consent walls', description: 'Blocking access until consent given', severity: 'critical', regulation: 'GDPR Art. 7, EDPB Guidelines', testMethod: 'Attempt to use site without accepting cookies' }, { category: 'Interface Tricks', pattern: 'Hard to find settings', description: 'Preference center difficult to locate or access', severity: 'high', regulation: 'GDPR Art. 7(3)', testMethod: 'Time how long it takes to find and access preferences' }, { category: 'Interface Tricks', pattern: 'Nested consent', description: 'Requiring multiple clicks to access reject option', severity: 'high', regulation: 'EDPB Guidelines', testMethod: 'Count clicks to reach reject from initial banner' }, // Default Manipulation { category: 'Default Manipulation', pattern: 'Pre-ticked boxes', description: 'Optional consent categories selected by default', severity: 'critical', regulation: 'GDPR Art. 7(2), Planet49 ruling', testMethod: 'Check default state of all preference checkboxes' }, { category: 'Default Manipulation', pattern: 'Bundled consent', description: 'Not offering granular choice for different purposes', severity: 'high', regulation: 'GDPR Art. 7', testMethod: 'Verify ability to consent to individual categories' } ]; return { checks, summary: { totalChecks: checks.length, criticalCount: checks.filter(c => c.severity === 'critical').length, highCount: checks.filter(c => c.severity === 'high').length, mediumCount: checks.filter(c => c.severity === 'medium').length } }; } } ``` ## Phase 3: Vendor Assessment Third-party vendors are often the source of compliance issues. Rigorous vendor assessment is essential. ```typescript // Vendor compliance assessment framework class VendorComplianceAssessor { async assessVendor(vendor: VendorInfo): Promise { const assessment: VendorAssessment = { vendor: vendor.name, assessmentDate: new Date().toISOString(), overallScore: 0, categories: [] }; // Category 1: Legal Framework assessment.categories.push(await this.assessLegalFramework(vendor)); // Category 2: Technical Compliance assessment.categories.push(await this.assessTechnicalCompliance(vendor)); // Category 3: Data Security assessment.categories.push(await this.assessDataSecurity(vendor)); // Category 4: Transparency assessment.categories.push(await this.assessTransparency(vendor)); // Category 5: Incident Response assessment.categories.push(await this.assessIncidentResponse(vendor)); // Calculate overall score assessment.overallScore = this.calculateOverallScore(assessment.categories); // Generate recommendations assessment.recommendations = this.generateRecommendations(assessment); return assessment; } private async assessLegalFramework(vendor: VendorInfo): Promise { const questions: AssessmentQuestion[] = [ { id: 'LF-001', question: 'Does vendor have valid Data Processing Agreement (DPA)?', weight: 10, response: null, evidence: null }, { id: 'LF-002', question: 'Does DPA include all GDPR Art. 28 requirements?', weight: 10, response: null, evidence: null, subQuestions: [ 'Processing only on documented instructions', 'Confidentiality obligations', 'Security measures', 'Sub-processor restrictions', 'Assistance with data subject rights', 'Audit rights', 'Deletion/return obligations', 'Demonstration of compliance' ] }, { id: 'LF-003', question: 'Where is vendor data processed geographically?', weight: 8, response: null, evidence: null }, { id: 'LF-004', question: 'If international transfer, what mechanism is used?', weight: 8, response: null, evidence: null, options: ['SCCs', 'Adequacy decision', 'BCRs', 'Consent', 'None'] }, { id: 'LF-005', question: 'Has vendor conducted Transfer Impact Assessment?', weight: 7, response: null, evidence: null }, { id: 'LF-006', question: 'Does vendor have designated DPO/privacy contact?', weight: 5, response: null, evidence: null }, { id: 'LF-007', question: 'Has vendor been subject to regulatory enforcement?', weight: 8, response: null, evidence: null } ]; return { name: 'Legal Framework', questions, maxScore: questions.reduce((sum, q) => sum + q.weight, 0), score: 0 // Calculated after responses }; } private async assessTechnicalCompliance(vendor: VendorInfo): Promise { const questions: AssessmentQuestion[] = [ { id: 'TC-001', question: 'Does vendor respect consent signals (TCF, GPP, etc.)?', weight: 10, response: null, evidence: null }, { id: 'TC-002', question: 'Does vendor provide mechanisms to honor opt-out?', weight: 10, response: null, evidence: null }, { id: 'TC-003', question: 'Does vendor support data deletion requests?', weight: 9, response: null, evidence: null }, { id: 'TC-004', question: 'What data does vendor collect and process?', weight: 8, response: null, evidence: null }, { id: 'TC-005', question: 'How long does vendor retain data?', weight: 8, response: null, evidence: null }, { id: 'TC-006', question: 'Does vendor share data with additional parties?', weight: 9, response: null, evidence: null }, { id: 'TC-007', question: 'Does vendor use data for own purposes?', weight: 8, response: null, evidence: null } ]; return { name: 'Technical Compliance', questions, maxScore: questions.reduce((sum, q) => sum + q.weight, 0), score: 0 }; } generateVendorRiskReport(assessment: VendorAssessment): VendorRiskReport { const riskLevel = this.calculateRiskLevel(assessment); return { vendor: assessment.vendor, riskLevel, summary: this.generateRiskSummary(assessment, riskLevel), criticalIssues: this.identifyCriticalIssues(assessment), requiredActions: this.determineRequiredActions(assessment), timeline: this.suggestRemediationTimeline(assessment), alternatives: riskLevel === 'critical' ? this.suggestAlternatives(assessment.vendor) : [] }; } } ``` ## Phase 4: Documentation and Evidence Collection Proper documentation is as important as the audit itself. It provides defensible evidence for regulators. ```typescript // Audit documentation framework class AuditDocumentationSystem { async generateAuditReport(auditData: CompletedAudit): Promise { return { metadata: { reportId: this.generateReportId(), organization: auditData.organization, auditDate: auditData.completedDate, auditor: auditData.auditor, scope: auditData.scope, methodology: 'GetCookies Enterprise Audit Framework v2.0' }, executiveSummary: this.generateExecutiveSummary(auditData), findings: { criticalIssues: auditData.findings.filter(f => f.severity === 'critical'), highIssues: auditData.findings.filter(f => f.severity === 'high'), mediumIssues: auditData.findings.filter(f => f.severity === 'medium'), lowIssues: auditData.findings.filter(f => f.severity === 'low'), observations: auditData.findings.filter(f => f.severity === 'observation') }, technicalAnalysis: { cookieInventory: auditData.cookieInventory, storageAnalysis: auditData.storageAnalysis, networkAnalysis: auditData.networkAnalysis, consentFlowAnalysis: auditData.consentFlowAnalysis }, complianceAssessment: { gdpr: this.assessGDPRCompliance(auditData), ccpa: this.assessCCPACompliance(auditData), pecr: this.assessPECRCompliance(auditData), other: auditData.otherRegulations }, vendorAnalysis: auditData.vendorAssessments, recommendations: this.prioritizeRecommendations(auditData), remediationPlan: this.generateRemediationPlan(auditData), appendices: { scanResults: auditData.rawScanResults, testResults: auditData.manualTestResults, screenshots: auditData.screenshots, networkCaptures: auditData.networkCaptures } }; } private generateExecutiveSummary(auditData: CompletedAudit): ExecutiveSummary { const criticalCount = auditData.findings.filter(f => f.severity === 'critical').length; const highCount = auditData.findings.filter(f => f.severity === 'high').length; let overallRating: 'compliant' | 'mostly_compliant' | 'partially_compliant' | 'non_compliant'; if (criticalCount > 0) { overallRating = 'non_compliant'; } else if (highCount > 3) { overallRating = 'partially_compliant'; } else if (highCount > 0) { overallRating = 'mostly_compliant'; } else { overallRating = 'compliant'; } return { overallRating, ratingExplanation: this.explainRating(overallRating, auditData), keyFindings: auditData.findings .filter(f => f.severity === 'critical' || f.severity === 'high') .slice(0, 5) .map(f => ({ issue: f.description, risk: f.riskDescription, immediateAction: f.recommendations[0] })), complianceScores: { gdpr: this.calculateComplianceScore(auditData, 'gdpr'), ccpa: this.calculateComplianceScore(auditData, 'ccpa'), overall: this.calculateComplianceScore(auditData, 'overall') }, recommendedPriorities: this.getTopPriorities(auditData) }; } generateComplianceEvidence(auditData: CompletedAudit): ComplianceEvidence { return { // Evidence for Article 30 - Records of Processing processingRecords: { cookieInventory: auditData.cookieInventory.map(cookie => ({ name: cookie.name, purpose: cookie.purpose, category: cookie.category, legalBasis: cookie.legalBasis, retention: cookie.retention, recipients: cookie.thirdParties })), lastUpdated: new Date().toISOString(), reviewedBy: auditData.auditor }, // Evidence for consent validity consentEvidence: { consentMechanismScreenshots: auditData.screenshots.filter( s => s.type === 'consent_banner' || s.type === 'preference_center' ), consentFlowTests: auditData.manualTestResults.filter( t => t.category === 'consent_functionality' ), preConsentCookieCheck: auditData.scanResults.cookiesBeforeConsent, postRejectCookieCheck: auditData.scanResults.cookiesAfterReject }, // Evidence for data subject rights dataSubjectRights: { accessMechanism: auditData.dataSubjectRightTests.access, deletionMechanism: auditData.dataSubjectRightTests.deletion, portabilityMechanism: auditData.dataSubjectRightTests.portability, preferenceCenter: auditData.screenshots.find(s => s.type === 'preference_center') }, // Evidence for vendor compliance vendorEvidence: auditData.vendorAssessments.map(v => ({ vendorName: v.vendor, dpaStatus: v.dpaStatus, subProcessors: v.subProcessors, dataTransfers: v.dataTransfers, lastReview: v.assessmentDate })), // Audit trail auditTrail: { auditDate: auditData.completedDate, methodology: auditData.methodology, toolsUsed: auditData.toolsUsed, auditor: auditData.auditor, reviewers: auditData.reviewers, nextScheduledAudit: this.calculateNextAuditDate(auditData) } }; } } ``` ## Phase 5: Remediation Tracking Identifying issues is only half the battle. Tracking remediation to completion is equally important. ```typescript // Remediation tracking system class RemediationTracker { private database: RemediationDatabase; async createRemediationPlan( findings: AuditFinding[] ): Promise { const plan: RemediationPlan = { id: this.generatePlanId(), createdDate: new Date().toISOString(), items: [], status: 'active', milestones: [] }; // Sort findings by priority const sortedFindings = this.prioritizeFindings(findings); for (const finding of sortedFindings) { const item: RemediationItem = { id: this.generateItemId(), finding, status: 'open', priority: this.determinePriority(finding), assignee: null, dueDate: this.calculateDueDate(finding), tasks: this.generateTasks(finding), verificationCriteria: this.generateVerificationCriteria(finding), dependencies: this.identifyDependencies(finding, sortedFindings) }; plan.items.push(item); } // Generate milestones plan.milestones = this.generateMilestones(plan.items); return plan; } private generateTasks(finding: AuditFinding): RemediationTask[] { const tasks: RemediationTask[] = []; switch (finding.type) { case 'cookies_before_consent': tasks.push( { id: this.generateTaskId(), description: 'Identify script setting cookies before consent', status: 'pending', estimatedHours: 2 }, { id: this.generateTaskId(), description: 'Implement consent-conditional script loading', status: 'pending', estimatedHours: 4 }, { id: this.generateTaskId(), description: 'Test cookie blocking with automated scan', status: 'pending', estimatedHours: 1 }, { id: this.generateTaskId(), description: 'Verify with manual browser testing', status: 'pending', estimatedHours: 1 } ); break; case 'missing_cookie_documentation': tasks.push( { id: this.generateTaskId(), description: 'Document cookie purpose and legal basis', status: 'pending', estimatedHours: 1 }, { id: this.generateTaskId(), description: 'Update cookie policy', status: 'pending', estimatedHours: 1 }, { id: this.generateTaskId(), description: 'Update CMP cookie categories', status: 'pending', estimatedHours: 0.5 } ); break; case 'dark_pattern_detected': tasks.push( { id: this.generateTaskId(), description: 'Design compliant consent interface', status: 'pending', estimatedHours: 4 }, { id: this.generateTaskId(), description: 'Implement equal prominence for accept/reject', status: 'pending', estimatedHours: 2 }, { id: this.generateTaskId(), description: 'Review language for neutrality', status: 'pending', estimatedHours: 1 }, { id: this.generateTaskId(), description: 'Get legal/compliance review', status: 'pending', estimatedHours: 2 } ); break; case 'vendor_compliance_issue': tasks.push( { id: this.generateTaskId(), description: 'Contact vendor about compliance issue', status: 'pending', estimatedHours: 1 }, { id: this.generateTaskId(), description: 'Request updated DPA if needed', status: 'pending', estimatedHours: 2 }, { id: this.generateTaskId(), description: 'Evaluate alternative vendors if unresolved', status: 'pending', estimatedHours: 4 }, { id: this.generateTaskId(), description: 'Implement technical controls for vendor', status: 'pending', estimatedHours: 3 } ); break; } return tasks; } async verifyRemediation(item: RemediationItem): Promise { const results: VerificationCheck[] = []; for (const criterion of item.verificationCriteria) { const check = await this.performVerificationCheck(criterion); results.push(check); } const allPassed = results.every(r => r.passed); if (allPassed) { await this.updateItemStatus(item.id, 'verified'); } else { await this.updateItemStatus(item.id, 'verification_failed'); } return { itemId: item.id, verificationDate: new Date().toISOString(), checks: results, overallPassed: allPassed, notes: this.generateVerificationNotes(results) }; } generateProgressReport(plan: RemediationPlan): ProgressReport { const statusCounts = { open: plan.items.filter(i => i.status === 'open').length, in_progress: plan.items.filter(i => i.status === 'in_progress').length, pending_verification: plan.items.filter(i => i.status === 'pending_verification').length, verified: plan.items.filter(i => i.status === 'verified').length, blocked: plan.items.filter(i => i.status === 'blocked').length }; const criticalRemaining = plan.items.filter( i => i.priority === 'critical' && i.status !== 'verified' ); const overdueItems = plan.items.filter( i => i.status !== 'verified' && new Date(i.dueDate) < new Date() ); return { planId: plan.id, reportDate: new Date().toISOString(), overallProgress: (statusCounts.verified / plan.items.length) * 100, statusBreakdown: statusCounts, criticalItemsRemaining: criticalRemaining, overdueItems, upcomingMilestones: plan.milestones.filter(m => m.status !== 'completed'), riskAssessment: this.assessRemediationRisk({ criticalRemaining, overdueItems, blockedItems: plan.items.filter(i => i.status === 'blocked') }), recommendations: this.generateProgressRecommendations(plan) }; } } ``` ## Audit Frequency Guidelines | Organization Type | Recommended Frequency | Triggers for Additional Audit | |-------------------|----------------------|------------------------------| | High-traffic consumer | Monthly | Major site updates, new vendors | | E-commerce | Monthly | Seasonal changes, payment updates | | B2B SaaS | Quarterly | New integrations, feature launches | | Healthcare | Monthly | Any system change | | Financial Services | Monthly | Regulatory updates, new products | | Small Business | Quarterly | Website redesigns | | Enterprise | Continuous monitoring + quarterly deep audit | M&A, expansion, incidents | ## Audit Tools and Technology Stack ```typescript // Recommended audit technology stack interface AuditToolStack { automated_scanning: { primary: 'GetCookies Enterprise Scanner'; alternatives: ['Cookiebot', 'OneTrust', 'CookiePro']; capabilities: [ 'Deep cookie discovery', 'Script analysis', 'Network monitoring', 'Fingerprinting detection' ]; }; manual_testing: { browsers: ['Chrome DevTools', 'Firefox Developer Tools']; extensions: ['EditThisCookie', 'GDPR Cookie Consent']; proxies: ['Charles Proxy', 'Burp Suite']; }; documentation: { evidence_collection: ['Full Page Screenshot', 'HAR recording']; report_generation: 'Automated from GetCookies'; version_control: 'Git for policy documents'; }; monitoring: { continuous_scanning: 'Scheduled daily scans'; alerting: 'Slack/email for new cookies'; dashboards: 'Compliance score tracking'; }; } ``` ## Common Audit Findings and Remediation | Finding | Frequency | Typical Cause | Remediation | |---------|-----------|---------------|-------------| | Cookies before consent | 78% of audits | Eager script loading | Implement consent-conditional loading | | Missing cookie documentation | 65% of audits | Dynamic/undocumented scripts | Automated discovery + documentation | | Dark patterns | 45% of audits | Design prioritizing conversions | Redesign with equal prominence | | Third-party non-compliance | 52% of audits | Vendor doesn't honor signals | Vendor negotiation or replacement | | Excessive retention | 38% of audits | Default vendor settings | Configure retention limits | | Missing reject option | 28% of audits | Poor CMP configuration | CMP reconfiguration | | Pre-ticked boxes | 22% of audits | Default CMP settings | Configuration update | ## Frequently Asked Questions ### How often should we conduct cookie compliance audits? The frequency depends on your organization type and risk profile. High-traffic consumer sites and e-commerce should audit monthly, B2B SaaS quarterly at minimum. Additionally, audit whenever you make significant website changes, add new vendors, or when regulations change. ### What should trigger an immediate audit outside the regular schedule? Immediate audits are warranted after major website updates, new third-party integrations, customer complaints about cookie behavior, regulatory enforcement actions in your industry, data breaches, M&A activity, or significant changes to privacy regulations. ### Can automated scanning replace manual testing? No. Automated scanning discovers tracking technologies, but manual testing validates the user experience. Dark patterns, consent flow usability, and policy accuracy all require human judgment. The best approach combines both: automated scanning for comprehensive discovery, manual testing for validation. ### What documentation do regulators typically request during an investigation? Regulators commonly request cookie inventories with legal basis for each, consent mechanism screenshots and testing evidence, records of processing activities, vendor agreements and DPAs, audit reports and remediation records, and evidence of consent withdrawal functionality. ## Building a Culture of Compliance Cookie compliance auditing isn't a one-time project—it's an ongoing discipline. The organizations that succeed are those that build compliance into their culture and processes, not those that treat it as a checkbox exercise. The framework in this guide provides the methodology, but success requires commitment: regular audits, prompt remediation, and continuous improvement. The good news is that this investment pays dividends not just in regulatory risk reduction, but in user trust and operational efficiency. Start with a baseline audit using this framework. Identify your critical issues and create a prioritized remediation plan. Then establish your ongoing monitoring and audit cadence. Within a quarter, you'll have transformed from reactive compliance scrambling to proactive privacy management. The regulators are watching. Your users are paying attention. And now you have the tools to meet their expectations. ## Additional Resources - [ICO Cookie Guidance](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guide-to-pecr/cookies-and-similar-technologies/) - [CNIL Cookie Guidelines](https://www.cnil.fr/en/cookies-and-other-tracking-devices-cnil-publishes-new-guidelines) - [EDPB Guidelines on Consent](https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_en.pdf) - [IAB Europe TCF Documentation](https://iabeurope.eu/transparency-consent-framework/)
M

Marcus Weber, Compliance Director

Přispívající autor GetCookies, specializující se na compliance soukromí, správu souhlasu a optimalizaci digitálního marketingu.

Připraveni zjednodušit souhlas s cookies?

GetCookies dělá GDPR, CCPA a globální compliance soukromí snadné. Začněte ještě dnes.