@zorilla/puppeteer-extra-plugin-stealth 1.0.0
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/.claude/settings.local.json +21 -0
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/evasions/README.md +13 -0
- package/dist/evasions/_template/README.md +18 -0
- package/dist/evasions/_template/index.d.ts +13 -0
- package/dist/evasions/_template/index.d.ts.map +1 -0
- package/dist/evasions/_template/index.js +26 -0
- package/dist/evasions/_template/index.js.map +1 -0
- package/dist/evasions/_template/package.json +5 -0
- package/dist/evasions/_utils/README.md +287 -0
- package/dist/evasions/_utils/index.d.ts +238 -0
- package/dist/evasions/_utils/index.d.ts.map +1 -0
- package/dist/evasions/_utils/index.js +588 -0
- package/dist/evasions/_utils/index.js.map +1 -0
- package/dist/evasions/_utils/withUtils.d.ts +12 -0
- package/dist/evasions/_utils/withUtils.d.ts.map +1 -0
- package/dist/evasions/_utils/withUtils.js +47 -0
- package/dist/evasions/_utils/withUtils.js.map +1 -0
- package/dist/evasions/chrome.app/README.md +16 -0
- package/dist/evasions/chrome.app/index.d.ts +11 -0
- package/dist/evasions/chrome.app/index.d.ts.map +1 -0
- package/dist/evasions/chrome.app/index.js +97 -0
- package/dist/evasions/chrome.app/index.js.map +1 -0
- package/dist/evasions/chrome.app/package.json +5 -0
- package/dist/evasions/chrome.csi/README.md +29 -0
- package/dist/evasions/chrome.csi/index.d.ts +25 -0
- package/dist/evasions/chrome.csi/index.d.ts.map +1 -0
- package/dist/evasions/chrome.csi/index.js +69 -0
- package/dist/evasions/chrome.csi/index.js.map +1 -0
- package/dist/evasions/chrome.csi/package.json +5 -0
- package/dist/evasions/chrome.loadTimes/README.md +27 -0
- package/dist/evasions/chrome.loadTimes/index.d.ts +23 -0
- package/dist/evasions/chrome.loadTimes/index.d.ts.map +1 -0
- package/dist/evasions/chrome.loadTimes/index.js +163 -0
- package/dist/evasions/chrome.loadTimes/index.js.map +1 -0
- package/dist/evasions/chrome.loadTimes/package.json +5 -0
- package/dist/evasions/chrome.runtime/README.md +32 -0
- package/dist/evasions/chrome.runtime/index.d.ts +14 -0
- package/dist/evasions/chrome.runtime/index.d.ts.map +1 -0
- package/dist/evasions/chrome.runtime/index.js +252 -0
- package/dist/evasions/chrome.runtime/index.js.map +1 -0
- package/dist/evasions/chrome.runtime/package.json +5 -0
- package/dist/evasions/chrome.runtime/staticData.json +41 -0
- package/dist/evasions/defaultArgs/README.md +17 -0
- package/dist/evasions/defaultArgs/index.d.ts +2 -0
- package/dist/evasions/defaultArgs/index.d.ts.map +1 -0
- package/dist/evasions/defaultArgs/index.js +43 -0
- package/dist/evasions/defaultArgs/index.js.map +1 -0
- package/dist/evasions/defaultArgs/package.json +5 -0
- package/dist/evasions/iframe.contentWindow/README.md +19 -0
- package/dist/evasions/iframe.contentWindow/index.d.ts +15 -0
- package/dist/evasions/iframe.contentWindow/index.d.ts.map +1 -0
- package/dist/evasions/iframe.contentWindow/index.js +132 -0
- package/dist/evasions/iframe.contentWindow/index.js.map +1 -0
- package/dist/evasions/iframe.contentWindow/package.json +5 -0
- package/dist/evasions/media.codecs/README.md +38 -0
- package/dist/evasions/media.codecs/index.d.ts +12 -0
- package/dist/evasions/media.codecs/index.d.ts.map +1 -0
- package/dist/evasions/media.codecs/index.js +89 -0
- package/dist/evasions/media.codecs/index.js.map +1 -0
- package/dist/evasions/media.codecs/package.json +5 -0
- package/dist/evasions/navigator.hardwareConcurrency/README.md +19 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.d.ts +2 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.d.ts.map +1 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.js +45 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.js.map +1 -0
- package/dist/evasions/navigator.hardwareConcurrency/package.json +5 -0
- package/dist/evasions/navigator.languages/README.md +17 -0
- package/dist/evasions/navigator.languages/index.d.ts +2 -0
- package/dist/evasions/navigator.languages/index.d.ts.map +1 -0
- package/dist/evasions/navigator.languages/index.js +44 -0
- package/dist/evasions/navigator.languages/index.js.map +1 -0
- package/dist/evasions/navigator.languages/package.json +5 -0
- package/dist/evasions/navigator.permissions/README.md +16 -0
- package/dist/evasions/navigator.permissions/index.d.ts +2 -0
- package/dist/evasions/navigator.permissions/index.d.ts.map +1 -0
- package/dist/evasions/navigator.permissions/index.js +66 -0
- package/dist/evasions/navigator.permissions/index.js.map +1 -0
- package/dist/evasions/navigator.permissions/package.json +5 -0
- package/dist/evasions/navigator.plugins/README.md +24 -0
- package/dist/evasions/navigator.plugins/data.json +48 -0
- package/dist/evasions/navigator.plugins/functionMocks.d.ts +9 -0
- package/dist/evasions/navigator.plugins/functionMocks.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/functionMocks.js +47 -0
- package/dist/evasions/navigator.plugins/functionMocks.js.map +1 -0
- package/dist/evasions/navigator.plugins/index.d.ts +19 -0
- package/dist/evasions/navigator.plugins/index.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/index.js +98 -0
- package/dist/evasions/navigator.plugins/index.js.map +1 -0
- package/dist/evasions/navigator.plugins/magicArray.d.ts +2 -0
- package/dist/evasions/navigator.plugins/magicArray.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/magicArray.js +145 -0
- package/dist/evasions/navigator.plugins/magicArray.js.map +1 -0
- package/dist/evasions/navigator.plugins/mimeTypes.d.ts +2 -0
- package/dist/evasions/navigator.plugins/mimeTypes.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/mimeTypes.js +18 -0
- package/dist/evasions/navigator.plugins/mimeTypes.js.map +1 -0
- package/dist/evasions/navigator.plugins/package.json +5 -0
- package/dist/evasions/navigator.plugins/plugins.d.ts +2 -0
- package/dist/evasions/navigator.plugins/plugins.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/plugins.js +18 -0
- package/dist/evasions/navigator.plugins/plugins.js.map +1 -0
- package/dist/evasions/navigator.vendor/README.md +36 -0
- package/dist/evasions/navigator.vendor/index.d.ts +2 -0
- package/dist/evasions/navigator.vendor/index.d.ts.map +1 -0
- package/dist/evasions/navigator.vendor/index.js +64 -0
- package/dist/evasions/navigator.vendor/index.js.map +1 -0
- package/dist/evasions/navigator.vendor/package.json +5 -0
- package/dist/evasions/navigator.webdriver/README.md +17 -0
- package/dist/evasions/navigator.webdriver/index.d.ts +13 -0
- package/dist/evasions/navigator.webdriver/index.d.ts.map +1 -0
- package/dist/evasions/navigator.webdriver/index.js +48 -0
- package/dist/evasions/navigator.webdriver/index.js.map +1 -0
- package/dist/evasions/navigator.webdriver/package.json +5 -0
- package/dist/evasions/sourceurl/README.md +17 -0
- package/dist/evasions/sourceurl/_fixtures/test.html +35 -0
- package/dist/evasions/sourceurl/index.d.ts +12 -0
- package/dist/evasions/sourceurl/index.d.ts.map +1 -0
- package/dist/evasions/sourceurl/index.js +82 -0
- package/dist/evasions/sourceurl/index.js.map +1 -0
- package/dist/evasions/sourceurl/package.json +5 -0
- package/dist/evasions/user-agent-override/README.md +53 -0
- package/dist/evasions/user-agent-override/index.d.ts +2 -0
- package/dist/evasions/user-agent-override/index.d.ts.map +1 -0
- package/dist/evasions/user-agent-override/index.js +206 -0
- package/dist/evasions/user-agent-override/index.js.map +1 -0
- package/dist/evasions/user-agent-override/package.json +5 -0
- package/dist/evasions/webgl.vendor/README.md +20 -0
- package/dist/evasions/webgl.vendor/index.d.ts +17 -0
- package/dist/evasions/webgl.vendor/index.d.ts.map +1 -0
- package/dist/evasions/webgl.vendor/index.js +57 -0
- package/dist/evasions/webgl.vendor/index.js.map +1 -0
- package/dist/evasions/webgl.vendor/package.json +5 -0
- package/dist/evasions/window.outerdimensions/README.md +17 -0
- package/dist/evasions/window.outerdimensions/index.d.ts +13 -0
- package/dist/evasions/window.outerdimensions/index.d.ts.map +1 -0
- package/dist/evasions/window.outerdimensions/index.js +42 -0
- package/dist/evasions/window.outerdimensions/index.js.map +1 -0
- package/dist/evasions/window.outerdimensions/package.json +5 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -0
- package/examples/detect-headless.js +95 -0
- package/examples/test1.js +20 -0
- package/examples/test2.js +25 -0
- package/package.json +70 -0
- package/src/evasions/README.md +13 -0
- package/src/evasions/_template/README.md +18 -0
- package/src/evasions/_template/index.js +26 -0
- package/src/evasions/_template/package.json +5 -0
- package/src/evasions/_utils/README.md +287 -0
- package/src/evasions/_utils/index.js +588 -0
- package/src/evasions/_utils/withUtils.js +47 -0
- package/src/evasions/chrome.app/README.md +16 -0
- package/src/evasions/chrome.app/index.js +97 -0
- package/src/evasions/chrome.app/package.json +5 -0
- package/src/evasions/chrome.csi/README.md +29 -0
- package/src/evasions/chrome.csi/index.js +69 -0
- package/src/evasions/chrome.csi/package.json +5 -0
- package/src/evasions/chrome.loadTimes/README.md +27 -0
- package/src/evasions/chrome.loadTimes/index.js +163 -0
- package/src/evasions/chrome.loadTimes/package.json +5 -0
- package/src/evasions/chrome.runtime/README.md +32 -0
- package/src/evasions/chrome.runtime/index.js +252 -0
- package/src/evasions/chrome.runtime/package.json +5 -0
- package/src/evasions/chrome.runtime/staticData.json +41 -0
- package/src/evasions/defaultArgs/README.md +17 -0
- package/src/evasions/defaultArgs/index.js +43 -0
- package/src/evasions/defaultArgs/package.json +5 -0
- package/src/evasions/iframe.contentWindow/README.md +19 -0
- package/src/evasions/iframe.contentWindow/index.js +132 -0
- package/src/evasions/iframe.contentWindow/package.json +5 -0
- package/src/evasions/media.codecs/README.md +38 -0
- package/src/evasions/media.codecs/index.js +89 -0
- package/src/evasions/media.codecs/package.json +5 -0
- package/src/evasions/navigator.hardwareConcurrency/README.md +19 -0
- package/src/evasions/navigator.hardwareConcurrency/index.js +45 -0
- package/src/evasions/navigator.hardwareConcurrency/package.json +5 -0
- package/src/evasions/navigator.languages/README.md +17 -0
- package/src/evasions/navigator.languages/index.js +44 -0
- package/src/evasions/navigator.languages/package.json +5 -0
- package/src/evasions/navigator.permissions/README.md +16 -0
- package/src/evasions/navigator.permissions/index.js +66 -0
- package/src/evasions/navigator.permissions/package.json +5 -0
- package/src/evasions/navigator.plugins/README.md +24 -0
- package/src/evasions/navigator.plugins/data.json +48 -0
- package/src/evasions/navigator.plugins/functionMocks.js +47 -0
- package/src/evasions/navigator.plugins/index.js +98 -0
- package/src/evasions/navigator.plugins/magicArray.js +145 -0
- package/src/evasions/navigator.plugins/mimeTypes.js +18 -0
- package/src/evasions/navigator.plugins/package.json +5 -0
- package/src/evasions/navigator.plugins/plugins.js +18 -0
- package/src/evasions/navigator.vendor/README.md +36 -0
- package/src/evasions/navigator.vendor/index.js +64 -0
- package/src/evasions/navigator.vendor/package.json +5 -0
- package/src/evasions/navigator.webdriver/README.md +17 -0
- package/src/evasions/navigator.webdriver/index.js +48 -0
- package/src/evasions/navigator.webdriver/package.json +5 -0
- package/src/evasions/sourceurl/README.md +17 -0
- package/src/evasions/sourceurl/_fixtures/test.html +35 -0
- package/src/evasions/sourceurl/index.js +82 -0
- package/src/evasions/sourceurl/package.json +5 -0
- package/src/evasions/user-agent-override/README.md +53 -0
- package/src/evasions/user-agent-override/index.js +206 -0
- package/src/evasions/user-agent-override/package.json +5 -0
- package/src/evasions/webgl.vendor/README.md +20 -0
- package/src/evasions/webgl.vendor/index.js +57 -0
- package/src/evasions/webgl.vendor/package.json +5 -0
- package/src/evasions/window.outerdimensions/README.md +17 -0
- package/src/evasions/window.outerdimensions/index.js +42 -0
- package/src/evasions/window.outerdimensions/package.json +5 -0
- package/src/index.d.ts +111 -0
- package/src/index.ts +188 -0
- package/test/cat-and-mouse.test.ts +170 -0
- package/test/evasions/_utils/index.test.ts +710 -0
- package/test/evasions/chrome.app/index.test.ts +69 -0
- package/test/evasions/chrome.csi/index.test.ts +46 -0
- package/test/evasions/chrome.loadTimes/index.test.ts +61 -0
- package/test/evasions/chrome.runtime/index.test.ts +282 -0
- package/test/evasions/defaultArgs/index.test.ts +36 -0
- package/test/evasions/iframe.contentWindow/index.test.js +450 -0
- package/test/evasions/media.codecs/index.test.js +103 -0
- package/test/evasions/navigator.hardwareConcurrency/index.test.js +58 -0
- package/test/evasions/navigator.languages/index.test.js +101 -0
- package/test/evasions/navigator.permissions/index.test.js +104 -0
- package/test/evasions/navigator.plugins/index.test.js +55 -0
- package/test/evasions/navigator.plugins/mimeTypes.test.js +220 -0
- package/test/evasions/navigator.plugins/plugins.test.js +181 -0
- package/test/evasions/navigator.vendor/index.test.js +68 -0
- package/test/evasions/navigator.webdriver/index.test.js +47 -0
- package/test/evasions/sourceurl/_fixtures/test.html +35 -0
- package/test/evasions/sourceurl/index.test.js +62 -0
- package/test/evasions/user-agent-override/index.test.js +338 -0
- package/test/evasions/webgl.vendor/index.test.js +220 -0
- package/test/fixtures/dummy-with-service-worker.html +22 -0
- package/test/fixtures/dummy.html +11 -0
- package/test/fixtures/sw.js +1 -0
- package/test/fpscanner.test.ts +54 -0
- package/test/index.test.ts +51 -0
- package/test/service-worker.test.ts +112 -0
- package/test/stealth/_results/_thumbs/headful-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/headful-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/headful-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/headful-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/headful-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/headless-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/headless-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/headless-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/headless-chromium-vanilla.js.png +0 -0
- package/test/stealth/headful-chrome-stealth.js +25 -0
- package/test/stealth/headful-chrome-vanilla.js +23 -0
- package/test/stealth/headful-chromium-stealth.js +22 -0
- package/test/stealth/headful-chromium-vanilla.js +20 -0
- package/test/stealth/headless-chrome-stealth.js +25 -0
- package/test/stealth/headless-chrome-vanilla.js +23 -0
- package/test/stealth/headless-chromium-stealth.js +22 -0
- package/test/stealth/headless-chromium-vanilla.js +20 -0
- package/test/util.js +82 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +28 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
|
|
2
|
+
/**
|
|
3
|
+
* Stealth mode: Applies various techniques to make detection of headless puppeteer harder. 💯
|
|
4
|
+
*
|
|
5
|
+
* ### Purpose
|
|
6
|
+
* There are a couple of ways the use of puppeteer can easily be detected by a target website.
|
|
7
|
+
* The addition of `HeadlessChrome` to the user-agent being only the most obvious one.
|
|
8
|
+
*
|
|
9
|
+
* The goal of this plugin is to be the definite companion to puppeteer to avoid
|
|
10
|
+
* detection, applying new techniques as they surface.
|
|
11
|
+
*
|
|
12
|
+
* As this cat & mouse game is in it's infancy and fast-paced the plugin
|
|
13
|
+
* is kept as flexibile as possible, to support quick testing and iterations.
|
|
14
|
+
*
|
|
15
|
+
* ### Modularity
|
|
16
|
+
* This plugin uses `puppeteer-extra`'s dependency system to only require
|
|
17
|
+
* code mods for evasions that have been enabled, to keep things modular and efficient.
|
|
18
|
+
*
|
|
19
|
+
* The `stealth` plugin is a convenience wrapper that requires multiple [evasion techniques](./evasions/)
|
|
20
|
+
* automatically and comes with defaults. You could also bypass the main module and require
|
|
21
|
+
* specific evasion plugins yourself, if you whish to do so (as they're standalone `puppeteer-extra` plugins):
|
|
22
|
+
*
|
|
23
|
+
* ```es6
|
|
24
|
+
* // bypass main module and require a specific stealth plugin directly:
|
|
25
|
+
* puppeteer.use(require('@zorilla/puppeteer-extra-plugin-stealth/evasions/console.debug')())
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ### Contributing
|
|
29
|
+
* PRs are welcome, if you want to add a new evasion technique I suggest you
|
|
30
|
+
* look at the [template](./evasions/_template) to kickstart things.
|
|
31
|
+
*
|
|
32
|
+
* ### Kudos
|
|
33
|
+
* Thanks to [Evan Sangaline](https://intoli.com/blog/not-possible-to-block-chrome-headless/) and [Paul Irish](https://github.com/paulirish/headless-cat-n-mouse) for kickstarting the discussion!
|
|
34
|
+
*
|
|
35
|
+
* ---
|
|
36
|
+
*
|
|
37
|
+
* @todo
|
|
38
|
+
* - white-/blacklist with url globs (make this a generic plugin method?)
|
|
39
|
+
* - dynamic whitelist based on function evaluation
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const puppeteer = require('@zorilla/puppeteer-extra')
|
|
43
|
+
* // Enable stealth plugin with all evasions
|
|
44
|
+
* puppeteer.use(require('@zorilla/puppeteer-extra-plugin-stealth')())
|
|
45
|
+
*
|
|
46
|
+
*
|
|
47
|
+
* ;(async () => {
|
|
48
|
+
* // Launch the browser in headless mode and set up a page.
|
|
49
|
+
* const browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true })
|
|
50
|
+
* const page = await browser.newPage()
|
|
51
|
+
*
|
|
52
|
+
* // Navigate to the page that will perform the tests.
|
|
53
|
+
* const testUrl = 'https://intoli.com/blog/' +
|
|
54
|
+
* 'not-possible-to-block-chrome-headless/chrome-headless-test.html'
|
|
55
|
+
* await page.goto(testUrl)
|
|
56
|
+
*
|
|
57
|
+
* // Save a screenshot of the results.
|
|
58
|
+
* const screenshotPath = '/tmp/headless-test-result.png'
|
|
59
|
+
* await page.screenshot({path: screenshotPath})
|
|
60
|
+
* console.log('have a look at the screenshot:', screenshotPath)
|
|
61
|
+
*
|
|
62
|
+
* await browser.close()
|
|
63
|
+
* })()
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} [opts] - Options
|
|
66
|
+
* @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)
|
|
67
|
+
*
|
|
68
|
+
*/
|
|
69
|
+
class StealthPlugin extends PuppeteerExtraPlugin {
|
|
70
|
+
constructor(opts = {}) {
|
|
71
|
+
super(opts);
|
|
72
|
+
}
|
|
73
|
+
get name() {
|
|
74
|
+
return 'stealth';
|
|
75
|
+
}
|
|
76
|
+
get defaults() {
|
|
77
|
+
const availableEvasions = new Set([
|
|
78
|
+
'chrome.app',
|
|
79
|
+
'chrome.csi',
|
|
80
|
+
'chrome.loadTimes',
|
|
81
|
+
'chrome.runtime',
|
|
82
|
+
'defaultArgs',
|
|
83
|
+
'iframe.contentWindow',
|
|
84
|
+
'media.codecs',
|
|
85
|
+
'navigator.hardwareConcurrency',
|
|
86
|
+
'navigator.languages',
|
|
87
|
+
'navigator.permissions',
|
|
88
|
+
'navigator.plugins',
|
|
89
|
+
'navigator.webdriver',
|
|
90
|
+
'sourceurl',
|
|
91
|
+
'user-agent-override',
|
|
92
|
+
'webgl.vendor',
|
|
93
|
+
'window.outerdimensions',
|
|
94
|
+
]);
|
|
95
|
+
return {
|
|
96
|
+
availableEvasions,
|
|
97
|
+
// Enable all available evasions by default
|
|
98
|
+
enabledEvasions: new Set([...availableEvasions]),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Requires evasion techniques dynamically based on configuration.
|
|
103
|
+
*
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
get dependencies() {
|
|
107
|
+
const enabledEvasions = this.opts.enabledEvasions;
|
|
108
|
+
return new Set([...enabledEvasions].map(e => `${this.name}/evasions/${e}`));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all available evasions.
|
|
112
|
+
*
|
|
113
|
+
* Please look into the [evasions directory](./evasions/) for an up to date list.
|
|
114
|
+
*
|
|
115
|
+
* @type {Set<string>} - A Set of all available evasions.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* const pluginStealth = require('@zorilla/puppeteer-extra-plugin-stealth')()
|
|
119
|
+
* console.log(pluginStealth.availableEvasions) // => Set { 'user-agent', 'console.debug' }
|
|
120
|
+
* puppeteer.use(pluginStealth)
|
|
121
|
+
*/
|
|
122
|
+
get availableEvasions() {
|
|
123
|
+
return this.defaults.availableEvasions;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get all enabled evasions.
|
|
127
|
+
*
|
|
128
|
+
* Enabled evasions can be configured either through `opts` or by modifying this property.
|
|
129
|
+
*
|
|
130
|
+
* @type {Set<string>} - A Set of all enabled evasions.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // Remove specific evasion from enabled ones dynamically
|
|
134
|
+
* const pluginStealth = require('@zorilla/puppeteer-extra-plugin-stealth')()
|
|
135
|
+
* pluginStealth.enabledEvasions.delete('console.debug')
|
|
136
|
+
* puppeteer.use(pluginStealth)
|
|
137
|
+
*/
|
|
138
|
+
get enabledEvasions() {
|
|
139
|
+
return this.opts.enabledEvasions;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
set enabledEvasions(evasions) {
|
|
145
|
+
this.opts.enabledEvasions = evasions;
|
|
146
|
+
}
|
|
147
|
+
async onBrowser(browser, _opts) {
|
|
148
|
+
// Browser extends EventEmitter, increase listeners to prevent MaxListenersExceededWarning
|
|
149
|
+
const emitter = browser;
|
|
150
|
+
if (emitter?.setMaxListeners) {
|
|
151
|
+
emitter.setMaxListeners(30);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Default export, PuppeteerExtraStealthPlugin
|
|
157
|
+
*
|
|
158
|
+
* @param {Object} [opts] - Options
|
|
159
|
+
* @param {Set<string>} [opts.enabledEvasions] - Specify which evasions to use (by default all)
|
|
160
|
+
*/
|
|
161
|
+
export default (opts) => new StealthPlugin(opts);
|
|
162
|
+
// Named export
|
|
163
|
+
export { StealthPlugin };
|
|
164
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAavE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,aAAc,SAAQ,oBAAoB;IAC9C,YAAY,OAAsB,EAAE;QAClC,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,IAAa,IAAI;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAa,QAAQ;QACnB,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;YAChC,YAAY;YACZ,YAAY;YACZ,kBAAkB;YAClB,gBAAgB;YAChB,aAAa;YACb,sBAAsB;YACtB,cAAc;YACd,+BAA+B;YAC/B,qBAAqB;YACrB,uBAAuB;YACvB,mBAAmB;YACnB,qBAAqB;YACrB,WAAW;YACX,qBAAqB;YACrB,cAAc;YACd,wBAAwB;SACzB,CAAC,CAAC;QACH,OAAO;YACL,iBAAiB;YACjB,2CAA2C;YAC3C,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC;SACjD,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAa,YAAY;QACvB,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,eAA8B,CAAC;QACjE,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED;;;;;;;;;;;OAWG;IACH,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACzC,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,eAA8B,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,IAAI,eAAe,CAAC,QAAqB;QACvC,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;IACvC,CAAC;IAEQ,KAAK,CAAC,SAAS,CACtB,OAA0B,EAC1B,KAAqB;QAErB,0FAA0F;QAC1F,MAAM,OAAO,GAAG,OAAkC,CAAC;QACnD,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;YAC7B,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;CACF;AAED;;;;;GAKG;AACH,eAAe,CAAC,IAAoB,EAAiB,EAAE,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AAEhF,eAAe;AACf,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// taken from: https://github.com/paulirish/headless-cat-n-mouse/blob/master/detect-headless.js
|
|
2
|
+
// initial detects from @antoinevastel
|
|
3
|
+
// http://antoinevastel.github.io/bot%20detection/2018/01/17/detect-chrome-headless-v2.html
|
|
4
|
+
|
|
5
|
+
export default async () => {
|
|
6
|
+
const results = {};
|
|
7
|
+
|
|
8
|
+
async function test(name, fn) {
|
|
9
|
+
const detectionPassed = await fn();
|
|
10
|
+
if (detectionPassed) {
|
|
11
|
+
console.log(`WARNING: Chrome headless detected via ${name}`);
|
|
12
|
+
} else {
|
|
13
|
+
console.log(`PASS: Chrome headless NOT detected via ${name}`);
|
|
14
|
+
}
|
|
15
|
+
results[name] = detectionPassed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await test('userAgent', _ => {
|
|
19
|
+
return /HeadlessChrome/.test(window.navigator.userAgent);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Detects the --enable-automation || --headless flags
|
|
23
|
+
// Will return true in headful if --enable-automation is provided
|
|
24
|
+
await test('navigator.webdriver present', _ => {
|
|
25
|
+
return 'webdriver' in navigator;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await test('window.chrome missing', _ => {
|
|
29
|
+
return /Chrome/.test(window.navigator.userAgent) && !window.chrome;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await test('permissions API', async _ => {
|
|
33
|
+
const permissionStatus = await navigator.permissions.query({
|
|
34
|
+
name: 'notifications',
|
|
35
|
+
});
|
|
36
|
+
// eslint-disable-next-line
|
|
37
|
+
return (
|
|
38
|
+
Notification.permission === 'denied' && // eslint-disable-line no-undef
|
|
39
|
+
permissionStatus.state === 'prompt'
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await test('permissions API overriden', _ => {
|
|
44
|
+
const permissions = window.navigator.permissions;
|
|
45
|
+
if (permissions.query.toString() !== 'function query() { [native code] }')
|
|
46
|
+
return true;
|
|
47
|
+
if (
|
|
48
|
+
permissions.query.toString.toString() !==
|
|
49
|
+
'function toString() { [native code] }'
|
|
50
|
+
)
|
|
51
|
+
return true;
|
|
52
|
+
if (
|
|
53
|
+
Object.hasOwn(permissions.query.toString, '[[Handler]]') && // eslint-disable-line no-prototype-builtins
|
|
54
|
+
Object.hasOwn(permissions.query.toString, '[[Target]]') && // eslint-disable-line no-prototype-builtins
|
|
55
|
+
Object.hasOwn(permissions.query.toString, '[[IsRevoked]]') // eslint-disable-line no-prototype-builtins
|
|
56
|
+
)
|
|
57
|
+
return true;
|
|
58
|
+
if (Object.hasOwn(permissions, 'query')) return true; // eslint-disable-line no-prototype-builtins
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await test('navigator.plugins empty', _ => {
|
|
62
|
+
return navigator.plugins.length === 0;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await test('navigator.languages blank', _ => {
|
|
66
|
+
return navigator.languages === '';
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await test('iFrame for fresh window object', _ => {
|
|
70
|
+
// evaluateOnNewDocument scripts don't apply within [srcdoc] (or [sandbox]) iframes
|
|
71
|
+
// https://github.com/GoogleChrome/puppeteer/issues/1106#issuecomment-359313898
|
|
72
|
+
const iframe = document.createElement('iframe');
|
|
73
|
+
iframe.srcdoc = 'page intentionally left blank';
|
|
74
|
+
document.body.appendChild(iframe);
|
|
75
|
+
|
|
76
|
+
// Here we would need to rerun all tests with `iframe.contentWindow` as `window`
|
|
77
|
+
// Example:
|
|
78
|
+
return iframe.contentWindow.navigator.plugins.length === 0;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// This detects that a devtools protocol agent is attached.
|
|
82
|
+
// So it will also pass true in headful Chrome if the devtools window is attached
|
|
83
|
+
await test('toString', _ => {
|
|
84
|
+
let gotYou = 0;
|
|
85
|
+
const spooky = /./;
|
|
86
|
+
spooky.toString = () => {
|
|
87
|
+
gotYou++;
|
|
88
|
+
return 'spooky';
|
|
89
|
+
};
|
|
90
|
+
console.debug(spooky);
|
|
91
|
+
return gotYou > 1;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return results;
|
|
95
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import puppeteer from '@zorilla/puppeteer-extra';
|
|
2
|
+
import stealthPlugin from '@zorilla/puppeteer-extra-plugin-stealth';
|
|
3
|
+
import detectHeadless from './detect-headless.js';
|
|
4
|
+
|
|
5
|
+
puppeteer.use(stealthPlugin());
|
|
6
|
+
|
|
7
|
+
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
|
|
8
|
+
const page = await browser.newPage();
|
|
9
|
+
page.on('console', msg => {
|
|
10
|
+
console.log('Page console: ', msg.text());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
await page.goto('about:blank');
|
|
14
|
+
const detectionResults = await page.evaluate(detectHeadless);
|
|
15
|
+
console.assert(
|
|
16
|
+
Object.keys(detectionResults).length,
|
|
17
|
+
'No detection results returned.'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
await browser.close();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import puppeteer from '@zorilla/puppeteer-extra';
|
|
2
|
+
import stealthPlugin from '@zorilla/puppeteer-extra-plugin-stealth';
|
|
3
|
+
|
|
4
|
+
// Enable stealth plugin
|
|
5
|
+
puppeteer.use(stealthPlugin());
|
|
6
|
+
|
|
7
|
+
// Launch the browser in headless mode and set up a page.
|
|
8
|
+
const browser = await puppeteer.launch({
|
|
9
|
+
args: ['--no-sandbox'],
|
|
10
|
+
headless: true,
|
|
11
|
+
});
|
|
12
|
+
const page = await browser.newPage();
|
|
13
|
+
|
|
14
|
+
// Navigate to the page that will perform the tests.
|
|
15
|
+
const testUrl =
|
|
16
|
+
'https://intoli.com/blog/' +
|
|
17
|
+
'not-possible-to-block-chrome-headless/chrome-headless-test.html';
|
|
18
|
+
await page.goto(testUrl);
|
|
19
|
+
|
|
20
|
+
// Save a screenshot of the results.
|
|
21
|
+
const screenshotPath = '/tmp/headless-test-result.png';
|
|
22
|
+
await page.screenshot({ path: screenshotPath });
|
|
23
|
+
console.log('have a look at the screenshot:', screenshotPath);
|
|
24
|
+
|
|
25
|
+
await browser.close();
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zorilla/puppeteer-extra-plugin-stealth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stealth mode: Applies various techniques to make detection of headless puppeteer harder.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./evasions/*": "./dist/evasions/*/index.js"
|
|
14
|
+
},
|
|
15
|
+
"repository": "zorillajs/zorilla",
|
|
16
|
+
"homepage": "https://github.com/zorillajs/zorilla/tree/main/packages/puppeteer-extra-plugin-stealth#readme",
|
|
17
|
+
"authors": [
|
|
18
|
+
"berstend",
|
|
19
|
+
"Justin Beckwith <justin.beckwith@gmail.com>"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"puppeteer",
|
|
27
|
+
"puppeteer-extra",
|
|
28
|
+
"puppeteer-extra-plugin",
|
|
29
|
+
"stealth",
|
|
30
|
+
"stealth-mode",
|
|
31
|
+
"detection-evasion",
|
|
32
|
+
"crawler",
|
|
33
|
+
"chrome",
|
|
34
|
+
"headless",
|
|
35
|
+
"pupeteer"
|
|
36
|
+
],
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.0.6",
|
|
39
|
+
"fpcollect": "^1.0.5",
|
|
40
|
+
"fpscanner": "^0.1.5",
|
|
41
|
+
"loop": "^3.3.6",
|
|
42
|
+
"npm-run-all": "^4.1.5",
|
|
43
|
+
"puppeteer": "^24.35.0",
|
|
44
|
+
"vitest": "^4.0.17",
|
|
45
|
+
"@zorilla/puppeteer-extra": "1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"debug": "^4.4.3",
|
|
49
|
+
"@zorilla/puppeteer-extra-plugin": "1.0.0",
|
|
50
|
+
"@zorilla/puppeteer-extra-plugin-user-preferences": "1.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"playwright-extra": "*",
|
|
54
|
+
"puppeteer-extra": "*"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"puppeteer-extra": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"playwright-extra": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsc && cp -r src/evasions dist/ && find dist/evasions -name '*.js' -exec sed -i '' \"s|'puppeteer-extra-plugin'|'@zorilla/puppeteer-extra-plugin'|g\" {} \\;",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"lint": "eslint --ext .js,.ts ."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# puppeteer-extra-plugin-stealth/evasions
|
|
2
|
+
|
|
3
|
+
Various detection evasion plugins for `puppeteer-extra-plugin-stealth`.
|
|
4
|
+
|
|
5
|
+
You can bypass the main module and require specific evasion plugins yourself, if you wish to do so:
|
|
6
|
+
|
|
7
|
+
```es6
|
|
8
|
+
puppeteer.use(
|
|
9
|
+
require('@zorilla/puppeteer-extra-plugin-stealth/evasions/console.debug')()
|
|
10
|
+
)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If you want to add a new evasion technique I suggest you look at the [template](./_template/) to kickstart things.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## API
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#### Table of Contents
|
|
5
|
+
|
|
6
|
+
- [class: Plugin](#class-plugin)
|
|
7
|
+
|
|
8
|
+
### class: [Plugin](https://github.com/zorillajs/zorilla/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_template/index.js#L10-L24)
|
|
9
|
+
|
|
10
|
+
- `opts` (optional, default `{}`)
|
|
11
|
+
|
|
12
|
+
**Extends: PuppeteerExtraPlugin**
|
|
13
|
+
|
|
14
|
+
Minimal stealth plugin template, not being used. :-)
|
|
15
|
+
|
|
16
|
+
Feel free to copy this folder as the basis for additional detection evasion plugins.
|
|
17
|
+
|
|
18
|
+
---
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal stealth plugin template, not being used. :-)
|
|
5
|
+
*
|
|
6
|
+
* Feel free to copy this folder as the basis for additional detection evasion plugins.
|
|
7
|
+
*/
|
|
8
|
+
class Plugin extends PuppeteerExtraPlugin {
|
|
9
|
+
constructor(opts = {}) {
|
|
10
|
+
super(opts);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get name() {
|
|
14
|
+
return 'stealth/evasions/_template';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async onPageCreated(page) {
|
|
18
|
+
await page.evaluateOnNewDocument(() => {
|
|
19
|
+
console.debug('hello world');
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function (pluginConfig) {
|
|
25
|
+
return new Plugin(pluginConfig);
|
|
26
|
+
}
|