@zorilla/puppeteer-extra-plugin-stealth 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +4 -4
- package/dist/evasions/defaultArgs/README.md +1 -1
- package/dist/evasions/media.codecs/README.md +2 -2
- package/dist/evasions/media.codecs/index.js +1 -1
- package/dist/evasions/media.codecs/index.js.map +1 -1
- package/dist/evasions/media.codecs/index.ts +1 -1
- package/dist/evasions/sourceurl/index.js +1 -1
- package/dist/evasions/sourceurl/index.js.map +1 -1
- package/dist/evasions/sourceurl/index.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/evasions/defaultArgs/README.md +1 -1
- package/src/evasions/media.codecs/README.md +2 -2
- package/src/evasions/media.codecs/index.ts +1 -1
- package/src/evasions/sourceurl/index.ts +1 -1
- package/src/index.ts +1 -1
- package/test/ambient.d.ts +85 -0
- package/test/cat-and-mouse.test.ts +21 -20
- package/test/evasions/_utils/index.test.ts +1 -1
- package/test/evasions/chrome.app/index.test.ts +36 -17
- package/test/evasions/chrome.csi/index.test.ts +10 -7
- package/test/evasions/chrome.loadTimes/index.test.ts +9 -7
- package/test/evasions/chrome.runtime/index.test.ts +1 -1
- package/test/evasions/defaultArgs/index.test.ts +9 -9
- package/test/evasions/iframe.contentWindow/{index.test.js → index.test.ts} +2 -1
- package/test/evasions/media.codecs/{index.test.js → index.test.ts} +1 -1
- package/test/evasions/navigator.hardwareConcurrency/{index.test.js → index.test.ts} +1 -1
- package/test/evasions/navigator.languages/{index.test.js → index.test.ts} +1 -1
- package/test/evasions/navigator.permissions/{index.test.js → index.test.ts} +1 -1
- package/test/evasions/navigator.plugins/{index.test.js → index.test.ts} +14 -3
- package/test/evasions/navigator.plugins/{mimeTypes.test.js → mimeTypes.test.ts} +2 -6
- package/test/evasions/navigator.plugins/{plugins.test.js → plugins.test.ts} +1 -5
- package/test/evasions/navigator.vendor/{index.test.js → index.test.ts} +1 -5
- package/test/evasions/navigator.webdriver/{index.test.js → index.test.ts} +1 -1
- package/test/evasions/sourceurl/{index.test.js → index.test.ts} +1 -5
- package/test/evasions/user-agent-override/{index.test.js → index.test.ts} +1 -5
- package/test/evasions/webgl.vendor/{index.test.js → index.test.ts} +9 -4
- package/test/fpscanner.test.ts +114 -45
- package/test/index.test.ts +13 -5
- package/test/service-worker.test.ts +40 -19
- package/test/{util.js → util.ts} +45 -15
- package/tsconfig.json +1 -1
- package/tsconfig.test.json +23 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -1
|
@@ -2,11 +2,11 @@ import { expect, test } from 'vitest';
|
|
|
2
2
|
import Plugin, {
|
|
3
3
|
argsToIgnore,
|
|
4
4
|
} from '../../../src/evasions/defaultArgs/index.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
5
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
6
|
+
|
|
7
|
+
type BrowserCommandLineResponse = {
|
|
8
|
+
arguments: string[];
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
test('vanilla: uses args to ignore', async () => {
|
|
12
12
|
const browser = await vanillaPuppeteer.launch({
|
|
@@ -16,9 +16,9 @@ test('vanilla: uses args to ignore', async () => {
|
|
|
16
16
|
const page = await browser.newPage();
|
|
17
17
|
const client =
|
|
18
18
|
typeof page._client === 'function' ? page._client() : page._client;
|
|
19
|
-
const { arguments: launchArgs } = await client.send(
|
|
19
|
+
const { arguments: launchArgs } = (await client.send(
|
|
20
20
|
'Browser.getBrowserCommandLine'
|
|
21
|
-
);
|
|
21
|
+
)) as BrowserCommandLineResponse;
|
|
22
22
|
const ok = argsToIgnore.every(arg => launchArgs.includes(arg));
|
|
23
23
|
if (!ok) {
|
|
24
24
|
console.log({ argsToIgnore, launchArgs });
|
|
@@ -35,9 +35,9 @@ test('stealth: does not use args to ignore', async () => {
|
|
|
35
35
|
const page = await browser.newPage();
|
|
36
36
|
const client =
|
|
37
37
|
typeof page._client === 'function' ? page._client() : page._client;
|
|
38
|
-
const { arguments: launchArgs } = await client.send(
|
|
38
|
+
const { arguments: launchArgs } = (await client.send(
|
|
39
39
|
'Browser.getBrowserCommandLine'
|
|
40
|
-
);
|
|
40
|
+
)) as BrowserCommandLineResponse;
|
|
41
41
|
const ok = argsToIgnore.every(arg => !launchArgs.includes(arg));
|
|
42
42
|
if (!ok) {
|
|
43
43
|
console.log({ argsToIgnore, launchArgs });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
// import Plugin from '../../../src/evasions/iframe.contentWindow/index.js'
|
|
2
3
|
// NOTE: We're using the full plugin for testing here as `iframe.contentWindow` uses data set by `chrome.runtime`
|
|
3
4
|
import Plugin from '@zorilla/puppeteer-extra-plugin-stealth';
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
getStealthFingerPrint,
|
|
10
11
|
getVanillaFingerPrint,
|
|
11
12
|
vanillaPuppeteer,
|
|
12
|
-
} from '../../util
|
|
13
|
+
} from '../../util';
|
|
13
14
|
|
|
14
15
|
// Fix CI issues with old versions
|
|
15
16
|
const isOldPuppeteerVersion = () => {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getStealthFingerPrint,
|
|
7
7
|
getVanillaFingerPrint,
|
|
8
8
|
vanillaPuppeteer,
|
|
9
|
-
} from '../../util
|
|
9
|
+
} from '../../util';
|
|
10
10
|
|
|
11
11
|
test.skip('vanilla: doesnt support proprietary codecs (requires fpcollect)', async () => {
|
|
12
12
|
const { videoCodecs, audioCodecs } = await getVanillaFingerPrint();
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getStealthFingerPrint,
|
|
7
7
|
getVanillaFingerPrint,
|
|
8
8
|
vanillaPuppeteer,
|
|
9
|
-
} from '../../util
|
|
9
|
+
} from '../../util';
|
|
10
10
|
|
|
11
11
|
// TODO: Vanilla seems fine, evasion obsolete?
|
|
12
12
|
// Note: We keep it around for now, as we will need this method in a fingerprinting plugin later anyway
|
|
@@ -6,10 +6,20 @@ import {
|
|
|
6
6
|
getStealthFingerPrint,
|
|
7
7
|
getVanillaFingerPrint,
|
|
8
8
|
vanillaPuppeteer,
|
|
9
|
-
} from '../../util
|
|
9
|
+
} from '../../util';
|
|
10
|
+
|
|
11
|
+
type FingerPrintWithPlugins = {
|
|
12
|
+
plugins: {
|
|
13
|
+
length: number;
|
|
14
|
+
};
|
|
15
|
+
mimeTypes: {
|
|
16
|
+
length: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
10
19
|
|
|
11
20
|
test.skip('vanilla: empty plugins, empty mimetypes (requires fpcollect)', async () => {
|
|
12
|
-
const { plugins, mimeTypes } =
|
|
21
|
+
const { plugins, mimeTypes } =
|
|
22
|
+
await getVanillaFingerPrint<FingerPrintWithPlugins>();
|
|
13
23
|
expect(plugins.length).toBe(0);
|
|
14
24
|
expect(mimeTypes.length).toBe(0);
|
|
15
25
|
});
|
|
@@ -35,7 +45,8 @@ test('vanilla: will not have modifications', async () => {
|
|
|
35
45
|
});
|
|
36
46
|
|
|
37
47
|
test.skip('stealth: has plugin, has mimetypes (requires fpcollect)', async () => {
|
|
38
|
-
const { plugins, mimeTypes } =
|
|
48
|
+
const { plugins, mimeTypes } =
|
|
49
|
+
await getStealthFingerPrint<FingerPrintWithPlugins>(Plugin);
|
|
39
50
|
expect(plugins.length).toBe(3);
|
|
40
51
|
expect(mimeTypes.length).toBe(4);
|
|
41
52
|
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
import Plugin from '../../../src/evasions/navigator.plugins/index.js';
|
|
3
|
-
import {
|
|
4
|
-
addExtra,
|
|
5
|
-
getDefaultLaunchArgs,
|
|
6
|
-
vanillaPuppeteer,
|
|
7
|
-
} from '../../util.js';
|
|
3
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
8
4
|
|
|
9
5
|
test('stealth: will have convincing mimeTypes', async () => {
|
|
10
6
|
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin());
|
|
@@ -55,7 +51,7 @@ test('stealth: will have convincing mimeTypes', async () => {
|
|
|
55
51
|
})(),
|
|
56
52
|
loopResult: (() => {
|
|
57
53
|
let res = '';
|
|
58
|
-
for (
|
|
54
|
+
for (let bK = 0; bK < window.navigator.mimeTypes.length; bK++)
|
|
59
55
|
bK === window.navigator.mimeTypes.length - 1
|
|
60
56
|
? (res += window.navigator.mimeTypes[bK].type)
|
|
61
57
|
: (res += window.navigator.mimeTypes[bK].type + ',');
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
import Plugin from '../../../src/evasions/navigator.plugins/index.js';
|
|
3
|
-
import {
|
|
4
|
-
addExtra,
|
|
5
|
-
getDefaultLaunchArgs,
|
|
6
|
-
vanillaPuppeteer,
|
|
7
|
-
} from '../../util.js';
|
|
3
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
8
4
|
|
|
9
5
|
test('stealth: will have convincing plugins', async () => {
|
|
10
6
|
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin());
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
import Plugin from '../../../src/evasions/navigator.vendor/index.js';
|
|
3
|
-
import {
|
|
4
|
-
addExtra,
|
|
5
|
-
getDefaultLaunchArgs,
|
|
6
|
-
vanillaPuppeteer,
|
|
7
|
-
} from '../../util.js';
|
|
3
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
8
4
|
|
|
9
5
|
test('vanilla: navigator.vendor is always Google Inc.', async () => {
|
|
10
6
|
const browser = await vanillaPuppeteer.launch({
|
|
@@ -2,11 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { expect, test } from 'vitest';
|
|
4
4
|
import Plugin from '../../../src/evasions/sourceurl/index.js';
|
|
5
|
-
import {
|
|
6
|
-
addExtra,
|
|
7
|
-
getDefaultLaunchArgs,
|
|
8
|
-
vanillaPuppeteer,
|
|
9
|
-
} from '../../util.js';
|
|
5
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
10
6
|
|
|
11
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
import Plugin from '../../../src/evasions/user-agent-override/index.js';
|
|
3
|
-
import {
|
|
4
|
-
addExtra,
|
|
5
|
-
getDefaultLaunchArgs,
|
|
6
|
-
vanillaPuppeteer,
|
|
7
|
-
} from '../../util.js';
|
|
3
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from '../../util';
|
|
8
4
|
|
|
9
5
|
// Fixed since 2.1.1?
|
|
10
6
|
// test('vanilla: Accept-Language header is missing', async () => {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getStealthFingerPrint,
|
|
7
7
|
getVanillaFingerPrint,
|
|
8
8
|
vanillaPuppeteer,
|
|
9
|
-
} from '../../util
|
|
9
|
+
} from '../../util';
|
|
10
10
|
|
|
11
11
|
// FIXME: This changed in more recent chrome versions
|
|
12
12
|
// test('vanilla: videoCard is Google Inc', async () => {
|
|
@@ -64,7 +64,7 @@ async function extendedTests() {
|
|
|
64
64
|
// Make sure we not reveal our proxy through errors
|
|
65
65
|
await test('errorOK', _ => {
|
|
66
66
|
try {
|
|
67
|
-
return context.getParameter();
|
|
67
|
+
return (context as WebGLRenderingContext).getParameter(undefined as any);
|
|
68
68
|
} catch (err) {
|
|
69
69
|
return !err.stack.includes(`at Object.apply`);
|
|
70
70
|
}
|
|
@@ -118,8 +118,13 @@ test.skip('stealth: webgl is native (requires fpcollect)', async () => {
|
|
|
118
118
|
* @see https://stackoverflow.com/questions/49267764/how-to-get-the-video-card-driver-name-using-javascript-browser-side
|
|
119
119
|
* @returns {Object}
|
|
120
120
|
*/
|
|
121
|
-
function getVideoCardInfo(
|
|
122
|
-
|
|
121
|
+
function getVideoCardInfo(
|
|
122
|
+
context: 'webgl' | 'webgl2' | 'experimental-webgl' = 'webgl'
|
|
123
|
+
) {
|
|
124
|
+
const gl = document.createElement('canvas').getContext(context) as
|
|
125
|
+
| WebGLRenderingContext
|
|
126
|
+
| WebGL2RenderingContext
|
|
127
|
+
| null;
|
|
123
128
|
if (!gl) {
|
|
124
129
|
return {
|
|
125
130
|
error: 'no webgl',
|
package/test/fpscanner.test.ts
CHANGED
|
@@ -1,54 +1,123 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import type { Browser } from 'puppeteer';
|
|
2
4
|
import { expect, test } from 'vitest';
|
|
3
5
|
import Plugin from '../dist/index.js';
|
|
4
6
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
addExtra,
|
|
8
|
+
dummyHTMLPath,
|
|
9
|
+
getDefaultLaunchArgs,
|
|
10
|
+
vanillaPuppeteer,
|
|
11
|
+
} from './util';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
// fpscanner v1.0.0 is a browser-side library — no longer a Node.js analyser.
|
|
16
|
+
// We inject the ES bundle into the page and expose the class as a window global,
|
|
17
|
+
// then call collectFingerprint() in the browser context to both collect and analyse.
|
|
18
|
+
const getFpScannerBrowserCode = (): string => {
|
|
19
|
+
const fpScannerPath = require.resolve(
|
|
20
|
+
'fpscanner/dist/fpScanner.es.js'
|
|
21
|
+
) as string;
|
|
22
|
+
const code = readFileSync(fpScannerPath, 'utf8');
|
|
23
|
+
// Replace `export { <name> as default };` with a window global assignment so
|
|
24
|
+
// the bundle can be injected as a regular (non-module) script tag.
|
|
25
|
+
return code.replace(/export \{([^}]+)\};?\s*$/, (_match, inner: string) => {
|
|
26
|
+
const name = inner.match(/(\w+)\s+as\s+default/)?.[1];
|
|
27
|
+
if (!name)
|
|
28
|
+
throw new Error('Could not find default export name in fpscanner bundle');
|
|
29
|
+
return `window.FingerprintScanner = ${name};`;
|
|
30
|
+
});
|
|
20
31
|
};
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
val => val.consistent < 3
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if (isOldPuppeteerVersion()) {
|
|
30
|
-
expect(failedChecks.length).toBe(8);
|
|
31
|
-
} else {
|
|
32
|
-
expect(failedChecks.length).toBe(7);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
33
|
+
const fpScannerCode = getFpScannerBrowserCode();
|
|
34
|
+
// GitHub's Windows runners can take over a minute to launch Chromium, inject
|
|
35
|
+
// fpscanner, and finish the vanilla baseline collection.
|
|
36
|
+
const fpscannerTestTimeout = process.platform === 'win32' ? 90000 : 30000;
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
val => val.consistent < 3
|
|
41
|
-
);
|
|
38
|
+
type FastBotDetectionDetails = Record<
|
|
39
|
+
string,
|
|
40
|
+
{ detected: boolean; severity: string }
|
|
41
|
+
>;
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
interface FpScannerResult {
|
|
44
|
+
fastBotDetection: boolean;
|
|
45
|
+
fastBotDetectionDetails: FastBotDetectionDetails;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface LaunchablePuppeteer {
|
|
49
|
+
launch(options?: { headless?: boolean; args?: string[] }): Promise<Browser>;
|
|
50
|
+
}
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const getFpScannerResult = async (
|
|
53
|
+
puppeteer: LaunchablePuppeteer
|
|
54
|
+
): Promise<FpScannerResult> => {
|
|
55
|
+
const browser = await puppeteer.launch({
|
|
56
|
+
headless: true,
|
|
57
|
+
args: getDefaultLaunchArgs(),
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const page = await browser.newPage();
|
|
61
|
+
await page.goto(`file://${dummyHTMLPath}`);
|
|
62
|
+
await page.addScriptTag({ content: fpScannerCode });
|
|
63
|
+
const result = await page.evaluate(async () => {
|
|
64
|
+
// FingerprintScanner is injected as a browser global via addScriptTag above
|
|
65
|
+
const FP = (window as Record<string, unknown>)
|
|
66
|
+
.FingerprintScanner as new () => {
|
|
67
|
+
collectFingerprint(opts: { encrypt: boolean }): Promise<unknown>;
|
|
68
|
+
};
|
|
69
|
+
const scanner = new FP();
|
|
70
|
+
return scanner.collectFingerprint({ encrypt: false });
|
|
71
|
+
});
|
|
72
|
+
return result as FpScannerResult;
|
|
73
|
+
} finally {
|
|
74
|
+
await browser.close();
|
|
53
75
|
}
|
|
54
|
-
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
test(
|
|
79
|
+
'vanilla: will fail multiple fpscanner bot detection checks',
|
|
80
|
+
async () => {
|
|
81
|
+
const result = await getFpScannerResult(vanillaPuppeteer);
|
|
82
|
+
const failedChecks = Object.entries(result.fastBotDetectionDetails)
|
|
83
|
+
.filter(([_name, val]) => val.detected)
|
|
84
|
+
.map(([name]) => name);
|
|
85
|
+
|
|
86
|
+
console.log('Vanilla failed checks:', failedChecks);
|
|
87
|
+
expect(result.fastBotDetection).toBe(true);
|
|
88
|
+
expect(failedChecks.length).toBeGreaterThan(0);
|
|
89
|
+
},
|
|
90
|
+
fpscannerTestTimeout
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
test(
|
|
94
|
+
'stealth: will pass core fpscanner automation checks',
|
|
95
|
+
async () => {
|
|
96
|
+
// PuppeteerExtra has a compatible launch() interface — assertion is safe
|
|
97
|
+
const stealthPuppeteer = addExtra(vanillaPuppeteer).use(
|
|
98
|
+
Plugin()
|
|
99
|
+
) as unknown as LaunchablePuppeteer;
|
|
100
|
+
const result = await getFpScannerResult(stealthPuppeteer);
|
|
101
|
+
const failedChecks = Object.entries(result.fastBotDetectionDetails)
|
|
102
|
+
.filter(([_name, val]) => val.detected)
|
|
103
|
+
.map(([name]) => name);
|
|
104
|
+
|
|
105
|
+
if (failedChecks.length) {
|
|
106
|
+
console.warn('The following checks failed:', failedChecks);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// These are the automation signals that the stealth plugin explicitly addresses.
|
|
110
|
+
// Stealth overrides navigator.webdriver → hasWebdriver should not fire.
|
|
111
|
+
expect(result.fastBotDetectionDetails.hasWebdriver.detected).toBe(false);
|
|
112
|
+
// Stealth injects chrome.* objects → hasMissingChromeObject should not fire.
|
|
113
|
+
expect(result.fastBotDetectionDetails.hasMissingChromeObject.detected).toBe(
|
|
114
|
+
false
|
|
115
|
+
);
|
|
116
|
+
// Neither Selenium nor Playwright markers are present in a Puppeteer session.
|
|
117
|
+
expect(result.fastBotDetectionDetails.hasSeleniumProperty.detected).toBe(
|
|
118
|
+
false
|
|
119
|
+
);
|
|
120
|
+
expect(result.fastBotDetectionDetails.hasPlaywright.detected).toBe(false);
|
|
121
|
+
},
|
|
122
|
+
fpscannerTestTimeout
|
|
123
|
+
);
|
package/test/index.test.ts
CHANGED
|
@@ -4,18 +4,26 @@ import { expect, test } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
import Plugin from '../dist/index.js';
|
|
6
6
|
|
|
7
|
+
type StealthPluginInstance = ReturnType<typeof Plugin> & {
|
|
8
|
+
opts: {
|
|
9
|
+
enabledEvasions: Set<string>;
|
|
10
|
+
};
|
|
11
|
+
availableEvasions: Set<string>;
|
|
12
|
+
dependencies: Set<string>;
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
test('is a function', async () => {
|
|
8
16
|
expect(typeof Plugin).toBe('function');
|
|
9
17
|
});
|
|
10
18
|
|
|
11
19
|
test('should have the basic class members', async () => {
|
|
12
|
-
const instance = Plugin();
|
|
20
|
+
const instance = Plugin() as StealthPluginInstance;
|
|
13
21
|
expect(instance.name).toBe(PLUGIN_NAME);
|
|
14
22
|
expect(instance._isPuppeteerExtraPlugin).toBe(true);
|
|
15
23
|
});
|
|
16
24
|
|
|
17
25
|
test('should have the public child class members', async () => {
|
|
18
|
-
const instance = Plugin();
|
|
26
|
+
const instance = Plugin() as StealthPluginInstance;
|
|
19
27
|
const prototype = Object.getPrototypeOf(instance);
|
|
20
28
|
const childClassMembers = Object.getOwnPropertyNames(prototype);
|
|
21
29
|
|
|
@@ -29,12 +37,12 @@ test('should have the public child class members', async () => {
|
|
|
29
37
|
});
|
|
30
38
|
|
|
31
39
|
test('should have opts with default values', async () => {
|
|
32
|
-
const instance = Plugin();
|
|
40
|
+
const instance = Plugin() as StealthPluginInstance;
|
|
33
41
|
expect(instance.opts.enabledEvasions).toEqual(instance.availableEvasions);
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
test('should add all dependencies dynamically', async () => {
|
|
37
|
-
const instance = Plugin();
|
|
45
|
+
const instance = Plugin() as StealthPluginInstance;
|
|
38
46
|
const deps = new Set(
|
|
39
47
|
[...instance.opts.enabledEvasions].map(e => `${PLUGIN_NAME}/evasions/${e}`)
|
|
40
48
|
);
|
|
@@ -42,7 +50,7 @@ test('should add all dependencies dynamically', async () => {
|
|
|
42
50
|
});
|
|
43
51
|
|
|
44
52
|
test('should add all dependencies dynamically including changes', async () => {
|
|
45
|
-
const instance = Plugin();
|
|
53
|
+
const instance = Plugin() as StealthPluginInstance;
|
|
46
54
|
const fakeDep = 'foobar';
|
|
47
55
|
instance.enabledEvasions = new Set([fakeDep]);
|
|
48
56
|
expect(instance.dependencies).toEqual(
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import http from 'node:http';
|
|
3
|
+
import type { AddressInfo } from 'node:net';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import type { Browser, Page, Target, WebWorker } from 'puppeteer';
|
|
5
7
|
import { afterAll, beforeAll, expect, test } from 'vitest';
|
|
6
8
|
import Plugin from '../dist/index.js';
|
|
7
|
-
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from './util
|
|
9
|
+
import { addExtra, getDefaultLaunchArgs, vanillaPuppeteer } from './util';
|
|
8
10
|
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -31,10 +33,13 @@ const httpServer = async () => {
|
|
|
31
33
|
})
|
|
32
34
|
.listen(0); // random free port
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
const address = server.address() as AddressInfo;
|
|
37
|
+
return `http://127.0.0.1:${address.port}/`;
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
let browser
|
|
40
|
+
let browser: Browser;
|
|
41
|
+
let page: Page;
|
|
42
|
+
let worker: WebWorker;
|
|
38
43
|
|
|
39
44
|
beforeAll(async () => {
|
|
40
45
|
const address = await httpServer();
|
|
@@ -45,16 +50,21 @@ beforeAll(async () => {
|
|
|
45
50
|
.launch({ headless: true, args: getDefaultLaunchArgs() });
|
|
46
51
|
page = await browser.newPage();
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
browser.on('targetcreated', async target => {
|
|
53
|
+
const workerPromise = new Promise<WebWorker>((resolve, reject) => {
|
|
54
|
+
browser.on('targetcreated', async (target: Target) => {
|
|
50
55
|
if (target.type() === 'service_worker') {
|
|
51
|
-
|
|
56
|
+
const serviceWorker = await target.worker();
|
|
57
|
+
if (serviceWorker) {
|
|
58
|
+
resolve(serviceWorker);
|
|
59
|
+
} else {
|
|
60
|
+
reject(new Error('Target did not expose a service worker'));
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
});
|
|
54
64
|
});
|
|
55
65
|
|
|
56
66
|
await page.goto(address);
|
|
57
|
-
worker = await
|
|
67
|
+
worker = await workerPromise;
|
|
58
68
|
});
|
|
59
69
|
|
|
60
70
|
afterAll(async () => {
|
|
@@ -82,9 +92,16 @@ test.skip('stealth: creepjs has good trust score', async () => {
|
|
|
82
92
|
|
|
83
93
|
/* global OffscreenCanvas */
|
|
84
94
|
function detectFingerprint() {
|
|
85
|
-
const results = {};
|
|
86
|
-
|
|
87
|
-
const props
|
|
95
|
+
const results: Record<string, string> = {};
|
|
96
|
+
|
|
97
|
+
const props: Array<
|
|
98
|
+
| 'userAgent'
|
|
99
|
+
| 'language'
|
|
100
|
+
| 'hardwareConcurrency'
|
|
101
|
+
| 'deviceMemory'
|
|
102
|
+
| 'languages'
|
|
103
|
+
| 'platform'
|
|
104
|
+
> = [
|
|
88
105
|
'userAgent',
|
|
89
106
|
'language',
|
|
90
107
|
'hardwareConcurrency',
|
|
@@ -92,19 +109,23 @@ function detectFingerprint() {
|
|
|
92
109
|
'languages',
|
|
93
110
|
'platform',
|
|
94
111
|
];
|
|
95
|
-
props.forEach(
|
|
96
|
-
results[
|
|
112
|
+
props.forEach(prop => {
|
|
113
|
+
results[prop] = String(navigator[prop]);
|
|
97
114
|
});
|
|
98
115
|
|
|
99
116
|
const canvasOffscreenWebgl = new OffscreenCanvas(256, 256);
|
|
100
117
|
const contextWebgl = canvasOffscreenWebgl.getContext('webgl');
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
rendererInfo
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
if (contextWebgl) {
|
|
119
|
+
const rendererInfo = contextWebgl.getExtension('WEBGL_debug_renderer_info');
|
|
120
|
+
if (rendererInfo) {
|
|
121
|
+
results.webglVendor = String(
|
|
122
|
+
contextWebgl.getParameter(rendererInfo.UNMASKED_VENDOR_WEBGL)
|
|
123
|
+
);
|
|
124
|
+
results.webglRenderer = String(
|
|
125
|
+
contextWebgl.getParameter(rendererInfo.UNMASKED_RENDERER_WEBGL)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
108
129
|
|
|
109
130
|
results.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
110
131
|
|