From 381fa9584248da14246d698054f4bc3b699ca8e0 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 1 Jul 2025 10:24:59 +0200 Subject: [PATCH 01/12] cleanup --- dist/privacy.js | 86 +------------------------------------------------ 1 file changed, 1 insertion(+), 85 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index cdf3c96..ca1438d 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -87,90 +87,6 @@ const parseDSRdelete = async (response) => { } let sync_metrics = { - /** - * Privacy policies - * Wording sourced from: https://github.com/RUB-SysSec/we-value-your-privacy/blob/master/privacy_wording.json - * words = privacy_wording.map(country => country.words).filter((v, i, a) => a.indexOf(v) === i).flat().sort().join('|'); - */ - privacy_wording_links: (() => { - const languageKeywords = { - af: "beskyttelse af personlige oplysninger|privatlivspolitik|persondata", - ar: "الخصوصية|سياسة البيانات|سياسة الخصوصية|سياسة الخصوصية والبيانات", - az: "məxfilik|şəxsi məlumatlar", - be: "абарона дадзеных|палітыка прыватнасці", - bg: "поверителност|политика за бисквитки|политика за данни|условия|условия за ползване|политика за поверителност", - bn: "গোপনীয়তা|ডেটা নীতি|গোপনীয়তা নীতি", - bs: "privatnost|politika privatnosti|politika podataka|pravila o privatnosti", - ca: "protecció de dades|política de privacitat", - cs: "ochrana dat|ochrana osobních údajů|ochrana soukromí|ochrana súkromia|ochrana udaju|ochrana údajov|ochrany osobných údajov|podmínky|soukromi|soukromí|zásady používání dat|zásady používání cookies", - da: "cookiepolitik|datapolicy|beskyttelse af personlige oplysninger|personlige data|personoplysninger|privatlivspolitik|regler om fortrolighed", - de: "datenrichtlinie|datenschutz|datenschutzbestimmungen|datenschutzrichtlinie|privatssphäre|cookie-richtlinie|privatsphärenerklärung", - el: "απόρρητο|πολιτική απορρήτου|πολιτική δεδομένων|προσωπικά δεδομένα|όροι και γνωστοποιήσεις|πολιτική cookies", - en: "cookie policy|cookies|data policy|datapolicy|privacy|privacy policy|cookiepolicy", - es: "aviso legal|confidencialidad|confidencialite|confidentialité|política de datos|privacidad|privacidad|politica de datos|política de privacidad|política de cookies", - et: "andmekaitsetingimused|isikuandmete|isikuandmete töötlemise|kasutustingimused|privaatsuspoliitika|andmepoliitika|küpsisepoliitika", - eu: "privatua|datu pertsonalen babesa|datu pertsonalen politika", - fa: "حریم خصوصی|سیاست حفظ حریم خصوصی|سیاست داده|داده های شخصی", - fi: "yksityisyyden suoja|yksityisyydensuoja|yksityisyys|tietokäytäntö|tietosuoja|tietosuojakäytäntö|tietosuojaseloste|evästekäytäntö", - fil: "patakaran sa cookies", - fr: "cgu|cgv|confidentialité|mentions légales|politique d’utilisation des données|rgpd|vie privée|politique de confidentialité|politique de données|politique de cookie", - ga: "beartas príobháideachta|beartas sonraí|beartas fianán|beartas sonraí pearsanta", - he: "מדיניות נתונים|פרטיות", - hi: "गोपनीयता|डेटा नीति|गोपनीयता नीति", - hr: "privatnost|pravila o privatnosti|pravila o podacima|pravila o kolačićima", - hu: "adatvédelem|adatvédelmi|személyes adatok védelme|adatvédelmi nyilatkozat|adatkezelési tájékoztató|cookie-kra vonatkozó irányelv", - id: "integritetspolicy|piškotki|kebijakan privasi", - is: "persónuvernd|persónuverndarstefna", - it: "normativa sui dati|privatezza|informativa sulla privacy|informativa sui dati|informativa sui cookie|politica dei dati|politica dei cookies", - ja: "プライバシー|データポリシー|個人情報保護", - ko: "개인정보|개인정보 처리방침|개인정보 보호정책|개인정보 보호|정보 처리 방침", - ka: "კერძო წამყვანი|პირადი ინფორმაციის დაცვა|პირადი ინფორმაციის პოლიტიკა", - lt: "privatumas|privatumo|slapukai|slapukkih|privatumo politika|duomenų politika|slapukų politika|privatumo pareiškimas", - lv: "sīkdatne|sīkdatņu|privātuma|privātums|privātuma politika|datu politika|sīkdatņu politika|privātuma politikas paziņojums", - mt: "politika dwar il-privatezza|politika tad-data|politika tal-cookies|politika dwar id-dati", - ms: "privasi|polisi data|polisi privasi|data peribadi|terma dan syarat", - nb: "personvern|informasjonskapselregler", - nl: "gegevensbeleid|privacybeleid|cookiebeleid|privacyverklaring", - no: "personvern|personvernerklæring|informasjonskapsler|personvernspolicy", - pl: "prywatnosci|prywatności|prywatność|zasady dotyczące danych|polityka prywatności|polityka danych|polityka plików cookie", - pt: "privacidade|política de privacidade|política de dados|política de cookies", - ro: "confidențialitate|politica de utilizare|protectia datelor|politica de confidențialitate|politica de date|politica cookie", - ru: "конфиденциальность|политика использования данных|политика конфиденциальности|политика данных|политика файлов cookie|персональных данных", - si: "piškotki", - sk: "ochrana osobných údajov|zásady ochrany osobných|zásady používání dat|zásady využívania údajov|zásady ochrany osobných údajov|zásady používania údajov|zásady používania cookies|ochrana údajov", - sl: "piškotki|varstvo podatkov|zasebnost|pravilnik o zasebnosti|pravilnik o podatkih|pravilnik o piškotkih|politika zasebnosti", - sq: "konfidencialiteti|politika e privatësisë|politika e të dhënave personale", - sr: "konfidentsiaalsuse|pravila o upotrebi podataka|privatnost|privatnosti|prywatnosci|prywatności|prywatność|protecţia datelor|политика о подацима|приватност|защита података", - sv: "integritetspolicy|personuppgifter|privatlivspolitik|sekretess|webbplatsen|yksityisyyden suoja|yksityisyydensuoja|yksityisyys|datapolitik", - sw: "política de datos", - tr: "gizlilik|kişisel verilerin korunması|politika e të dhënave|politikat e privatesise|politikat e privatësisë|veri i̇lkesi|veri politikası|gizlilik politikası|veri politikası|çerez politikası", - th: "ความเป็นส่วนตัว|นโยบายความเป็นส่วนตัว|นโยบายข้อมูล|ข้อมูลส่วนบุคคล|เงื่อนไข", - vi: "quyền riêng tư|chính sách bảo mật|chính sách dữ liệu|dữ liệu cá nhân|điều khoản và điều kiện", - uk: "конфіденційність|конфіденційності|політика даних|файлів cookie|персональних даних|захисту даних", - zh: "数据使用政策|隐私政策|数据保护政策|隐私保护政策|數據使用政策|隱私政策|數據保護政策|隱私保護政策" - } - const websiteLanguage = document.documentElement.lang.slice(0, 2).toLowerCase(); - let keywords; - if (websiteLanguage == 'en') { - keywords = languageKeywords[websiteLanguage] - } else if (!(websiteLanguage in languageKeywords)) { - keywords = Object.values(languageKeywords).join('|'); - } else { - keywords = languageKeywords[websiteLanguage] + '|' + languageKeywords['en'] - } - const pattern = new RegExp(`(?:${keywords})`, 'gi'); - - const privacy_links = Array.from(document.querySelectorAll('a')).filter(a => - pattern.test(a.innerText) - ).map( - a => ({ - text: a.innerText, - }) - ); - - return privacy_links; - })(), - // Consent Management Platforms /** @@ -250,7 +166,7 @@ let sync_metrics = { })(), /** - * Global Privacy Platfrom (GPP) + * Global Privacy Protocol (GPP) * https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform */ iab_gpp: (() => { From 0a8ed2338dbc332b4453510e81848b33b8141fc4 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:03:32 +0200 Subject: [PATCH 02/12] generalized fingerprints --- dist/privacy.js | 210 +++++++++++++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 83 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index ca1438d..708c152 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -296,112 +296,156 @@ let sync_metrics = { return rp; })(), - /** - * Media devices - * https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices - */ - media_devices: { - navigator_mediaDevices_enumerateDevices: testPropertyStringInResponseBodies( - 'mediaDevices.+enumerateDevices' - ), - navigator_mediaDevices_getUserMedia: testPropertyStringInResponseBodies( - 'mediaDevices.+getUserMedia' - ), - navigator_mediaDevices_getDisplayMedia: testPropertyStringInResponseBodies( - 'mediaDevices.+getDisplayMedia' - ), - }, - - /** - * Geolocation API - * https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API - */ - geolocation: { - navigator_geolocation_getCurrentPosition: testPropertyStringInResponseBodies( - 'geolocation.+getCurrentPosition' - ), - navigator_geolocation_watchPosition: testPropertyStringInResponseBodies( - 'geolocation.+watchPosition' - ), - }, - fingerprinting: (() => { - //These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs + // These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs const fingerprintingAPIs = [ - 'ApplePaySession.canMakePayments', - 'getChannelData', //audioContext - 'toDataURL', //canvas - 'getImageData', //canvas, not actually used by fingerprintJS - 'screen.colorDepth', - 'color-gamut', - 'prefers-contrast', + // Payment APIs + 'ApplePaySession\\.canMakePayments', + + // Audio fingerprinting + 'createAnalyser', + 'createOscillator', + 'createScriptProcessor', + 'getChannelData', + 'getFloatFrequencyData', + 'getByteFrequencyData', + 'OscillatorNode', + + // Canvas fingerprinting + 'canvas\\.getContext', + 'canvas\\.toDataURL', + 'canvasRenderingContext2D\\.fillText', + 'canvasRenderingContext2D\\.strokeText', + 'canvasRenderingContext2D\\.getImageData', + 'HTMLCanvasElement\\.toBlob', + + // CSS media queries for fingerprinting + '@media.*color-gamut', + '@media.*prefers-contrast', + '@media.*forced-colors', + '@media.*dynamic-range', + '@media.*inverted-colors', + '@media.*min-monochrome', + '@media.*max-monochrome', + '@media.*prefers-reduced-motion', + '@media.*prefers-reduced-transparency', + + // Hardware fingerprinting 'cpuClass', 'deviceMemory', - 'forced-colors', 'hardwareConcurrency', - 'dynamic-range', + 'maxTouchPoints', + 'ontouchstart', + + // Storage APIs (potential fingerprinting) 'indexedDB', - 'inverted-colors', - 'navigator.language', //"language" would be too generic here - 'navigator.userLanguage', //TODO exists? 'localStorage', - 'min-monochrome', - 'max-monochrome', + 'sessionStorage', 'openDatabase', - 'navigator.oscpu', + + // PDF and plugins 'pdfViewerEnabled', - 'navigator.platform', //"platform" would be too generic - 'navigator.plugins', + + // Attribution and tracking 'attributionSourceId', - 'prefers-reduced-motion', - 'prefers-reduced-transparency', - 'availWidth', - 'availHeight', - 'screen.width', - 'screen.height', - 'sessionStorage', - 'resolvedOptions().timeZone', + + // Time zone fingerprinting + 'resolvedOptions\\(\\)\\.timeZone', 'getTimezoneOffset', - 'maxTouchPoints', - 'ontouchstart', - 'navigator.vendor', + + // WebGL fingerprinting 'vendorUnmasked', 'rendererUnmasked', 'shadingLanguageVersion', 'WEBGL_debug_renderer_info', - 'getShaderPrecisionFormat' - ].map(api => api.toLowerCase()) + 'getShaderPrecisionFormat', - const response_bodies = $WPT_BODIES.filter(body => (body.response_body && (body.type === 'Document' || body.type === 'Script'))) - - let fingerprintingUsageCounts = {} - let likelyFingerprintingScripts = [] + // Screen properties + 'availWidth', + 'availHeight', + 'screen\\.width', + 'screen\\.height', + 'screen\\.colorDepth', + + // Navigator properties + 'navigator\\.platform', + 'navigator\\.plugins', + 'navigator\\.language', + 'navigator\\.oscpu', + 'navigator\\.vendor', + 'navigator\\.getBattery', + 'navigator\\.getGamepads', + + // Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API + 'getCurrentPosition', + 'watchPosition', + + // Media devices: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices + 'enumerateDevices', + 'getUserMedia', + 'getDisplayMedia', + + // Additional modern fingerprinting vectors + 'RTCPeerConnection', + 'document\\.fonts', + 'performance\\.memory', + ]; + + // Pre-compile regexes - handle already escaped patterns + const compiledRegexes = fingerprintingAPIs.map(api => ({ + api, + regex: new RegExp(api, 'gi') + })); + let likelyFingerprintingScripts = []; response_bodies.forEach(req => { - let total_occurrences = 0 - - let body = req.response_body.toLowerCase() + try { + let detectedApis = []; + let totalOccurrences = 0; + + compiledRegexes.forEach(({ api, regex }) => { + try { + // Reset regex index for global regex + regex.lastIndex = 0; + + // Use a more memory-efficient counting approach + let match; + let matches = 0; + while ((match = regex.exec(req.response_body)) !== null) { + matches++; + // Prevent infinite loops on zero-length matches + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + } - fingerprintingAPIs.forEach(api => { - let api_occurrences = 0 - let index = body.indexOf(api) - while (index !== -1) { - api_occurrences++ - index = body.indexOf(api, index + 1) - } + if (matches > 0) { + detectedApis.push(api); + totalOccurrences += matches; + } + } catch (regexError) { + // Skip this API on regex error - avoid console.warn in WebPageTest + } + }); - if (api_occurrences > 0) { - fingerprintingUsageCounts[api] = (fingerprintingUsageCounts[api] || 0) + api_occurrences + // Track scripts with significant fingerprinting API usage (threshold: 4+ APIs or high occurrence count) + const suspicionScore = Math.round((detectedApis.length * 2 + totalOccurrences) / 3); + if (detectedApis.length >= 4 || suspicionScore >= 8) { + likelyFingerprintingScripts.push({ + url: req.url, + detectedApis: detectedApis, + suspicionScore: suspicionScore + }); } - total_occurrences += api_occurrences - }) - - if (total_occurrences >= 5) { //TODO what should this threshold be? - likelyFingerprintingScripts.push(req.url) + } catch (error) { + // Skip this request on error - avoid console.warn in WebPageTest } - }) + }); + + // Sort by suspicion score (highest first) + likelyFingerprintingScripts.sort((a, b) => b.suspicionScore - a.suspicionScore); - return { counts: fingerprintingUsageCounts, likelyFingerprintingScripts } + return likelyFingerprintingScripts; })(), /** From 782c75ca517326c5dfdb1d13b4974fabe5b96424 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:09:52 +0200 Subject: [PATCH 03/12] unescape --- dist/privacy.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 708c152..49b3e75 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -300,7 +300,7 @@ let sync_metrics = { // These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs const fingerprintingAPIs = [ // Payment APIs - 'ApplePaySession\\.canMakePayments', + 'ApplePaySession\.canMakePayments', // Audio fingerprinting 'createAnalyser', @@ -312,12 +312,12 @@ let sync_metrics = { 'OscillatorNode', // Canvas fingerprinting - 'canvas\\.getContext', - 'canvas\\.toDataURL', - 'canvasRenderingContext2D\\.fillText', - 'canvasRenderingContext2D\\.strokeText', - 'canvasRenderingContext2D\\.getImageData', - 'HTMLCanvasElement\\.toBlob', + 'canvas\.getContext', + 'canvas\.toDataURL', + 'canvasRenderingContext2D\.fillText', + 'canvasRenderingContext2D\.strokeText', + 'canvasRenderingContext2D\.getImageData', + 'HTMLCanvasElement\.toBlob', // CSS media queries for fingerprinting '@media.*color-gamut', @@ -350,7 +350,7 @@ let sync_metrics = { 'attributionSourceId', // Time zone fingerprinting - 'resolvedOptions\\(\\)\\.timeZone', + 'resolvedOptions\(\)\.timeZone', 'getTimezoneOffset', // WebGL fingerprinting @@ -363,18 +363,18 @@ let sync_metrics = { // Screen properties 'availWidth', 'availHeight', - 'screen\\.width', - 'screen\\.height', - 'screen\\.colorDepth', + 'screen\.width', + 'screen\.height', + 'screen\.colorDepth', // Navigator properties - 'navigator\\.platform', - 'navigator\\.plugins', - 'navigator\\.language', - 'navigator\\.oscpu', - 'navigator\\.vendor', - 'navigator\\.getBattery', - 'navigator\\.getGamepads', + 'navigator\.platform', + 'navigator\.plugins', + 'navigator\.language', + 'navigator\.oscpu', + 'navigator\.vendor', + 'navigator\.getBattery', + 'navigator\.getGamepads', // Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API 'getCurrentPosition', @@ -387,8 +387,8 @@ let sync_metrics = { // Additional modern fingerprinting vectors 'RTCPeerConnection', - 'document\\.fonts', - 'performance\\.memory', + 'document\.fonts', + 'performance\.memory', ]; // Pre-compile regexes - handle already escaped patterns From f2322c04726bdfc0218cb8a2d9f8d6140b949db2 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:42:53 +0200 Subject: [PATCH 04/12] more APIs --- dist/privacy.js | 181 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 160 insertions(+), 21 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 49b3e75..620035a 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -302,6 +302,16 @@ let sync_metrics = { // Payment APIs 'ApplePaySession\.canMakePayments', + // User Agent and Platform fingerprinting + 'navigator\.userAgent', + 'navigator\.platform', + 'navigator\.oscpu', + 'navigator\.vendor', + 'navigator\.vendorSub', + 'navigator\.product', + 'navigator\.productSub', + 'navigator\.buildID', + // Audio fingerprinting 'createAnalyser', 'createOscillator', @@ -310,6 +320,19 @@ let sync_metrics = { 'getFloatFrequencyData', 'getByteFrequencyData', 'OscillatorNode', + 'AudioContext', + 'webkitAudioContext', + 'AnalyserNode', + 'createDynamicsCompressor', + 'channelCount', + 'channelCountMode', + 'channelInterpretation', + 'fftSize', + 'frequencyBinCount', + 'maxDecibels', + 'minDecibels', + 'smoothingTimeConstant', + 'sampleRate', // Canvas fingerprinting 'canvas\.getContext', @@ -318,6 +341,8 @@ let sync_metrics = { 'canvasRenderingContext2D\.strokeText', 'canvasRenderingContext2D\.getImageData', 'HTMLCanvasElement\.toBlob', + 'getContext\(.*2d.*\)', + 'getContext\(.*webgl.*\)', // CSS media queries for fingerprinting '@media.*color-gamut', @@ -345,13 +370,21 @@ let sync_metrics = { // PDF and plugins 'pdfViewerEnabled', + 'navigator\.plugins', + 'navigator\.mimeTypes', + 'Plugin\s', + 'MimeType', // Attribution and tracking 'attributionSourceId', - // Time zone fingerprinting + // Time zone and language fingerprinting 'resolvedOptions\(\)\.timeZone', 'getTimezoneOffset', + 'navigator\.language', + 'navigator\.languages', + 'Intl\.DateTimeFormat', + 'Intl\.Collator', // WebGL fingerprinting 'vendorUnmasked', @@ -359,6 +392,14 @@ let sync_metrics = { 'shadingLanguageVersion', 'WEBGL_debug_renderer_info', 'getShaderPrecisionFormat', + 'WebGLRenderingContext', + 'getParameter', + 'getSupportedExtensions', + 'getExtension', + 'VENDOR', + 'RENDERER', + 'VERSION', + 'SHADING_LANGUAGE_VERSION', // Screen properties 'availWidth', @@ -366,29 +407,126 @@ let sync_metrics = { 'screen\.width', 'screen\.height', 'screen\.colorDepth', - - // Navigator properties - 'navigator\.platform', - 'navigator\.plugins', - 'navigator\.language', - 'navigator\.oscpu', - 'navigator\.vendor', - 'navigator\.getBattery', - 'navigator\.getGamepads', + 'screen\.pixelDepth', + 'screen\.availTop', + 'screen\.availLeft', + 'outerWidth', + 'outerHeight', + 'innerWidth', + 'innerHeight', + 'devicePixelRatio', + + // Window and browser chrome fingerprinting + 'locationbar', + 'menubar', + 'personalbar', + 'scrollbars', + 'statusbar', + 'toolbar', + 'history\.length', // Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API 'getCurrentPosition', 'watchPosition', + 'navigator\.geolocation', - // Media devices: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices + // Media devices and capabilities 'enumerateDevices', 'getUserMedia', 'getDisplayMedia', + 'navigator\.mediaDevices', + 'canPlayType', + 'HTMLVideoElement\.canPlayType', + 'HTMLAudioElement\.canPlayType', + + // Permissions API + 'navigator\.permissions', + 'permissions\.query', + + // Battery API + 'navigator\.battery', + 'navigator\.getBattery', + 'charging', + 'chargingTime', + 'dischargingTime', + // 'level', + + // Connection API + 'navigator\.connection', + 'navigator\.mozConnection', + 'navigator\.webkitConnection', + 'downlink', + 'effectiveType', + // 'rtt', + // 'saveData', + + // Sensors APIs + 'Accelerometer', + 'Gyroscope', + 'LinearAccelerationSensor', + 'AbsoluteOrientationSensor', + 'RelativeOrientationSensor', + 'AmbientLightSensor', + 'ProximitySensor', + + // Touch and input + 'TouchEvent', + 'createTouch', + 'createTouchList', + + // Font detection + 'document\.fonts', + 'FontFace', + + // Do Not Track + 'navigator\.doNotTrack', + 'window\.doNotTrack', + + // Cookie detection + 'navigator\.cookieEnabled', + + // Java detection + 'navigator\.javaEnabled', // Additional modern fingerprinting vectors 'RTCPeerConnection', - 'document\.fonts', + 'webkitRTCPeerConnection', + 'mozRTCPeerConnection', 'performance\.memory', + 'performance\.timing', + 'Notification\.permission', + + // Keyboard layout detection + 'KeyboardLayoutMap', + 'navigator\.keyboard', + 'getLayoutMap', + + // Gamepad API + 'navigator\.getGamepads', + 'GamepadEvent', + + // Storage quota + 'navigator\.storage', + 'navigator\.webkitTemporaryStorage', + 'navigator\.webkitPersistentStorage', + 'estimate', + + // Speech APIs + 'SpeechSynthesis', + 'SpeechRecognition', + + // WebRTC Data Channel + 'RTCDataChannel', + 'createDataChannel', + + // Crypto subtle fingerprinting + 'crypto\.subtle', + 'SubtleCrypto', + + // Worker capabilities + 'Worker', + 'SharedWorker', + 'ServiceWorker' ]; // Pre-compile regexes - handle already escaped patterns @@ -402,6 +540,12 @@ let sync_metrics = { try { let detectedApis = []; let totalOccurrences = 0; + let body = req.response_body; + + // Validate response body + if (!body || typeof body !== 'string') { + return; // Skip invalid response bodies + } compiledRegexes.forEach(({ api, regex }) => { try { @@ -411,7 +555,7 @@ let sync_metrics = { // Use a more memory-efficient counting approach let match; let matches = 0; - while ((match = regex.exec(req.response_body)) !== null) { + while ((match = regex.exec(body)) !== null) { matches++; // Prevent infinite loops on zero-length matches if (match.index === regex.lastIndex) { @@ -428,13 +572,11 @@ let sync_metrics = { } }); - // Track scripts with significant fingerprinting API usage (threshold: 4+ APIs or high occurrence count) - const suspicionScore = Math.round((detectedApis.length * 2 + totalOccurrences) / 3); - if (detectedApis.length >= 4 || suspicionScore >= 8) { + // Track scripts with significant fingerprinting API usage + if (detectedApis.length >= 5 ) { likelyFingerprintingScripts.push({ url: req.url, - detectedApis: detectedApis, - suspicionScore: suspicionScore + detectedApis }); } } catch (error) { @@ -442,9 +584,6 @@ let sync_metrics = { } }); - // Sort by suspicion score (highest first) - likelyFingerprintingScripts.sort((a, b) => b.suspicionScore - a.suspicionScore); - return likelyFingerprintingScripts; })(), From 33bb579b18f6484dde9f25630921dffd61c4b654 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:37:01 +0200 Subject: [PATCH 05/12] classify API --- dist/privacy.js | 266 ++++++++++++------------------------------------ 1 file changed, 64 insertions(+), 202 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 620035a..924f543 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -297,275 +297,137 @@ let sync_metrics = { })(), fingerprinting: (() => { - // These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs - const fingerprintingAPIs = [ + // The APIs are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs and https://amiunique.org/fingerprint + // Grouped by unique API to improve diversity metric reliability + const fingerprintingAPIs = { // Payment APIs - 'ApplePaySession\.canMakePayments', + 'payment': 'ApplePaySession\.canMakePayments', // User Agent and Platform fingerprinting - 'navigator\.userAgent', - 'navigator\.platform', - 'navigator\.oscpu', - 'navigator\.vendor', - 'navigator\.vendorSub', - 'navigator\.product', - 'navigator\.productSub', - 'navigator\.buildID', + 'navigator_userAgent': 'navigator\.userAgent', + 'navigator_platform': 'navigator\.platform', + 'navigator_oscpu': 'navigator\.oscpu', + 'navigator_vendor': 'navigator\.(vendor|vendorSub)', + 'navigator_product': 'navigator\.(product|productSub)', + 'navigator_buildID': 'navigator\.buildID', // Audio fingerprinting - 'createAnalyser', - 'createOscillator', - 'createScriptProcessor', - 'getChannelData', - 'getFloatFrequencyData', - 'getByteFrequencyData', - 'OscillatorNode', - 'AudioContext', - 'webkitAudioContext', - 'AnalyserNode', - 'createDynamicsCompressor', - 'channelCount', - 'channelCountMode', - 'channelInterpretation', - 'fftSize', - 'frequencyBinCount', - 'maxDecibels', - 'minDecibels', - 'smoothingTimeConstant', - 'sampleRate', + 'audio_context': '(AudioContext|webkitAudioContext)', + 'audio_analysis': '(createAnalyser|AnalyserNode|getFloatFrequencyData|getByteFrequencyData|fftSize|frequencyBinCount|maxDecibels|minDecibels|smoothingTimeConstant)', + 'audio_processing': '(createOscillator|OscillatorNode|createScriptProcessor|createDynamicsCompressor)', + 'audio_data': '(getChannelData|channelCount|channelCountMode|channelInterpretation|sampleRate)', // Canvas fingerprinting - 'canvas\.getContext', - 'canvas\.toDataURL', - 'canvasRenderingContext2D\.fillText', - 'canvasRenderingContext2D\.strokeText', - 'canvasRenderingContext2D\.getImageData', - 'HTMLCanvasElement\.toBlob', - 'getContext\(.*2d.*\)', - 'getContext\(.*webgl.*\)', + 'canvas_context': 'canvas\.getContext|getContext\(.*(2d|webgl).*\)', + 'canvas_rendering': '(canvasRenderingContext2D\.(fillText|strokeText|getImageData)|canvas\.toDataURL|HTMLCanvasElement\.toBlob)', // CSS media queries for fingerprinting - '@media.*color-gamut', - '@media.*prefers-contrast', - '@media.*forced-colors', - '@media.*dynamic-range', - '@media.*inverted-colors', - '@media.*min-monochrome', - '@media.*max-monochrome', - '@media.*prefers-reduced-motion', - '@media.*prefers-reduced-transparency', + 'css_media_queries': '@media.*(color-gamut|prefers-contrast|forced-colors|dynamic-range|inverted-colors|min-monochrome|max-monochrome|prefers-reduced-motion|prefers-reduced-transparency)', // Hardware fingerprinting - 'cpuClass', - 'deviceMemory', - 'hardwareConcurrency', - 'maxTouchPoints', - 'ontouchstart', + 'hardware_info': '(cpuClass|deviceMemory|hardwareConcurrency|maxTouchPoints)', + + // Touch capabilities + 'touch_capabilities': '(ontouchstart|TouchEvent|createTouch|createTouchList)', // Storage APIs (potential fingerprinting) - 'indexedDB', - 'localStorage', - 'sessionStorage', - 'openDatabase', + 'storage_apis': '(indexedDB|localStorage|sessionStorage|openDatabase)', // PDF and plugins - 'pdfViewerEnabled', - 'navigator\.plugins', - 'navigator\.mimeTypes', - 'Plugin\s', - 'MimeType', + 'plugins': '(pdfViewerEnabled|navigator\.(plugins|mimeTypes)|Plugin\s|MimeType)', // Attribution and tracking - 'attributionSourceId', + 'attribution': 'attributionSourceId', // Time zone and language fingerprinting - 'resolvedOptions\(\)\.timeZone', - 'getTimezoneOffset', - 'navigator\.language', - 'navigator\.languages', - 'Intl\.DateTimeFormat', - 'Intl\.Collator', + 'timezone': '(resolvedOptions\(\)\.timeZone|getTimezoneOffset)', + 'language': '(navigator\.(language|languages)|Intl\.(DateTimeFormat|Collator))', // WebGL fingerprinting - 'vendorUnmasked', - 'rendererUnmasked', - 'shadingLanguageVersion', - 'WEBGL_debug_renderer_info', - 'getShaderPrecisionFormat', - 'WebGLRenderingContext', - 'getParameter', - 'getSupportedExtensions', - 'getExtension', - 'VENDOR', - 'RENDERER', - 'VERSION', - 'SHADING_LANGUAGE_VERSION', + 'webgl_info': '(vendorUnmasked|rendererUnmasked|shadingLanguageVersion|WEBGL_debug_renderer_info|WebGLRenderingContext)', + 'webgl_params': '(getShaderPrecisionFormat|getParameter|getSupportedExtensions|getExtension|VENDOR|RENDERER|VERSION|SHADING_LANGUAGE_VERSION)', // Screen properties - 'availWidth', - 'availHeight', - 'screen\.width', - 'screen\.height', - 'screen\.colorDepth', - 'screen\.pixelDepth', - 'screen\.availTop', - 'screen\.availLeft', - 'outerWidth', - 'outerHeight', - 'innerWidth', - 'innerHeight', - 'devicePixelRatio', + 'screen_properties': '(availWidth|availHeight)|screen\.(width|height|colorDepth|pixelDepth|availTop|availLeft)|(outerWidth|outerHeight|innerWidth|innerHeight)|devicePixelRatio', // Window and browser chrome fingerprinting - 'locationbar', - 'menubar', - 'personalbar', - 'scrollbars', - 'statusbar', - 'toolbar', - 'history\.length', - - // Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API - 'getCurrentPosition', - 'watchPosition', - 'navigator\.geolocation', + 'browser_chrome': '(locationbar|menubar|personalbar|scrollbars|statusbar|toolbar|history\.length)', + + // Geolocation API + 'geolocation': '(getCurrentPosition|watchPosition|navigator\.geolocation)', // Media devices and capabilities - 'enumerateDevices', - 'getUserMedia', - 'getDisplayMedia', - 'navigator\.mediaDevices', - 'canPlayType', - 'HTMLVideoElement\.canPlayType', - 'HTMLAudioElement\.canPlayType', + 'media_devices': '(enumerateDevices|getUserMedia|getDisplayMedia|navigator\.mediaDevices)', + 'media_capabilities': '(canPlayType|HTMLVideoElement\.canPlayType|HTMLAudioElement\.canPlayType)', // Permissions API - 'navigator\.permissions', - 'permissions\.query', + 'permissions': '(navigator\.permissions|permissions\.query)', // Battery API - 'navigator\.battery', - 'navigator\.getBattery', - 'charging', - 'chargingTime', - 'dischargingTime', - // 'level', + 'battery': '(navigator\.(battery|getBattery)|charging|chargingTime|dischargingTime)', // Connection API - 'navigator\.connection', - 'navigator\.mozConnection', - 'navigator\.webkitConnection', - 'downlink', - 'effectiveType', - // 'rtt', - // 'saveData', + 'connection': '(navigator\.(connection|mozConnection|webkitConnection)|downlink|effectiveType)', // Sensors APIs - 'Accelerometer', - 'Gyroscope', - 'LinearAccelerationSensor', - 'AbsoluteOrientationSensor', - 'RelativeOrientationSensor', - 'AmbientLightSensor', - 'ProximitySensor', - - // Touch and input - 'TouchEvent', - 'createTouch', - 'createTouchList', + 'sensors': '(Accelerometer|Gyroscope|LinearAccelerationSensor|AbsoluteOrientationSensor|RelativeOrientationSensor|AmbientLightSensor|ProximitySensor)', // Font detection - 'document\.fonts', - 'FontFace', + 'fonts': '(document\.fonts|FontFace)', // Do Not Track - 'navigator\.doNotTrack', - 'window\.doNotTrack', + 'do_not_track': '(navigator\.doNotTrack|window\.doNotTrack)', // Cookie detection - 'navigator\.cookieEnabled', + 'cookies': 'navigator\.cookieEnabled', // Java detection - 'navigator\.javaEnabled', + 'java': 'navigator\.javaEnabled', + + // WebRTC + 'webrtc_peer': '(RTCPeerConnection|webkitRTCPeerConnection|mozRTCPeerConnection)', + 'webrtc_data': '(RTCDataChannel|createDataChannel)', - // Additional modern fingerprinting vectors - 'RTCPeerConnection', - 'webkitRTCPeerConnection', - 'mozRTCPeerConnection', - 'performance\.memory', - 'performance\.timing', - 'Notification\.permission', + // Performance APIs + 'performance': '(performance\.(memory|timing))', + + // Notifications + 'notifications': 'Notification\.permission', // Keyboard layout detection - 'KeyboardLayoutMap', - 'navigator\.keyboard', - 'getLayoutMap', + 'keyboard': '(KeyboardLayoutMap|navigator\.keyboard|getLayoutMap)', // Gamepad API - 'navigator\.getGamepads', - 'GamepadEvent', + 'gamepad': '(navigator\.getGamepads|GamepadEvent)', // Storage quota - 'navigator\.storage', - 'navigator\.webkitTemporaryStorage', - 'navigator\.webkitPersistentStorage', - 'estimate', + 'storage_quota': '(navigator\.(storage|webkitTemporaryStorage|webkitPersistentStorage)|estimate)', // Speech APIs - 'SpeechSynthesis', - 'SpeechRecognition', - - // WebRTC Data Channel - 'RTCDataChannel', - 'createDataChannel', + 'speech': '(SpeechSynthesis|SpeechRecognition)', // Crypto subtle fingerprinting - 'crypto\.subtle', - 'SubtleCrypto', + 'crypto': '(crypto\.subtle|SubtleCrypto)', // Worker capabilities - 'Worker', - 'SharedWorker', - 'ServiceWorker' - ]; + 'workers': '(Worker|SharedWorker|ServiceWorker)' + }; // Pre-compile regexes - handle already escaped patterns - const compiledRegexes = fingerprintingAPIs.map(api => ({ - api, - regex: new RegExp(api, 'gi') + const compiledRegexes = Object.entries(fingerprintingAPIs).map(([apiName, pattern]) => ({ + api: apiName, + regex: new RegExp(pattern, 'gi') })); let likelyFingerprintingScripts = []; response_bodies.forEach(req => { try { let detectedApis = []; - let totalOccurrences = 0; - let body = req.response_body; - - // Validate response body - if (!body || typeof body !== 'string') { - return; // Skip invalid response bodies - } compiledRegexes.forEach(({ api, regex }) => { try { - // Reset regex index for global regex - regex.lastIndex = 0; - - // Use a more memory-efficient counting approach - let match; - let matches = 0; - while ((match = regex.exec(body)) !== null) { - matches++; - // Prevent infinite loops on zero-length matches - if (match.index === regex.lastIndex) { - regex.lastIndex++; - } - } - - if (matches > 0) { + if (regex.test(req.response_body)) { detectedApis.push(api); - totalOccurrences += matches; } } catch (regexError) { // Skip this API on regex error - avoid console.warn in WebPageTest @@ -573,7 +435,7 @@ let sync_metrics = { }); // Track scripts with significant fingerprinting API usage - if (detectedApis.length >= 5 ) { + if (detectedApis.length >= 5) { likelyFingerprintingScripts.push({ url: req.url, detectedApis From ab5e32083c427f7789ff0515c9691639e9783344 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:18:39 +0200 Subject: [PATCH 06/12] test fingerprintjs --- dist/privacy.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 924f543..f6d9578 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -48,14 +48,6 @@ const fetchAndParse = async (url, parser) => { } }; -/** - * Checks if the response URL ends with any of the specified endings and if the response is OK. - * @param {Response} response - The fetch response object. - * @param {string[]} endings - An array of URL endings to check against. - * @returns {boolean} - Returns true if the response is OK and the URL ends with one of the specified endings. - */ -const isPresent = (response, endings) => response.ok && endings.some(ending => response.url.endsWith(ending)); - /** * Parses the response from a DSR delete request. * @param {Response} response - The response object from the fetch request. @@ -63,13 +55,13 @@ const isPresent = (response, endings) => response.ok && endings.some(ending => r */ const parseDSRdelete = async (response) => { let result = { - present: isPresent(response, ['/dsrdelete.json']), + present: response.ok && response.url.endsWith('/dsrdelete.json') && response.headers.get('content-type') === 'application/json', status: response.status, }; Object.assign(result, result.present ? { redirected: response.redirected } : {}); try { - let content = JSON.parse(await response.text()); + let content = JSON.parse(response.text()); if (result.present && content) { for (const element of content.identifiers) { delete element.id; From 4766f0d7369e4f4afbecae3f29859e7069cedb9a Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:51:50 +0200 Subject: [PATCH 07/12] cleanup --- dist/privacy.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index f6d9578..1890c87 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -529,10 +529,7 @@ let sync_metrics = { let CCPAdata = { hasCCPALink: CCPALinks.length > 0, - } - if (CCPAdata.hasCCPALink) { - CCPAdata.CCPALinkPhrases = CCPALinks.map(link => link.textContent.trim().toLowerCase()) - } + }; return CCPAdata })() From 55fbe5a477b69adfc649126e53e570a347543dc1 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:01:25 +0200 Subject: [PATCH 08/12] remove fingerprints --- dist/privacy.js | 153 ------------------------------------------------ 1 file changed, 153 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 1890c87..48724f1 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -288,159 +288,6 @@ let sync_metrics = { return rp; })(), - fingerprinting: (() => { - // The APIs are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs and https://amiunique.org/fingerprint - // Grouped by unique API to improve diversity metric reliability - const fingerprintingAPIs = { - // Payment APIs - 'payment': 'ApplePaySession\.canMakePayments', - - // User Agent and Platform fingerprinting - 'navigator_userAgent': 'navigator\.userAgent', - 'navigator_platform': 'navigator\.platform', - 'navigator_oscpu': 'navigator\.oscpu', - 'navigator_vendor': 'navigator\.(vendor|vendorSub)', - 'navigator_product': 'navigator\.(product|productSub)', - 'navigator_buildID': 'navigator\.buildID', - - // Audio fingerprinting - 'audio_context': '(AudioContext|webkitAudioContext)', - 'audio_analysis': '(createAnalyser|AnalyserNode|getFloatFrequencyData|getByteFrequencyData|fftSize|frequencyBinCount|maxDecibels|minDecibels|smoothingTimeConstant)', - 'audio_processing': '(createOscillator|OscillatorNode|createScriptProcessor|createDynamicsCompressor)', - 'audio_data': '(getChannelData|channelCount|channelCountMode|channelInterpretation|sampleRate)', - - // Canvas fingerprinting - 'canvas_context': 'canvas\.getContext|getContext\(.*(2d|webgl).*\)', - 'canvas_rendering': '(canvasRenderingContext2D\.(fillText|strokeText|getImageData)|canvas\.toDataURL|HTMLCanvasElement\.toBlob)', - - // CSS media queries for fingerprinting - 'css_media_queries': '@media.*(color-gamut|prefers-contrast|forced-colors|dynamic-range|inverted-colors|min-monochrome|max-monochrome|prefers-reduced-motion|prefers-reduced-transparency)', - - // Hardware fingerprinting - 'hardware_info': '(cpuClass|deviceMemory|hardwareConcurrency|maxTouchPoints)', - - // Touch capabilities - 'touch_capabilities': '(ontouchstart|TouchEvent|createTouch|createTouchList)', - - // Storage APIs (potential fingerprinting) - 'storage_apis': '(indexedDB|localStorage|sessionStorage|openDatabase)', - - // PDF and plugins - 'plugins': '(pdfViewerEnabled|navigator\.(plugins|mimeTypes)|Plugin\s|MimeType)', - - // Attribution and tracking - 'attribution': 'attributionSourceId', - - // Time zone and language fingerprinting - 'timezone': '(resolvedOptions\(\)\.timeZone|getTimezoneOffset)', - 'language': '(navigator\.(language|languages)|Intl\.(DateTimeFormat|Collator))', - - // WebGL fingerprinting - 'webgl_info': '(vendorUnmasked|rendererUnmasked|shadingLanguageVersion|WEBGL_debug_renderer_info|WebGLRenderingContext)', - 'webgl_params': '(getShaderPrecisionFormat|getParameter|getSupportedExtensions|getExtension|VENDOR|RENDERER|VERSION|SHADING_LANGUAGE_VERSION)', - - // Screen properties - 'screen_properties': '(availWidth|availHeight)|screen\.(width|height|colorDepth|pixelDepth|availTop|availLeft)|(outerWidth|outerHeight|innerWidth|innerHeight)|devicePixelRatio', - - // Window and browser chrome fingerprinting - 'browser_chrome': '(locationbar|menubar|personalbar|scrollbars|statusbar|toolbar|history\.length)', - - // Geolocation API - 'geolocation': '(getCurrentPosition|watchPosition|navigator\.geolocation)', - - // Media devices and capabilities - 'media_devices': '(enumerateDevices|getUserMedia|getDisplayMedia|navigator\.mediaDevices)', - 'media_capabilities': '(canPlayType|HTMLVideoElement\.canPlayType|HTMLAudioElement\.canPlayType)', - - // Permissions API - 'permissions': '(navigator\.permissions|permissions\.query)', - - // Battery API - 'battery': '(navigator\.(battery|getBattery)|charging|chargingTime|dischargingTime)', - - // Connection API - 'connection': '(navigator\.(connection|mozConnection|webkitConnection)|downlink|effectiveType)', - - // Sensors APIs - 'sensors': '(Accelerometer|Gyroscope|LinearAccelerationSensor|AbsoluteOrientationSensor|RelativeOrientationSensor|AmbientLightSensor|ProximitySensor)', - - // Font detection - 'fonts': '(document\.fonts|FontFace)', - - // Do Not Track - 'do_not_track': '(navigator\.doNotTrack|window\.doNotTrack)', - - // Cookie detection - 'cookies': 'navigator\.cookieEnabled', - - // Java detection - 'java': 'navigator\.javaEnabled', - - // WebRTC - 'webrtc_peer': '(RTCPeerConnection|webkitRTCPeerConnection|mozRTCPeerConnection)', - 'webrtc_data': '(RTCDataChannel|createDataChannel)', - - // Performance APIs - 'performance': '(performance\.(memory|timing))', - - // Notifications - 'notifications': 'Notification\.permission', - - // Keyboard layout detection - 'keyboard': '(KeyboardLayoutMap|navigator\.keyboard|getLayoutMap)', - - // Gamepad API - 'gamepad': '(navigator\.getGamepads|GamepadEvent)', - - // Storage quota - 'storage_quota': '(navigator\.(storage|webkitTemporaryStorage|webkitPersistentStorage)|estimate)', - - // Speech APIs - 'speech': '(SpeechSynthesis|SpeechRecognition)', - - // Crypto subtle fingerprinting - 'crypto': '(crypto\.subtle|SubtleCrypto)', - - // Worker capabilities - 'workers': '(Worker|SharedWorker|ServiceWorker)' - }; - - // Pre-compile regexes - handle already escaped patterns - const compiledRegexes = Object.entries(fingerprintingAPIs).map(([apiName, pattern]) => ({ - api: apiName, - regex: new RegExp(pattern, 'gi') - })); - let likelyFingerprintingScripts = []; - - response_bodies.forEach(req => { - try { - let detectedApis = []; - - compiledRegexes.forEach(({ api, regex }) => { - try { - if (regex.test(req.response_body)) { - detectedApis.push(api); - } - } catch (regexError) { - // Skip this API on regex error - avoid console.warn in WebPageTest - } - }); - - // Track scripts with significant fingerprinting API usage - if (detectedApis.length >= 5) { - likelyFingerprintingScripts.push({ - url: req.url, - detectedApis - }); - } - } catch (error) { - // Skip this request on error - avoid console.warn in WebPageTest - } - }); - - return likelyFingerprintingScripts; - })(), - /** * List of hostnames with CNAME record */ From 818a8e74b6d5e744e4858d399a5ac0366b9db6a4 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:39:50 +0200 Subject: [PATCH 09/12] sync --- dist/privacy.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 48724f1..2fb1dac 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -30,14 +30,14 @@ function testPropertyStringInResponseBodies(pattern) { * @param {function} parser - The function to parse the response. * @returns {Promise} The parsed response or an error object. */ -const fetchAndParse = async (url, parser) => { +const fetchAndParse = (url, parser) => { const timeout = 5000; const controller = new AbortController(); const { signal } = controller; setTimeout(() => controller.abort(), timeout); try { - const response = await fetch(url, { signal }); + const response = fetch(url, { signal }); return parser(response); } catch (error) { return { @@ -53,7 +53,7 @@ const fetchAndParse = async (url, parser) => { * @param {Response} response - The response object from the fetch request. * @returns {Promise} A promise that resolves to an object containing the parsed response data. */ -const parseDSRdelete = async (response) => { +const parseDSRdelete = (response) => { let result = { present: response.ok && response.url.endsWith('/dsrdelete.json') && response.headers.get('content-type') === 'application/json', status: response.status, @@ -388,7 +388,7 @@ let sync_metrics = { * IAB: Data Deletion Request Framework * https://github.com/InteractiveAdvertisingBureau/Data-Subject-Rights/blob/main/Data%20Deletion%20Request%20Framework.md */ -let iab_ddr = fetchAndParse("/dsrdelete.json", parseDSRdelete); +let iab_ddr = fetchAndParse("/.well-known/dsrdelete.json", parseDSRdelete); return Promise.all([iab_ddr]).then(([iab_ddr]) => { return JSON.stringify({ From 5f0f4f2c2adf0646334bbc1d35e809818275ce1a Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Mon, 7 Jul 2025 00:02:26 +0200 Subject: [PATCH 10/12] async fetch --- dist/privacy.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 2fb1dac..14842d8 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -30,15 +30,15 @@ function testPropertyStringInResponseBodies(pattern) { * @param {function} parser - The function to parse the response. * @returns {Promise} The parsed response or an error object. */ -const fetchAndParse = (url, parser) => { +const fetchAndParse = async (url, parser) => { const timeout = 5000; const controller = new AbortController(); const { signal } = controller; setTimeout(() => controller.abort(), timeout); try { - const response = fetch(url, { signal }); - return parser(response); + const response = await fetch(url, { signal }); + return parser(url, await response); } catch (error) { return { status: -1, @@ -53,9 +53,9 @@ const fetchAndParse = (url, parser) => { * @param {Response} response - The response object from the fetch request. * @returns {Promise} A promise that resolves to an object containing the parsed response data. */ -const parseDSRdelete = (response) => { +const parseDSRdelete = (url, response) => { let result = { - present: response.ok && response.url.endsWith('/dsrdelete.json') && response.headers.get('content-type') === 'application/json', + present: response.ok && response.url.endsWith(url) && response.headers.get('content-type') === 'application/json', status: response.status, }; Object.assign(result, result.present ? { redirected: response.redirected } : {}); @@ -75,7 +75,7 @@ const parseDSRdelete = (response) => { Object.assign(result, result.present ? { error: error.message } : {}); } - return Promise.resolve(result); + return result; } let sync_metrics = { @@ -388,7 +388,7 @@ let sync_metrics = { * IAB: Data Deletion Request Framework * https://github.com/InteractiveAdvertisingBureau/Data-Subject-Rights/blob/main/Data%20Deletion%20Request%20Framework.md */ -let iab_ddr = fetchAndParse("/.well-known/dsrdelete.json", parseDSRdelete); +let iab_ddr = fetchAndParse("/dsrdelete.json", parseDSRdelete); return Promise.all([iab_ddr]).then(([iab_ddr]) => { return JSON.stringify({ From a380982ec73e0f6a60353053a5ece6659bad049c Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Mon, 7 Jul 2025 00:13:08 +0200 Subject: [PATCH 11/12] no promise --- dist/privacy.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 14842d8..613f186 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -388,11 +388,9 @@ let sync_metrics = { * IAB: Data Deletion Request Framework * https://github.com/InteractiveAdvertisingBureau/Data-Subject-Rights/blob/main/Data%20Deletion%20Request%20Framework.md */ -let iab_ddr = fetchAndParse("/dsrdelete.json", parseDSRdelete); +let iab_ddr = await fetchAndParse("/dsrdelete.json", parseDSRdelete); -return Promise.all([iab_ddr]).then(([iab_ddr]) => { - return JSON.stringify({ - ...sync_metrics, - ...{ iab_ddr: iab_ddr } - }); +return JSON.stringify({ + ...sync_metrics, + ...{ iab_ddr: iab_ddr } }); From bbf4db690739e24916824d718ce770a89adc90c1 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:29:41 +0200 Subject: [PATCH 12/12] fix --- dist/privacy.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dist/privacy.js b/dist/privacy.js index 613f186..14842d8 100644 --- a/dist/privacy.js +++ b/dist/privacy.js @@ -388,9 +388,11 @@ let sync_metrics = { * IAB: Data Deletion Request Framework * https://github.com/InteractiveAdvertisingBureau/Data-Subject-Rights/blob/main/Data%20Deletion%20Request%20Framework.md */ -let iab_ddr = await fetchAndParse("/dsrdelete.json", parseDSRdelete); +let iab_ddr = fetchAndParse("/dsrdelete.json", parseDSRdelete); -return JSON.stringify({ - ...sync_metrics, - ...{ iab_ddr: iab_ddr } +return Promise.all([iab_ddr]).then(([iab_ddr]) => { + return JSON.stringify({ + ...sync_metrics, + ...{ iab_ddr: iab_ddr } + }); });