Introduction

The term “cookieless” has become the hottest buzzword in digital marketing and ad tech. You can’t attend a conference, read a vendor pitch, or scroll through LinkedIn without seeing someone touting their “cookieless solution.”

Google keeps postponing the deprecation of third-party cookies in Chrome. Safari and Firefox have already blocked them. The GDPR and ePrivacy regulations are tightening consent requirements. Everyone’s scrambling for alternatives.

But here’s the uncomfortable question nobody wants to ask: when a solution claims to be “cookieless,” what does that actually mean?

Spoiler alert: in most cases, it means “we’re still tracking you, we’ve just changed the method.”

In this deep dive, we’re not just going to talk theory. We’re going to open the browser DevTools, inspect actual cookies, examine local storage, write JavaScript to detect respawning, and show you exactly how these “cookieless” systems work under the hood.

By the end of this article, you’ll be able to audit any website and see for yourself what tracking techniques they’re really using.

Let’s dive deep.


1. Context: Why is everyone talking about “cookieless”?

To understand the cookieless movement, we need to understand what’s driving it.

Third-party cookie deprecation

Third-party cookies—those set by domains other than the one you’re visiting—have been the backbone of cross-site tracking for decades. But browsers are killing them:

Regulatory pressure

The GDPR and ePrivacy Directive require explicit consent for non-essential cookies. The CNIL (French data protection authority) has issued guidelines that make persistent tracking harder to justify legally.

The industry’s response

Marketing tech vendors saw this coming and pivoted hard. The term “cookieless” became a competitive differentiator. “We’re future-proof!” “We don’t rely on cookies!” “We’re privacy-first!”

But as we’ll see, the reality is far more nuanced—and in many cases, misleading.


2. Master ID: The art of making identifiers persist

One of the most sophisticated “cookieless” techniques is the Master ID approach. Instead of relying on a single cookie, create a network of identifiers that back each other up.

2.1 Commanders Act: CAID + Phoenix

Commanders Act offers a perfect case study with their CAID (Commanders Act ID) system.

What is CAID?

CAID is their Master ID—a first-party cookie that serves as the primary user identifier. It’s a 24-character string combining the year and random numbers, set to expire after 12 months.

The cookies to look for

Open Chrome DevTools (F12) → Application → Cookies → select your domain.

Look for these Commanders Act cookies:

Cookie NamePurposeTypical Duration
CAIDMaster ID (Commanders Act ID)12 months
TCID or tc_idTag Commander ID (legacy)13 months
TC_PRIVACYPrivacy consent settings13 months
tC_CJ_*Customer journey deduplicationVariable
TC_PRIVACY_CENTERPrivacy center display preferences13 months

How to inspect the CAID

javascript

// In browser console
document.cookie.split('; ').find(row => row.startsWith('CAID='))

// Example output:
// "CAID=2025123456789012345678"
// Year (2025) + 20 random digits

The “Phoenix” feature

Commanders Act doesn’t just set one cookie. They create what they call an “identifier graph”—multiple first-party cookies with different expiration dates that all point to the same Master ID.

From their documentation, Phoenix works by:

  1. Creating multiple cookies with staggered expiration dates
  2. Monitoring which cookies are present on each page load
  3. If any cookie from the graph is missing, regenerating it from the others
  4. Maintaining the Master ID as long as at least one cookie survives

Testing Phoenix regeneration

Try this experiment:

javascript

// Step 1: Note your current CAID
const originalCAID = document.cookie.split('; ')
  .find(row => row.startsWith('CAID='))
  ?.split('=')[1];
console.log('Original CAID:', originalCAID);

// Step 2: Delete the CAID cookie
document.cookie = 'CAID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';

// Step 3: Verify it's gone
console.log('CAID after deletion:', 
  document.cookie.split('; ').find(row => row.startsWith('CAID=')));
// Should return undefined

// Step 4: Reload the page and check again
// (You'll need to reload manually)

// Step 5: After reload, check if CAID was regenerated
const newCAID = document.cookie.split('; ')
  .find(row => row.startsWith('CAID='))
  ?.split('=')[1];
console.log('New CAID:', newCAID);
console.log('Match?', originalCAID === newCAID);

If Phoenix is active and other cookies in the graph are still present, the CAID will be regenerated with the same value.

Local Storage integration

Commanders Act also uses local storage as backup:

javascript

// Check local storage for Commanders Act data
Object.keys(localStorage).filter(key => 
  key.includes('TC') || key.includes('commander')
).forEach(key => {
  console.log(key, ':', localStorage.getItem(key));
});

// Common keys to look for:
// - tC.visitor_id
// - TC_PRIVACY
// - tC.id_sync_data

Complete audit script for Commanders Act

javascript

// Comprehensive Commanders Act tracking audit
function auditCommandersAct() {
  console.log('=== COMMANDERS ACT AUDIT ===\n');
  
  // 1. Check cookies
  console.log('COOKIES:');
  const cookies = document.cookie.split('; ');
  const tcCookies = cookies.filter(c => 
    c.startsWith('CAID') || 
    c.startsWith('TC') || 
    c.startsWith('tc_')
  );
  tcCookies.forEach(c => console.log('  ', c));
  
  // 2. Check local storage
  console.log('\nLOCAL STORAGE:');
  Object.keys(localStorage).filter(key => 
    key.toLowerCase().includes('tc') || 
    key.toLowerCase().includes('commander')
  ).forEach(key => {
    console.log('  ', key, ':', localStorage.getItem(key));
  });
  
  // 3. Check session storage
  console.log('\nSESSION STORAGE:');
  Object.keys(sessionStorage).filter(key => 
    key.toLowerCase().includes('tc') || 
    key.toLowerCase().includes('commander')
  ).forEach(key => {
    console.log('  ', key, ':', sessionStorage.getItem(key));
  });
  
  // 4. Extract CAID if present
  const caidCookie = cookies.find(c => c.startsWith('CAID='));
  if (caidCookie) {
    const caid = caidCookie.split('=')[1];
    console.log('\n=== MASTER ID (CAID) ===');
    console.log('Value:', caid);
    console.log('Year:', caid.substring(0, 4));
    console.log('Random ID:', caid.substring(4));
  }
  
  console.log('\n=== END AUDIT ===');
}

// Run the audit
auditCommandersAct();

2.2 Adobe: ECID ecosystem

Adobe takes a slightly different but equally sophisticated approach with their Experience Cloud ID (ECID).

The cookies to look for

Open DevTools → Application → Cookies:

Cookie NamePurposeTypical DurationDomain Type
AMCV_{ORG_ID}Experience Cloud Visitor ID (contains ECID)2 years (truncated to 13 months)First-party
s_ecidECID reference cookie2 yearsFirst-party
s_viLegacy Analytics visitor ID2 years (or 7 days on Safari)First or third-party
s_fidFallback ID when s_vi can’t be set2 yearsFirst-party
demdexCross-domain sync ID (UUID)180 daysThird-party (demdex.net)

Finding your Adobe Organization ID

The {ORG_ID} in the AMCV cookie name is your Adobe Experience Cloud Organization ID.

javascript

// Find Adobe cookies
const adobeCookies = document.cookie.split('; ')
  .filter(c => c.startsWith('AMCV_') || c.startsWith('s_'));

console.log('Adobe cookies found:', adobeCookies);

// Extract Organization ID from AMCV cookie
const amcvCookie = adobeCookies.find(c => c.startsWith('AMCV_'));
if (amcvCookie) {
  const orgId = amcvCookie.split('=')[0].replace('AMCV_', '').split('%')[0];
  console.log('Adobe Org ID:', orgId);
}

Decoding the AMCV cookie

The AMCV cookie contains multiple pieces of information encoded as pipe-separated key-value pairs:

javascript

// Extract and decode AMCV cookie
function decodeAMCV() {
  const amcvCookie = document.cookie.split('; ')
    .find(c => c.startsWith('AMCV_'));
  
  if (!amcvCookie) {
    console.log('No AMCV cookie found');
    return;
  }
  
  const cookieValue = decodeURIComponent(amcvCookie.split('=')[1]);
  const parts = cookieValue.split('|');
  
  console.log('=== AMCV COOKIE STRUCTURE ===');
  for (let i = 0; i < parts.length; i += 2) {
    const key = parts[i];
    const value = parts[i + 1];
    console.log(`${key}: ${value}`);
    
    // Highlight the ECID
    if (key === 'MCMID' || key === 'MID') {
      console.log('  ^^^ THIS IS YOUR ECID (Experience Cloud ID)');
    }
  }
}

decodeAMCV();

// Example output:
// MCMID: 12345678901234567890123456789012345678
//   ^^^ THIS IS YOUR ECID (Experience Cloud ID)
// MCAAMLH: 9
// MCAAMB: base64encodedvalue...
// MCOPTOUT: ...

Extracting the ECID programmatically

If Adobe’s Visitor API is loaded, you can access the ECID directly:

javascript

// Method 1: Using Adobe Visitor API (if available)
if (typeof Visitor !== 'undefined' && window.s && window.s.visitor) {
  const ecid = window.s.visitor.getMarketingCloudVisitorID();
  console.log('ECID from Visitor API:', ecid);
}

// Method 2: Parse from AMCV cookie manually
function getECIDFromCookie() {
  const amcvCookie = document.cookie.split('; ')
    .find(c => c.startsWith('AMCV_'));
  
  if (!amcvCookie) return null;
  
  const cookieValue = decodeURIComponent(amcvCookie.split('=')[1]);
  const parts = cookieValue.split('|');
  
  // Find MCMID or MID (both refer to ECID)
  for (let i = 0; i < parts.length; i += 2) {
    if (parts[i] === 'MCMID' || parts[i] === 'MID') {
      return parts[i + 1];
    }
  }
  
  return null;
}

console.log('ECID from cookie:', getECIDFromCookie());

Testing FPID implementation

First-Party Device ID (FPID) is set server-side by the customer and used to seed the ECID:

javascript

// Check if FPID is being used
function checkFPID() {
  // FPID cookie is typically named something like "FPID" or custom name
  const fpidCookie = document.cookie.split('; ')
    .find(c => c.startsWith('FPID='));
  
  if (fpidCookie) {
    console.log('FPID detected:', fpidCookie.split('=')[1]);
    console.log('FPID is being used to seed ECID');
  } else {
    console.log('No FPID cookie found - using standard ECID generation');
  }
  
  // Check if Web SDK is loaded (required for FPID)
  if (typeof alloy !== 'undefined') {
    console.log('Adobe Web SDK (alloy) detected - FPID supported');
  } else if (typeof s !== 'undefined' && s.version) {
    console.log('Legacy AppMeasurement detected - FPID NOT supported');
    console.log('AppMeasurement version:', s.version);
  }
}

checkFPID();

The ITP problem visualized

javascript

// Check cookie expiration and ITP impact
function checkAdobeCookieExpiration() {
  console.log('=== ADOBE COOKIE EXPIRATION CHECK ===\n');
  
  // Get all cookies (Note: JavaScript can't read actual expiration dates)
  // But we can check if cookies exist and when they were last set
  
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  console.log('Browser:', isSafari ? 'Safari (ITP active)' : 'Other');
  
  if (isSafari) {
    console.log('⚠️  WARNING: Safari ITP limits first-party cookies to 7 days');
    console.log('⚠️  Ad click-through cookies limited to 24 hours');
    console.log('💡 Adobe FPID recommended for this browser');
  }
  
  // Check which cookies are present
  const hasAMCV = document.cookie.includes('AMCV_');
  const hasSVI = document.cookie.includes('s_vi');
  const hasSFID = document.cookie.includes('s_fid');
  const hasFPID = document.cookie.includes('FPID');
  
  console.log('\nCookie presence:');
  console.log('  AMCV (ECID):', hasAMCV ? '✓' : '✗');
  console.log('  s_vi (legacy):', hasSVI ? '✓' : '✗');
  console.log('  s_fid (fallback):', hasSFID ? '✓' : '✗');
  console.log('  FPID (server-side seed):', hasFPID ? '✓' : '✗');
}

checkAdobeCookieExpiration();

Complete Adobe audit script

javascript

// Comprehensive Adobe tracking audit
function auditAdobe() {
  console.log('=== ADOBE EXPERIENCE CLOUD AUDIT ===\n');
  
  // 1. Identify all Adobe cookies
  console.log('COOKIES:');
  const cookies = document.cookie.split('; ');
  const adobeCookies = cookies.filter(c => 
    c.startsWith('AMCV_') || 
    c.startsWith('s_') ||
    c.includes('demdex') ||
    c.startsWith('FPID')
  );
  adobeCookies.forEach(c => {
    const [name, value] = c.split('=');
    console.log(`  ${name}:`, value.substring(0, 50) + (value.length > 50 ? '...' : ''));
  });
  
  // 2. Extract ECID
  console.log('\n=== ECID EXTRACTION ===');
  const ecid = getECIDFromCookie();
  if (ecid) {
    console.log('ECID:', ecid);
    console.log('Length:', ecid.length, 'characters');
  } else {
    console.log('No ECID found');
  }
  
  // 3. Check Adobe libraries
  console.log('\n=== ADOBE LIBRARIES ===');
  console.log('Visitor API:', typeof Visitor !== 'undefined' ? '✓ Loaded' : '✗ Not loaded');
  console.log('AppMeasurement:', typeof s !== 'undefined' ? `✓ Loaded (v${s.version})` : '✗ Not loaded');
  console.log('Web SDK (alloy):', typeof alloy !== 'undefined' ? '✓ Loaded' : '✗ Not loaded');
  
  // 4. Check for FPID
  console.log('\n=== FPID CHECK ===');
  checkFPID();
  
  // 5. Browser compatibility
  console.log('\n=== BROWSER COMPATIBILITY ===');
  checkAdobeCookieExpiration();
  
  console.log('\n=== END AUDIT ===');
}

// Run the audit
auditAdobe();

3. Local Storage: The cookie that doesn’t call itself one

Here’s a favorite trick: stop using cookies and start using local storage. Problem solved, right?

Not quite.

What is local storage?

Local storage is an HTML5 web storage mechanism that allows websites to store data in the browser. Unlike cookies:

Inspecting local storage

Open DevTools → Application → Local Storage → select your domain.

You’ll see key-value pairs. Look for tracking-related keys.

Common patterns to spot

javascript

// List all local storage keys
console.log('=== LOCAL STORAGE KEYS ===');
Object.keys(localStorage).forEach(key => {
  console.log(key, ':', localStorage.getItem(key));
});

// Common tracking-related key patterns:
// - user_id, userId, visitor_id, visitorId
// - session_id, sessionId
// - _ga, _gid (Google Analytics)
// - amplitude_*, mixpanel_*
// - Any UUID-looking values

Example: Google Analytics in local storage

Google Analytics 4 uses local storage extensively:

javascript

// Check for GA4 local storage
Object.keys(localStorage).filter(key => key.startsWith('_ga')).forEach(key => {
  console.log(key, ':', localStorage.getItem(key));
});

// Common GA4 local storage keys:
// _ga_XXXXXXXXXX : Stores client ID and session info
// _ga : Cross-property client ID

The mutual backup strategy

Many tracking solutions store the same identifier in both a cookie and local storage:

javascript

// Example tracking implementation with dual storage
function setTrackingID(id) {
  // Store in cookie
  document.cookie = `user_id=${id}; max-age=31536000; path=/`;
  
  // Also store in local storage
  localStorage.setItem('user_id', id);
  
  console.log('Tracking ID stored in both cookie and local storage');
}

// Retrieve with fallback
function getTrackingID() {
  // Try cookie first
  const cookieMatch = document.cookie.match(/user_id=([^;]+)/);
  if (cookieMatch) return cookieMatch[1];
  
  // Fall back to local storage
  return localStorage.getItem('user_id');
}

// Sync function (runs on page load)
function syncTrackingID() {
  const cookieID = document.cookie.match(/user_id=([^;]+)/)?.[1];
  const storageID = localStorage.getItem('user_id');
  
  if (cookieID && !storageID) {
    // Cookie exists but local storage doesn't - copy to storage
    localStorage.setItem('user_id', cookieID);
    console.log('Restored local storage from cookie');
  } else if (storageID && !cookieID) {
    // Local storage exists but cookie doesn't - recreate cookie
    document.cookie = `user_id=${storageID}; max-age=31536000; path=/`;
    console.log('🔄 COOKIE RESPAWNED from local storage!');
  } else if (cookieID !== storageID) {
    // Conflict - use most recent (in this case, cookie wins)
    localStorage.setItem('user_id', cookieID);
  }
}

// This sync function runs on every page load
syncTrackingID();

Testing for cookie-local storage backup

javascript

// Test if a site uses local storage backup for cookies
function testLocalStorageBackup() {
  console.log('=== TESTING LOCAL STORAGE BACKUP ===\n');
  
  // Step 1: Identify a tracking cookie
  const trackingCookie = document.cookie.split('; ')
    .find(c => c.includes('id') || c.includes('ID'));
  
  if (!trackingCookie) {
    console.log('No obvious tracking cookie found');
    return;
  }
  
  const [cookieName, cookieValue] = trackingCookie.split('=');
  console.log('Found tracking cookie:', cookieName, '=', cookieValue);
  
  // Step 2: Check if same value exists in local storage
  let foundInStorage = false;
  Object.keys(localStorage).forEach(key => {
    const value = localStorage.getItem(key);
    if (value === cookieValue || value.includes(cookieValue)) {
      console.log('✓ FOUND IN LOCAL STORAGE:', key, '=', value);
      foundInStorage = true;
    }
  });
  
  if (!foundInStorage) {
    console.log('Value not found in local storage (yet)');
  }
  
  // Step 3: Delete the cookie and reload to see if it respawns
  console.log('\n📝 EXPERIMENT:');
  console.log('1. Delete this cookie:', cookieName);
  console.log('2. Reload the page');
  console.log('3. Check if cookie reappears with same value');
  console.log('\nTo delete cookie, run:');
  console.log(`document.cookie = '${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'`);
}

testLocalStorageBackup();

Detecting IndexedDB usage

Some sites use IndexedDB for even more persistent storage:

javascript

// Check for IndexedDB usage
async function checkIndexedDB() {
  console.log('=== INDEXEDDB CHECK ===\n');
  
  const databases = await indexedDB.databases();
  
  if (databases.length === 0) {
    console.log('No IndexedDB databases found');
    return;
  }
  
  console.log(`Found ${databases.length} IndexedDB database(s):`);
  databases.forEach(db => {
    console.log(`  - ${db.name} (v${db.version})`);
  });
  
  console.log('\n💡 IndexedDB can store tracking data invisible to cookie inspection');
}

checkIndexedDB();

Complete storage audit script

javascript

// Comprehensive storage audit (cookies + local + session + IndexedDB)
async function auditAllStorage() {
  console.log('=== COMPLETE STORAGE AUDIT ===\n');
  
  // 1. Cookies
  console.log('COOKIES (' + document.cookie.split('; ').length + '):');
  document.cookie.split('; ').forEach(c => {
    const [name, value] = c.split('=');
    console.log(`  ${name}: ${value.substring(0, 40)}${value.length > 40 ? '...' : ''}`);
  });
  
  // 2. Local Storage
  console.log('\nLOCAL STORAGE (' + Object.keys(localStorage).length + ' keys):');
  Object.keys(localStorage).forEach(key => {
    const value = localStorage.getItem(key);
    console.log(`  ${key}: ${value.substring(0, 40)}${value.length > 40 ? '...' : ''}`);
  });
  
  // 3. Session Storage
  console.log('\nSESSION STORAGE (' + Object.keys(sessionStorage).length + ' keys):');
  Object.keys(sessionStorage).forEach(key => {
    const value = sessionStorage.getItem(key);
    console.log(`  ${key}: ${value.substring(0, 40)}${value.length > 40 ? '...' : ''}`);
  });
  
  // 4. IndexedDB
  console.log('\nINDEXEDDB:');
  const databases = await indexedDB.databases();
  if (databases.length > 0) {
    databases.forEach(db => console.log(`  ${db.name} (v${db.version})`));
  } else {
    console.log('  None');
  }
  
  // 5. Calculate total storage
  let totalSize = 0;
  totalSize += new Blob([document.cookie]).size;
  totalSize += new Blob(Object.values(localStorage)).size;
  totalSize += new Blob(Object.values(sessionStorage)).size;
  
  console.log('\nAPPROXIMATE STORAGE SIZE:', (totalSize / 1024).toFixed(2), 'KB');
  
  console.log('\n=== END AUDIT ===');
}

auditAllStorage();

4. Cookie Respawning: Resurrection techniques

Now we get into the really sophisticated stuff: techniques that actively resurrect deleted cookies.

4.1 The principle

Cookie respawning (also called “evercookies” or “zombie cookies”) is the practice of recreating a deleted cookie using backup data stored elsewhere.

The user thinks they’ve deleted their tracking cookie. But the website has secretly stored the same identifier in multiple places. When the site detects the cookie is gone, it automatically recreates it from the backup.

4.2 Detecting cookie respawning in real-time

Let’s build a detector that monitors for cookie respawning:

javascript

// Real-time cookie respawning detector
class CookieRespawnDetector {
  constructor() {
    this.cookieSnapshot = {};
    this.respawnEvents = [];
  }
  
  // Take initial snapshot of all cookies
  takeSnapshot() {
    this.cookieSnapshot = {};
    document.cookie.split('; ').forEach(cookie => {
      const [name, value] = cookie.split('=');
      this.cookieSnapshot[name] = value;
    });
    console.log('📸 Snapshot taken:', Object.keys(this.cookieSnapshot).length, 'cookies');
  }
  
  // Delete all cookies
  deleteAllCookies() {
    console.log('🗑️  Deleting all cookies...');
    document.cookie.split('; ').forEach(cookie => {
      const name = cookie.split('=')[0];
      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
      // Also try with domain variants
      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${location.hostname}`;
    });
    console.log('✓ Cookies deleted');
  }
  
  // Check for respawned cookies
  checkForRespawn() {
    const currentCookies = {};
    document.cookie.split('; ').forEach(cookie => {
      if (cookie.trim()) {
        const [name, value] = cookie.split('=');
        currentCookies[name] = value;
      }
    });
    
    // Compare with snapshot
    Object.keys(this.cookieSnapshot).forEach(name => {
      if (currentCookies[name] === this.cookieSnapshot[name]) {
        const event = {
          name: name,
          value: currentCookies[name],
          timestamp: new Date(),
          type: 'respawned'
        };
        this.respawnEvents.push(event);
        console.log('🧟 RESPAWNED COOKIE DETECTED!', name, '=', currentCookies[name]);
      }
    });
    
    return this.respawnEvents;
  }
  
  // Start monitoring
  startMonitoring(intervalMs = 1000) {
    console.log('👀 Starting respawn monitoring...');
    this.takeSnapshot();
    this.deleteAllCookies();
    
    let checks = 0;
    const maxChecks = 30; // Monitor for 30 seconds
    
    const interval = setInterval(() => {
      checks++;
      console.log(`Check ${checks}/${maxChecks}...`);
      this.checkForRespawn();
      
      if (checks >= maxChecks) {
        clearInterval(interval);
        this.reportResults();
      }
    }, intervalMs);
  }
  
  // Report results
  reportResults() {
    console.log('\n=== RESPAWN DETECTION RESULTS ===');
    if (this.respawnEvents.length === 0) {
      console.log('✓ No cookie respawning detected');
    } else {
      console.log(`⚠️  ${this.respawnEvents.length} cookie(s) respawned:`);
      this.respawnEvents.forEach(event => {
        console.log(`  - ${event.name} (at ${event.timestamp.toLocaleTimeString()})`);
      });
      console.log('\n🚨 This site is using cookie respawning techniques!');
    }
  }
}

// Usage:
const detector = new CookieRespawnDetector();
detector.startMonitoring();

4.3 Browser fingerprinting for respawning

This is the most sophisticated technique, and it’s surprisingly common.

How it works

Instead of storing the identifier client-side, the server creates a “fingerprint” of your browser and associates it with your tracking ID.

Collecting browser fingerprint data

Here’s what a fingerprinting script might collect:

javascript

// Browser fingerprinting data collection (example)
function collectFingerprint() {
  const fingerprint = {
    // Basic browser info
    userAgent: navigator.userAgent,
    language: navigator.language,
    languages: navigator.languages,
    platform: navigator.platform,
    hardwareConcurrency: navigator.hardwareConcurrency,
    deviceMemory: navigator.deviceMemory,
    
    // Screen info
    screenResolution: `${screen.width}x${screen.height}`,
    screenDepth: screen.colorDepth,
    availableScreenSize: `${screen.availWidth}x${screen.availHeight}`,
    
    // Timezone
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    timezoneOffset: new Date().getTimezoneOffset(),
    
    // Canvas fingerprint (simplified)
    canvas: getCanvasFingerprint(),
    
    // WebGL fingerprint (simplified)
    webgl: getWebGLFingerprint(),
    
    // Installed plugins (deprecated in modern browsers)
    plugins: Array.from(navigator.plugins).map(p => p.name),
    
    // Do Not Track
    doNotTrack: navigator.doNotTrack,
    
    // Fonts (requires additional library)
    // fonts: detectFonts(),
    
    // Touch support
    touchSupport: 'ontouchstart' in window,
    maxTouchPoints: navigator.maxTouchPoints,
  };
  
  return fingerprint;
}

// Canvas fingerprinting
function getCanvasFingerprint() {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillText('Browser fingerprint', 2, 2);
  return canvas.toDataURL();
}

// WebGL fingerprinting
function getWebGLFingerprint() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl');
  if (!gl) return null;
  
  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
  return {
    vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
    renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
  };
}

// Generate a hash from fingerprint (simple example)
function hashFingerprint(fingerprint) {
  const str = JSON.stringify(fingerprint);
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash.toString(36);
}

// Usage
const fingerprint = collectFingerprint();
const fingerprintHash = hashFingerprint(fingerprint);
console.log('Fingerprint hash:', fingerprintHash);
console.log('Full fingerprint:', fingerprint);

Detecting fingerprinting activity

javascript

// Detect if a site is fingerprinting you
function detectFingerprinting() {
  console.log('=== FINGERPRINTING DETECTION ===\n');
  
  let fingerprintingSignals = [];
  
  // Check for canvas fingerprinting
  const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
  HTMLCanvasElement.prototype.toDataURL = function() {
    fingerprintingSignals.push({
      type: 'canvas',
      method: 'toDataURL',
      stack: new Error().stack
    });
    console.log('🎨 Canvas fingerprinting detected!');
    return originalToDataURL.apply(this, arguments);
  };
  
  // Check for WebGL fingerprinting
  const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
  WebGLRenderingContext.prototype.getParameter = function(param) {
    const debugInfo = {
      37445: 'UNMASKED_VENDOR_WEBGL',
      37446: 'UNMASKED_RENDERER_WEBGL'
    };
    
    if (debugInfo[param]) {
      fingerprintingSignals.push({
        type: 'webgl',
        parameter: debugInfo[param]
      });
      console.log('🖼️  WebGL fingerprinting detected! Parameter:', debugInfo[param]);
    }
    
    return originalGetParameter.apply(this, arguments);
  };
  
  // Monitor for 10 seconds then report
  setTimeout(() => {
    console.log('\n=== FINGERPRINTING REPORT ===');
    if (fingerprintingSignals.length === 0) {
      console.log('✓ No fingerprinting detected');
    } else {
      console.log(`⚠️  ${fingerprintingSignals.length} fingerprinting signals detected:`);
      fingerprintingSignals.forEach((signal, i) => {
        console.log(`${i+1}. ${signal.type.toUpperCase()}:`, signal);
      });
    }
  }, 10000);
  
  console.log('Monitoring for fingerprinting... (10 seconds)');
}

// Run detection
detectFingerprinting();

Network requests indicating fingerprinting

Look for these patterns in DevTools → Network:

javascript

// Monitor network requests for fingerprinting endpoints
function monitorFingerprintingRequests() {
  // Common fingerprinting endpoint patterns
  const patterns = [
    /fingerprint/i,
    /canvas/i,
    /webgl/i,
    /font.*detect/i,
    /device.*info/i,
    /browser.*info/i
  ];
  
  // Override fetch
  const originalFetch = window.fetch;
  window.fetch = function() {
    const url = arguments[0];
    const urlString = typeof url === 'string' ? url : url.url;
    
    patterns.forEach(pattern => {
      if (pattern.test(urlString)) {
        console.log('🔍 Potential fingerprinting request:', urlString);
      }
    });
    
    return originalFetch.apply(this, arguments);
  };
  
  // Override XMLHttpRequest
  const originalOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function(method, url) {
    patterns.forEach(pattern => {
      if (pattern.test(url)) {
        console.log('🔍 Potential fingerprinting XHR:', url);
      }
    });
    
    return originalOpen.apply(this, arguments);
  };
  
  console.log('👀 Monitoring network requests for fingerprinting...');
}

monitorFingerprintingRequests();

4.4 Complete respawning detection suite

javascript

// Ultimate cookie respawning and fingerprinting detection suite
class TrackingDetector {
  constructor() {
    this.results = {
      cookieRespawning: [],
      fingerprinting: [],
      localStorage: [],
      networkRequests: []
    };
  }
  
  // Run all detection methods
  async runFullScan() {
    console.log('🔬 Starting comprehensive tracking detection...\n');
    
    this.detectCookieRespawning();
    this.detectFingerprinting();
    this.detectLocalStorageBackup();
    this.monitorNetworkRequests();
    
    // Wait 30 seconds then report
    setTimeout(() => {
      this.generateReport();
    }, 30000);
    
    console.log('⏱️  Scan running for 30 seconds...\n');
  }
  
  detectCookieRespawning() {
    const detector = new CookieRespawnDetector();
    detector.startMonitoring(2000); // Check every 2 seconds
    this.results.cookieRespawning = detector.respawnEvents;
  }
  
  detectFingerprinting() {
    // Canvas detection
    const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
    HTMLCanvasElement.prototype.toDataURL = function() {
      this.results.fingerprinting.push({type: 'canvas', timestamp: new Date()});
      return originalToDataURL.apply(this, arguments);
    }.bind(this);
    
    // WebGL detection
    const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(param) {
      if (param === 37445 || param === 37446) {
        this.results.fingerprinting.push({type: 'webgl', timestamp: new Date()});
      }
      return originalGetParameter.apply(this, arguments);
    }.bind(this);
  }
  
  detectLocalStorageBackup() {
    // Check if cookie values exist in localStorage
    const cookies = document.cookie.split('; ').reduce((acc, c) => {
      const [name, value] = c.split('=');
      acc[name] = value;
      return acc;
    }, {});
    
    Object.keys(localStorage).forEach(key => {
      const value = localStorage.getItem(key);
      Object.entries(cookies).forEach(([cookieName, cookieValue]) => {
        if (value.includes(cookieValue) || cookieValue.includes(value)) {
          this.results.localStorage.push({
            localStorageKey: key,
            cookieName: cookieName,
            matched: true
          });
        }
      });
    });
  }
  
  monitorNetworkRequests() {
    const patterns = [/fingerprint/i, /canvas/i, /webgl/i, /track/i, /collect/i];
    
    const originalFetch = window.fetch;
    window.fetch = function() {
      const url = arguments[0];
      const urlString = typeof url === 'string' ? url : url.url;
      
      patterns.forEach(pattern => {
        if (pattern.test(urlString)) {
          this.results.networkRequests.push({url: urlString, timestamp: new Date()});
        }
      });
      
      return originalFetch.apply(this, arguments);
    }.bind(this);
  }
  
  generateReport() {
    console.log('\n═══════════════════════════════════════');
    console.log('     TRACKING DETECTION REPORT');
    console.log('═══════════════════════════════════════\n');
    
    // Cookie respawning
    console.log('🧟 COOKIE RESPAWNING:');
    if (this.results.cookieRespawning.length === 0) {
      console.log('  ✓ None detected');
    } else {
      console.log(`  ⚠️  ${this.results.cookieRespawning.length} instances`);
      this.results.cookieRespawning.forEach(e => console.log(`    - ${e.name}`));
    }
    
    // Fingerprinting
    console.log('\n🔍 FINGERPRINTING:');
    if (this.results.fingerprinting.length === 0) {
      console.log('  ✓ None detected');
    } else {
      console.log(`  ⚠️  ${this.results.fingerprinting.length} instances`);
      const byType = this.results.fingerprinting.reduce((acc, f) => {
        acc[f.type] = (acc[f.type] || 0) + 1;
        return acc;
      }, {});
      Object.entries(byType).forEach(([type, count]) => {
        console.log(`    - ${type}: ${count}x`);
      });
    }
    
    // Local storage backup
    console.log('\n💾 LOCAL STORAGE BACKUP:');
    if (this.results.localStorage.length === 0) {
      console.log('  ✓ No cookie-localStorage pairing detected');
    } else {
      console.log(`  ⚠️  ${this.results.localStorage.length} pairings found`);
      this.results.localStorage.forEach(p => {
        console.log(`    - ${p.localStorageKey} ↔ ${p.cookieName}`);
      });
    }
    
    // Network requests
    console.log('\n🌐 SUSPICIOUS NETWORK REQUESTS:');
    if (this.results.networkRequests.length === 0) {
      console.log('  ✓ None detected');
    } else {
      console.log(`  ⚠️  ${this.results.networkRequests.length} requests`);
      this.results.networkRequests.slice(0, 5).forEach(r => {
        console.log(`    - ${r.url.substring(0, 60)}...`);
      });
    }
    
    // Overall verdict
    console.log('\n═══════════════════════════════════════');
    const totalIssues = this.results.cookieRespawning.length + 
                        this.results.fingerprinting.length +
                        this.results.localStorage.length +
                        this.results.networkRequests.length;
    
    if (totalIssues === 0) {
      console.log('✅ VERDICT: No aggressive tracking detected');
    } else if (totalIssues < 5) {
      console.log('⚠️  VERDICT: Some tracking techniques detected');
    } else {
      console.log('🚨 VERDICT: Multiple aggressive tracking techniques in use!');
    }
    console.log('═══════════════════════════════════════\n');
  }
}

// Run the full scan
const scanner = new TrackingDetector();
scanner.runFullScan();

5. The reality on the ground: What “cookieless” actually means

Let’s be clear about the spectrum of “cookieless” claims:

Level 1: “No third-party cookies”

“We only use first-party cookies!”

This is the most common claim. Technically accurate—they’re not using third-party cookies. But they’re absolutely still using cookies for persistent tracking, just on the first-party domain.

How to verify:

javascript

// Check if cookies are first-party or third-party
document.cookie.split('; ').forEach(cookie => {
  const name = cookie.split('=')[0];
  console.log(`${name}: first-party (by definition, since we can read it)`);
});

// Third-party cookies can't be accessed via JavaScript from the current page
// They're only visible in DevTools → Application → Cookies → other domains

Level 2: “No client-side cookies”

“We don’t set cookies with JavaScript!”

Instead, cookies are set server-side via HTTP Set-Cookie headers.

How to verify:

javascript

// Check Network tab for Set-Cookie headers
// DevTools → Network → select a request → Headers → Response Headers
// Look for "Set-Cookie"

Level 3: “No cookies, just local storage”

“We use local storage instead of cookies!”

As discussed earlier, this is privacy theater.

How to verify:

javascript

// Already covered in section 3
auditAllStorage();

Level 4: “Server-side tracking”

“All tracking happens server-side!”

The identifier might not be stored client-side at all. Instead, server-side session management uses IP + User Agent + other request headers.

How to verify: Look for tracking parameters in URLs:

javascript

// Check for tracking parameters in current URL
const url = new URL(window.location.href);
const trackingParams = ['utm_', 'fbclid', 'gclid', '_ga', 'mc_', 'msclkid'];

console.log('Tracking parameters in URL:');
trackingParams.forEach(param => {
  url.searchParams.forEach((value, key) => {
    if (key.startsWith(param)) {
      console.log(`  ${key}: ${value}`);
    }
  });
});

Level 5: “Pure fingerprinting”

“We don’t store anything client-side!”

Already covered in section 4.3.


6. The legal questions (I’m not a lawyer, but…)

The GDPR applies to tracking, not just cookies

The GDPR regulates the processing of personal data. An advertising ID, analytics ID, or any persistent identifier that can track you across sessions is personal data.

It doesn’t matter if it’s stored in:

If it identifies you, it’s regulated.

What about local storage?

The ePrivacy Directive (the “Cookie Law”) specifically mentions “cookies and similar technologies.”

The CNIL’s position: local storage used for tracking or advertising purposes requires consent, just like cookies.

Is fingerprinting legal?

Browser fingerprinting processes device and browser characteristics to create a unique identifier. Under the GDPR, this is personal data processing.

The CNIL has stated that fingerprinting for tracking purposes requires consent.

Cookie respawning = consent bypass?

If a user deletes a tracking cookie, they’ve clearly indicated they don’t want to be tracked.

Automatically recreating that cookie from backup storage or fingerprinting data arguably:

Potential penalties: up to €20 million or 4% of global annual revenue.

Yet hundreds of major websites do this every day.


7. What really matters: Transparency

Here’s my take after 10 years in ad tech and analytics:

“Cookieless” has become a marketing wording

The term no longer means what it says. It’s a competitive differentiator that obscures more than it clarifies.

When a vendor says “cookieless,” you need to ask:

User identification isn’t inherently evil

There are legitimate reasons for persistent user identification:

But transparency and respect must come first

If you’re going to identify users:

  1. Tell them clearly what you’re doing
  2. Explain why you’re doing it
  3. Give them real control over their data
  4. Respect their choices when they opt out

Don’t claim to be “cookieless” while using six different persistence techniques.
Don’t regenerate identifiers after users delete them.
Don’t hide behind technical jargon.


Conclusion

“Cookieless” exists on a spectrum. It’s not binary.

The techniques we’ve covered—Master IDs, Phoenix resurrection, local storage backups, fingerprinting, cookie respawning—are all real. They’re used by major platforms serving billions of users.

Now you have the tools to audit any website yourself: