Skip to content

Commit 0a8ed23

Browse files
committed
generalized fingerprints
1 parent 381fa95 commit 0a8ed23

File tree

1 file changed

+127
-83
lines changed

1 file changed

+127
-83
lines changed

dist/privacy.js

Lines changed: 127 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -296,112 +296,156 @@ let sync_metrics = {
296296
return rp;
297297
})(),
298298

299-
/**
300-
* Media devices
301-
* https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
302-
*/
303-
media_devices: {
304-
navigator_mediaDevices_enumerateDevices: testPropertyStringInResponseBodies(
305-
'mediaDevices.+enumerateDevices'
306-
),
307-
navigator_mediaDevices_getUserMedia: testPropertyStringInResponseBodies(
308-
'mediaDevices.+getUserMedia'
309-
),
310-
navigator_mediaDevices_getDisplayMedia: testPropertyStringInResponseBodies(
311-
'mediaDevices.+getDisplayMedia'
312-
),
313-
},
314-
315-
/**
316-
* Geolocation API
317-
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
318-
*/
319-
geolocation: {
320-
navigator_geolocation_getCurrentPosition: testPropertyStringInResponseBodies(
321-
'geolocation.+getCurrentPosition'
322-
),
323-
navigator_geolocation_watchPosition: testPropertyStringInResponseBodies(
324-
'geolocation.+watchPosition'
325-
),
326-
},
327-
328299
fingerprinting: (() => {
329-
//These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs
300+
// These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs
330301
const fingerprintingAPIs = [
331-
'ApplePaySession.canMakePayments',
332-
'getChannelData', //audioContext
333-
'toDataURL', //canvas
334-
'getImageData', //canvas, not actually used by fingerprintJS
335-
'screen.colorDepth',
336-
'color-gamut',
337-
'prefers-contrast',
302+
// Payment APIs
303+
'ApplePaySession\\.canMakePayments',
304+
305+
// Audio fingerprinting
306+
'createAnalyser',
307+
'createOscillator',
308+
'createScriptProcessor',
309+
'getChannelData',
310+
'getFloatFrequencyData',
311+
'getByteFrequencyData',
312+
'OscillatorNode',
313+
314+
// Canvas fingerprinting
315+
'canvas\\.getContext',
316+
'canvas\\.toDataURL',
317+
'canvasRenderingContext2D\\.fillText',
318+
'canvasRenderingContext2D\\.strokeText',
319+
'canvasRenderingContext2D\\.getImageData',
320+
'HTMLCanvasElement\\.toBlob',
321+
322+
// CSS media queries for fingerprinting
323+
'@media.*color-gamut',
324+
'@media.*prefers-contrast',
325+
'@media.*forced-colors',
326+
'@media.*dynamic-range',
327+
'@media.*inverted-colors',
328+
'@media.*min-monochrome',
329+
'@media.*max-monochrome',
330+
'@media.*prefers-reduced-motion',
331+
'@media.*prefers-reduced-transparency',
332+
333+
// Hardware fingerprinting
338334
'cpuClass',
339335
'deviceMemory',
340-
'forced-colors',
341336
'hardwareConcurrency',
342-
'dynamic-range',
337+
'maxTouchPoints',
338+
'ontouchstart',
339+
340+
// Storage APIs (potential fingerprinting)
343341
'indexedDB',
344-
'inverted-colors',
345-
'navigator.language', //"language" would be too generic here
346-
'navigator.userLanguage', //TODO exists?
347342
'localStorage',
348-
'min-monochrome',
349-
'max-monochrome',
343+
'sessionStorage',
350344
'openDatabase',
351-
'navigator.oscpu',
345+
346+
// PDF and plugins
352347
'pdfViewerEnabled',
353-
'navigator.platform', //"platform" would be too generic
354-
'navigator.plugins',
348+
349+
// Attribution and tracking
355350
'attributionSourceId',
356-
'prefers-reduced-motion',
357-
'prefers-reduced-transparency',
358-
'availWidth',
359-
'availHeight',
360-
'screen.width',
361-
'screen.height',
362-
'sessionStorage',
363-
'resolvedOptions().timeZone',
351+
352+
// Time zone fingerprinting
353+
'resolvedOptions\\(\\)\\.timeZone',
364354
'getTimezoneOffset',
365-
'maxTouchPoints',
366-
'ontouchstart',
367-
'navigator.vendor',
355+
356+
// WebGL fingerprinting
368357
'vendorUnmasked',
369358
'rendererUnmasked',
370359
'shadingLanguageVersion',
371360
'WEBGL_debug_renderer_info',
372-
'getShaderPrecisionFormat'
373-
].map(api => api.toLowerCase())
361+
'getShaderPrecisionFormat',
374362

375-
const response_bodies = $WPT_BODIES.filter(body => (body.response_body && (body.type === 'Document' || body.type === 'Script')))
376-
377-
let fingerprintingUsageCounts = {}
378-
let likelyFingerprintingScripts = []
363+
// Screen properties
364+
'availWidth',
365+
'availHeight',
366+
'screen\\.width',
367+
'screen\\.height',
368+
'screen\\.colorDepth',
369+
370+
// Navigator properties
371+
'navigator\\.platform',
372+
'navigator\\.plugins',
373+
'navigator\\.language',
374+
'navigator\\.oscpu',
375+
'navigator\\.vendor',
376+
'navigator\\.getBattery',
377+
'navigator\\.getGamepads',
378+
379+
// Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
380+
'getCurrentPosition',
381+
'watchPosition',
382+
383+
// Media devices: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
384+
'enumerateDevices',
385+
'getUserMedia',
386+
'getDisplayMedia',
387+
388+
// Additional modern fingerprinting vectors
389+
'RTCPeerConnection',
390+
'document\\.fonts',
391+
'performance\\.memory',
392+
];
393+
394+
// Pre-compile regexes - handle already escaped patterns
395+
const compiledRegexes = fingerprintingAPIs.map(api => ({
396+
api,
397+
regex: new RegExp(api, 'gi')
398+
}));
399+
let likelyFingerprintingScripts = [];
379400

380401
response_bodies.forEach(req => {
381-
let total_occurrences = 0
382-
383-
let body = req.response_body.toLowerCase()
402+
try {
403+
let detectedApis = [];
404+
let totalOccurrences = 0;
405+
406+
compiledRegexes.forEach(({ api, regex }) => {
407+
try {
408+
// Reset regex index for global regex
409+
regex.lastIndex = 0;
410+
411+
// Use a more memory-efficient counting approach
412+
let match;
413+
let matches = 0;
414+
while ((match = regex.exec(req.response_body)) !== null) {
415+
matches++;
416+
// Prevent infinite loops on zero-length matches
417+
if (match.index === regex.lastIndex) {
418+
regex.lastIndex++;
419+
}
420+
}
384421

385-
fingerprintingAPIs.forEach(api => {
386-
let api_occurrences = 0
387-
let index = body.indexOf(api)
388-
while (index !== -1) {
389-
api_occurrences++
390-
index = body.indexOf(api, index + 1)
391-
}
422+
if (matches > 0) {
423+
detectedApis.push(api);
424+
totalOccurrences += matches;
425+
}
426+
} catch (regexError) {
427+
// Skip this API on regex error - avoid console.warn in WebPageTest
428+
}
429+
});
392430

393-
if (api_occurrences > 0) {
394-
fingerprintingUsageCounts[api] = (fingerprintingUsageCounts[api] || 0) + api_occurrences
431+
// Track scripts with significant fingerprinting API usage (threshold: 4+ APIs or high occurrence count)
432+
const suspicionScore = Math.round((detectedApis.length * 2 + totalOccurrences) / 3);
433+
if (detectedApis.length >= 4 || suspicionScore >= 8) {
434+
likelyFingerprintingScripts.push({
435+
url: req.url,
436+
detectedApis: detectedApis,
437+
suspicionScore: suspicionScore
438+
});
395439
}
396-
total_occurrences += api_occurrences
397-
})
398-
399-
if (total_occurrences >= 5) { //TODO what should this threshold be?
400-
likelyFingerprintingScripts.push(req.url)
440+
} catch (error) {
441+
// Skip this request on error - avoid console.warn in WebPageTest
401442
}
402-
})
443+
});
444+
445+
// Sort by suspicion score (highest first)
446+
likelyFingerprintingScripts.sort((a, b) => b.suspicionScore - a.suspicionScore);
403447

404-
return { counts: fingerprintingUsageCounts, likelyFingerprintingScripts }
448+
return likelyFingerprintingScripts;
405449
})(),
406450

407451
/**

0 commit comments

Comments
 (0)