feat(daemon): add stealth navigator property injection (Task 11)
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
// Integration tests for injectNavigatorStealth / 隐身导航注入集成测试
|
||||||
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { chromium } from 'playwright-extra';
|
||||||
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||||
|
import type { Browser, BrowserContext } from 'playwright';
|
||||||
|
import type { FingerprintProfile } from '@visionl/core';
|
||||||
|
import { injectNavigatorStealth } from '../stealth/navigator.js';
|
||||||
|
|
||||||
|
chromium.use(StealthPlugin());
|
||||||
|
|
||||||
|
const integration = process.env.VISIONL_INTEGRATION === '1' ? describe : describe.skip;
|
||||||
|
|
||||||
|
const testProfile: FingerprintProfile = {
|
||||||
|
id: 'fp_stealth_nav',
|
||||||
|
name: 'Stealth Navigator Test',
|
||||||
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
platform: 'Win32',
|
||||||
|
languages: ['en-US', 'en'],
|
||||||
|
acceptLanguage: 'en-US,en;q=0.9',
|
||||||
|
screen: { width: 1920, height: 1080, colorDepth: 24, pixelRatio: 1 },
|
||||||
|
viewport: { width: 1280, height: 720 },
|
||||||
|
webgl: { vendor: 'Google Inc.', renderer: 'ANGLE (NVIDIA GeForce RTX 3060)' },
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
permissions: {
|
||||||
|
notifications: 'denied',
|
||||||
|
geolocation: 'granted',
|
||||||
|
camera: 'denied',
|
||||||
|
microphone: 'denied',
|
||||||
|
},
|
||||||
|
behavior: {
|
||||||
|
mouseMoveDelay: { min: 50, max: 150 },
|
||||||
|
keyPressDelay: { min: 80, max: 200 },
|
||||||
|
scrollStepDelay: { min: 30, max: 100 },
|
||||||
|
},
|
||||||
|
canvasNoise: { enabled: true, strength: 0.5 },
|
||||||
|
};
|
||||||
|
|
||||||
|
integration('injectNavigatorStealth', () => {
|
||||||
|
let browser: Browser;
|
||||||
|
let context: BrowserContext;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
browser = await chromium.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (context) await context.close();
|
||||||
|
if (browser) await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set navigator.webdriver to false', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const webdriver = await page.evaluate(() => navigator.webdriver);
|
||||||
|
expect(webdriver).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override navigator.languages from profile', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const langs = await page.evaluate(() => navigator.languages);
|
||||||
|
expect(langs).toEqual(testProfile.languages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override navigator.platform from profile', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const platform = await page.evaluate(() => navigator.platform);
|
||||||
|
expect(platform).toBe(testProfile.platform);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set navigator.vendor to "Google Inc."', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const vendor = await page.evaluate(() => navigator.vendor);
|
||||||
|
expect(vendor).toBe('Google Inc.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set navigator.productSub to "20030107"', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const productSub = await page.evaluate(() => navigator.productSub);
|
||||||
|
expect(productSub).toBe('20030107');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject navigator.connection if missing', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const connection = await page.evaluate(() => {
|
||||||
|
const c = (navigator as any).connection;
|
||||||
|
if (!c) return null;
|
||||||
|
return { downlink: c.downlink, effectiveType: c.effectiveType, rtt: c.rtt, saveData: c.saveData };
|
||||||
|
});
|
||||||
|
expect(connection).not.toBeNull();
|
||||||
|
expect(connection!.downlink).toBe(10);
|
||||||
|
expect(connection!.effectiveType).toBe('4g');
|
||||||
|
expect(connection!.rtt).toBe(50);
|
||||||
|
expect(connection!.saveData).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep navigator.plugins accessible', async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
await injectNavigatorStealth(context, testProfile);
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
|
||||||
|
const plugins = await page.evaluate(() => {
|
||||||
|
const p = navigator.plugins;
|
||||||
|
return { length: p.length, hasItem: typeof p.item === 'function', hasNamedItem: typeof p.namedItem === 'function' };
|
||||||
|
});
|
||||||
|
expect(typeof plugins.length).toBe('number');
|
||||||
|
expect(plugins.hasItem).toBe(true);
|
||||||
|
expect(plugins.hasNamedItem).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Stealth navigator property overrides / 隐身导航属性覆盖
|
||||||
|
import type { BrowserContext } from 'playwright';
|
||||||
|
import type { FingerprintProfile } from '@visionl/core';
|
||||||
|
|
||||||
|
export async function injectNavigatorStealth(
|
||||||
|
context: BrowserContext,
|
||||||
|
profile: FingerprintProfile
|
||||||
|
): Promise<void> {
|
||||||
|
await context.addInitScript((opts) => {
|
||||||
|
Object.defineProperty(navigator, 'webdriver', {
|
||||||
|
get: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (navigator.plugins.length === 0) {
|
||||||
|
Object.defineProperty(navigator, 'plugins', {
|
||||||
|
get: () => {
|
||||||
|
const arr = Object.create(PluginArray.prototype);
|
||||||
|
arr.length = 0;
|
||||||
|
arr.item = () => null;
|
||||||
|
arr.namedItem = () => null;
|
||||||
|
arr.refresh = () => {};
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(navigator, 'languages', {
|
||||||
|
get: () => opts.languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(navigator, 'platform', {
|
||||||
|
get: () => opts.platform,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!('connection' in navigator)) {
|
||||||
|
Object.defineProperty(navigator, 'connection', {
|
||||||
|
get: () => ({
|
||||||
|
downlink: 10,
|
||||||
|
effectiveType: '4g',
|
||||||
|
rtt: 50,
|
||||||
|
saveData: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.' });
|
||||||
|
Object.defineProperty(navigator, 'productSub', { get: () => '20030107' });
|
||||||
|
}, {
|
||||||
|
languages: profile.languages,
|
||||||
|
platform: profile.platform,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user