From 06a050ca77ee35a2810eddd849732f12eb348e23 Mon Sep 17 00:00:00 2001 From: Tine Date: Wed, 20 May 2026 22:12:59 +0900 Subject: [PATCH] Guard generated viewport dimensions --- .../src/fingerprint-generator.ts | 54 +++++++++++++++ test/fingerprint-generator/generation.test.ts | 67 +++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/packages/fingerprint-generator/src/fingerprint-generator.ts b/packages/fingerprint-generator/src/fingerprint-generator.ts index c567e271..40f8dd9a 100644 --- a/packages/fingerprint-generator/src/fingerprint-generator.ts +++ b/packages/fingerprint-generator/src/fingerprint-generator.ts @@ -244,6 +244,13 @@ export class FingerprintGenerator extends HeaderGenerator { if (!fingerprint.screen) continue; // fix? sometimes, fingerprints are generated 90% empty/null. This is just a workaround. + fingerprint.screen = this.normalizeScreenFingerprint( + fingerprint.screen, + ); + + if (!this.isPlausibleScreenFingerprint(fingerprint.screen)) + continue; // fix? sometimes, fingerprints are generated 90% empty/null. This is just a workaround. + // Manually add the set of accepted languages required by the input const acceptLanguageHeaderValue = 'Accept-Language' in headers @@ -352,4 +359,51 @@ export class FingerprintGenerator extends HeaderGenerator { fonts, } as Fingerprint; } + + private normalizeScreenFingerprint( + screen: ScreenFingerprint, + ): ScreenFingerprint { + const outerWidth = screen.outerWidth || screen.width; + const outerHeight = screen.outerHeight || screen.height; + const innerWidth = + screen.innerWidth || Math.min(screen.width, outerWidth); + const innerHeight = + screen.innerHeight || Math.min(screen.availHeight, outerHeight); + + return { + ...screen, + outerWidth, + outerHeight, + innerWidth, + innerHeight, + clientWidth: screen.clientWidth || innerWidth, + clientHeight: screen.clientHeight || innerHeight, + }; + } + + private isPlausibleScreenFingerprint(screen: Partial) { + const positiveViewportFields = [ + screen.clientWidth, + screen.clientHeight, + screen.innerWidth, + screen.innerHeight, + screen.outerWidth, + screen.outerHeight, + ]; + + if ( + positiveViewportFields.some( + (value) => typeof value !== 'number' || value <= 0, + ) + ) { + return false; + } + + return ( + screen.clientWidth! <= screen.innerWidth! && + screen.clientHeight! <= screen.innerHeight! && + screen.innerWidth! <= screen.outerWidth! && + screen.innerHeight! <= screen.outerHeight! + ); + } } diff --git a/test/fingerprint-generator/generation.test.ts b/test/fingerprint-generator/generation.test.ts index 158f54ad..4ad0b341 100644 --- a/test/fingerprint-generator/generation.test.ts +++ b/test/fingerprint-generator/generation.test.ts @@ -84,6 +84,73 @@ describe('Generation tests', () => { expect(field).toBeDefined(); } }); + + test('rejects generated fingerprints with impossible viewport dimensions', () => { + const { + fingerprint: { screen }, + } = fingerprintGenerator.getFingerprint({ + browsers: [{ name: 'chrome', minVersion: 100, maxVersion: 100 }], + devices: ['desktop'], + operatingSystems: ['macos'], + }); + const isPlausibleScreenFingerprint = ( + fingerprintGenerator as any + ).isPlausibleScreenFingerprint.bind(fingerprintGenerator); + + expect(isPlausibleScreenFingerprint(screen)).toBe(true); + expect( + isPlausibleScreenFingerprint({ + ...screen, + clientWidth: 0, + }), + ).toBe(false); + expect( + isPlausibleScreenFingerprint({ + ...screen, + innerHeight: 0, + }), + ).toBe(false); + expect( + isPlausibleScreenFingerprint({ + ...screen, + clientWidth: screen.innerWidth + 1, + }), + ).toBe(false); + expect( + isPlausibleScreenFingerprint({ + ...screen, + innerHeight: screen.outerHeight + 1, + }), + ).toBe(false); + }); + + test('normalizes zero viewport dimensions from generated screen data', () => { + const { + fingerprint: { screen }, + } = fingerprintGenerator.getFingerprint({ + browsers: [{ name: 'chrome', minVersion: 100, maxVersion: 100 }], + devices: ['desktop'], + operatingSystems: ['macos'], + }); + const normalizeScreenFingerprint = ( + fingerprintGenerator as any + ).normalizeScreenFingerprint.bind(fingerprintGenerator); + + const normalizedScreen = normalizeScreenFingerprint({ + ...screen, + clientWidth: 0, + clientHeight: 0, + innerWidth: 0, + innerHeight: 0, + }); + + expect(normalizedScreen.innerWidth).toBeGreaterThan(0); + expect(normalizedScreen.innerHeight).toBeGreaterThan(0); + expect(normalizedScreen.clientWidth).toBe(normalizedScreen.innerWidth); + expect(normalizedScreen.clientHeight).toBe( + normalizedScreen.innerHeight, + ); + }); }); describe('Generate fingerprints with basic constraints', () => {