@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.
Files changed (270) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +324 -0
  4. package/dist/evasions/README.md +13 -0
  5. package/dist/evasions/_template/README.md +18 -0
  6. package/dist/evasions/_template/index.d.ts +13 -0
  7. package/dist/evasions/_template/index.d.ts.map +1 -0
  8. package/dist/evasions/_template/index.js +26 -0
  9. package/dist/evasions/_template/index.js.map +1 -0
  10. package/dist/evasions/_template/package.json +5 -0
  11. package/dist/evasions/_utils/README.md +287 -0
  12. package/dist/evasions/_utils/index.d.ts +238 -0
  13. package/dist/evasions/_utils/index.d.ts.map +1 -0
  14. package/dist/evasions/_utils/index.js +588 -0
  15. package/dist/evasions/_utils/index.js.map +1 -0
  16. package/dist/evasions/_utils/withUtils.d.ts +12 -0
  17. package/dist/evasions/_utils/withUtils.d.ts.map +1 -0
  18. package/dist/evasions/_utils/withUtils.js +47 -0
  19. package/dist/evasions/_utils/withUtils.js.map +1 -0
  20. package/dist/evasions/chrome.app/README.md +16 -0
  21. package/dist/evasions/chrome.app/index.d.ts +11 -0
  22. package/dist/evasions/chrome.app/index.d.ts.map +1 -0
  23. package/dist/evasions/chrome.app/index.js +97 -0
  24. package/dist/evasions/chrome.app/index.js.map +1 -0
  25. package/dist/evasions/chrome.app/package.json +5 -0
  26. package/dist/evasions/chrome.csi/README.md +29 -0
  27. package/dist/evasions/chrome.csi/index.d.ts +25 -0
  28. package/dist/evasions/chrome.csi/index.d.ts.map +1 -0
  29. package/dist/evasions/chrome.csi/index.js +69 -0
  30. package/dist/evasions/chrome.csi/index.js.map +1 -0
  31. package/dist/evasions/chrome.csi/package.json +5 -0
  32. package/dist/evasions/chrome.loadTimes/README.md +27 -0
  33. package/dist/evasions/chrome.loadTimes/index.d.ts +23 -0
  34. package/dist/evasions/chrome.loadTimes/index.d.ts.map +1 -0
  35. package/dist/evasions/chrome.loadTimes/index.js +163 -0
  36. package/dist/evasions/chrome.loadTimes/index.js.map +1 -0
  37. package/dist/evasions/chrome.loadTimes/package.json +5 -0
  38. package/dist/evasions/chrome.runtime/README.md +32 -0
  39. package/dist/evasions/chrome.runtime/index.d.ts +14 -0
  40. package/dist/evasions/chrome.runtime/index.d.ts.map +1 -0
  41. package/dist/evasions/chrome.runtime/index.js +252 -0
  42. package/dist/evasions/chrome.runtime/index.js.map +1 -0
  43. package/dist/evasions/chrome.runtime/package.json +5 -0
  44. package/dist/evasions/chrome.runtime/staticData.json +41 -0
  45. package/dist/evasions/defaultArgs/README.md +17 -0
  46. package/dist/evasions/defaultArgs/index.d.ts +2 -0
  47. package/dist/evasions/defaultArgs/index.d.ts.map +1 -0
  48. package/dist/evasions/defaultArgs/index.js +43 -0
  49. package/dist/evasions/defaultArgs/index.js.map +1 -0
  50. package/dist/evasions/defaultArgs/package.json +5 -0
  51. package/dist/evasions/iframe.contentWindow/README.md +19 -0
  52. package/dist/evasions/iframe.contentWindow/index.d.ts +15 -0
  53. package/dist/evasions/iframe.contentWindow/index.d.ts.map +1 -0
  54. package/dist/evasions/iframe.contentWindow/index.js +132 -0
  55. package/dist/evasions/iframe.contentWindow/index.js.map +1 -0
  56. package/dist/evasions/iframe.contentWindow/package.json +5 -0
  57. package/dist/evasions/media.codecs/README.md +38 -0
  58. package/dist/evasions/media.codecs/index.d.ts +12 -0
  59. package/dist/evasions/media.codecs/index.d.ts.map +1 -0
  60. package/dist/evasions/media.codecs/index.js +89 -0
  61. package/dist/evasions/media.codecs/index.js.map +1 -0
  62. package/dist/evasions/media.codecs/package.json +5 -0
  63. package/dist/evasions/navigator.hardwareConcurrency/README.md +19 -0
  64. package/dist/evasions/navigator.hardwareConcurrency/index.d.ts +2 -0
  65. package/dist/evasions/navigator.hardwareConcurrency/index.d.ts.map +1 -0
  66. package/dist/evasions/navigator.hardwareConcurrency/index.js +45 -0
  67. package/dist/evasions/navigator.hardwareConcurrency/index.js.map +1 -0
  68. package/dist/evasions/navigator.hardwareConcurrency/package.json +5 -0
  69. package/dist/evasions/navigator.languages/README.md +17 -0
  70. package/dist/evasions/navigator.languages/index.d.ts +2 -0
  71. package/dist/evasions/navigator.languages/index.d.ts.map +1 -0
  72. package/dist/evasions/navigator.languages/index.js +44 -0
  73. package/dist/evasions/navigator.languages/index.js.map +1 -0
  74. package/dist/evasions/navigator.languages/package.json +5 -0
  75. package/dist/evasions/navigator.permissions/README.md +16 -0
  76. package/dist/evasions/navigator.permissions/index.d.ts +2 -0
  77. package/dist/evasions/navigator.permissions/index.d.ts.map +1 -0
  78. package/dist/evasions/navigator.permissions/index.js +66 -0
  79. package/dist/evasions/navigator.permissions/index.js.map +1 -0
  80. package/dist/evasions/navigator.permissions/package.json +5 -0
  81. package/dist/evasions/navigator.plugins/README.md +24 -0
  82. package/dist/evasions/navigator.plugins/data.json +48 -0
  83. package/dist/evasions/navigator.plugins/functionMocks.d.ts +9 -0
  84. package/dist/evasions/navigator.plugins/functionMocks.d.ts.map +1 -0
  85. package/dist/evasions/navigator.plugins/functionMocks.js +47 -0
  86. package/dist/evasions/navigator.plugins/functionMocks.js.map +1 -0
  87. package/dist/evasions/navigator.plugins/index.d.ts +19 -0
  88. package/dist/evasions/navigator.plugins/index.d.ts.map +1 -0
  89. package/dist/evasions/navigator.plugins/index.js +98 -0
  90. package/dist/evasions/navigator.plugins/index.js.map +1 -0
  91. package/dist/evasions/navigator.plugins/magicArray.d.ts +2 -0
  92. package/dist/evasions/navigator.plugins/magicArray.d.ts.map +1 -0
  93. package/dist/evasions/navigator.plugins/magicArray.js +145 -0
  94. package/dist/evasions/navigator.plugins/magicArray.js.map +1 -0
  95. package/dist/evasions/navigator.plugins/mimeTypes.d.ts +2 -0
  96. package/dist/evasions/navigator.plugins/mimeTypes.d.ts.map +1 -0
  97. package/dist/evasions/navigator.plugins/mimeTypes.js +18 -0
  98. package/dist/evasions/navigator.plugins/mimeTypes.js.map +1 -0
  99. package/dist/evasions/navigator.plugins/package.json +5 -0
  100. package/dist/evasions/navigator.plugins/plugins.d.ts +2 -0
  101. package/dist/evasions/navigator.plugins/plugins.d.ts.map +1 -0
  102. package/dist/evasions/navigator.plugins/plugins.js +18 -0
  103. package/dist/evasions/navigator.plugins/plugins.js.map +1 -0
  104. package/dist/evasions/navigator.vendor/README.md +36 -0
  105. package/dist/evasions/navigator.vendor/index.d.ts +2 -0
  106. package/dist/evasions/navigator.vendor/index.d.ts.map +1 -0
  107. package/dist/evasions/navigator.vendor/index.js +64 -0
  108. package/dist/evasions/navigator.vendor/index.js.map +1 -0
  109. package/dist/evasions/navigator.vendor/package.json +5 -0
  110. package/dist/evasions/navigator.webdriver/README.md +17 -0
  111. package/dist/evasions/navigator.webdriver/index.d.ts +13 -0
  112. package/dist/evasions/navigator.webdriver/index.d.ts.map +1 -0
  113. package/dist/evasions/navigator.webdriver/index.js +48 -0
  114. package/dist/evasions/navigator.webdriver/index.js.map +1 -0
  115. package/dist/evasions/navigator.webdriver/package.json +5 -0
  116. package/dist/evasions/sourceurl/README.md +17 -0
  117. package/dist/evasions/sourceurl/_fixtures/test.html +35 -0
  118. package/dist/evasions/sourceurl/index.d.ts +12 -0
  119. package/dist/evasions/sourceurl/index.d.ts.map +1 -0
  120. package/dist/evasions/sourceurl/index.js +82 -0
  121. package/dist/evasions/sourceurl/index.js.map +1 -0
  122. package/dist/evasions/sourceurl/package.json +5 -0
  123. package/dist/evasions/user-agent-override/README.md +53 -0
  124. package/dist/evasions/user-agent-override/index.d.ts +2 -0
  125. package/dist/evasions/user-agent-override/index.d.ts.map +1 -0
  126. package/dist/evasions/user-agent-override/index.js +206 -0
  127. package/dist/evasions/user-agent-override/index.js.map +1 -0
  128. package/dist/evasions/user-agent-override/package.json +5 -0
  129. package/dist/evasions/webgl.vendor/README.md +20 -0
  130. package/dist/evasions/webgl.vendor/index.d.ts +17 -0
  131. package/dist/evasions/webgl.vendor/index.d.ts.map +1 -0
  132. package/dist/evasions/webgl.vendor/index.js +57 -0
  133. package/dist/evasions/webgl.vendor/index.js.map +1 -0
  134. package/dist/evasions/webgl.vendor/package.json +5 -0
  135. package/dist/evasions/window.outerdimensions/README.md +17 -0
  136. package/dist/evasions/window.outerdimensions/index.d.ts +13 -0
  137. package/dist/evasions/window.outerdimensions/index.d.ts.map +1 -0
  138. package/dist/evasions/window.outerdimensions/index.js +42 -0
  139. package/dist/evasions/window.outerdimensions/index.js.map +1 -0
  140. package/dist/evasions/window.outerdimensions/package.json +5 -0
  141. package/dist/index.d.ts +130 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +164 -0
  144. package/dist/index.js.map +1 -0
  145. package/examples/detect-headless.js +95 -0
  146. package/examples/test1.js +20 -0
  147. package/examples/test2.js +25 -0
  148. package/package.json +70 -0
  149. package/src/evasions/README.md +13 -0
  150. package/src/evasions/_template/README.md +18 -0
  151. package/src/evasions/_template/index.js +26 -0
  152. package/src/evasions/_template/package.json +5 -0
  153. package/src/evasions/_utils/README.md +287 -0
  154. package/src/evasions/_utils/index.js +588 -0
  155. package/src/evasions/_utils/withUtils.js +47 -0
  156. package/src/evasions/chrome.app/README.md +16 -0
  157. package/src/evasions/chrome.app/index.js +97 -0
  158. package/src/evasions/chrome.app/package.json +5 -0
  159. package/src/evasions/chrome.csi/README.md +29 -0
  160. package/src/evasions/chrome.csi/index.js +69 -0
  161. package/src/evasions/chrome.csi/package.json +5 -0
  162. package/src/evasions/chrome.loadTimes/README.md +27 -0
  163. package/src/evasions/chrome.loadTimes/index.js +163 -0
  164. package/src/evasions/chrome.loadTimes/package.json +5 -0
  165. package/src/evasions/chrome.runtime/README.md +32 -0
  166. package/src/evasions/chrome.runtime/index.js +252 -0
  167. package/src/evasions/chrome.runtime/package.json +5 -0
  168. package/src/evasions/chrome.runtime/staticData.json +41 -0
  169. package/src/evasions/defaultArgs/README.md +17 -0
  170. package/src/evasions/defaultArgs/index.js +43 -0
  171. package/src/evasions/defaultArgs/package.json +5 -0
  172. package/src/evasions/iframe.contentWindow/README.md +19 -0
  173. package/src/evasions/iframe.contentWindow/index.js +132 -0
  174. package/src/evasions/iframe.contentWindow/package.json +5 -0
  175. package/src/evasions/media.codecs/README.md +38 -0
  176. package/src/evasions/media.codecs/index.js +89 -0
  177. package/src/evasions/media.codecs/package.json +5 -0
  178. package/src/evasions/navigator.hardwareConcurrency/README.md +19 -0
  179. package/src/evasions/navigator.hardwareConcurrency/index.js +45 -0
  180. package/src/evasions/navigator.hardwareConcurrency/package.json +5 -0
  181. package/src/evasions/navigator.languages/README.md +17 -0
  182. package/src/evasions/navigator.languages/index.js +44 -0
  183. package/src/evasions/navigator.languages/package.json +5 -0
  184. package/src/evasions/navigator.permissions/README.md +16 -0
  185. package/src/evasions/navigator.permissions/index.js +66 -0
  186. package/src/evasions/navigator.permissions/package.json +5 -0
  187. package/src/evasions/navigator.plugins/README.md +24 -0
  188. package/src/evasions/navigator.plugins/data.json +48 -0
  189. package/src/evasions/navigator.plugins/functionMocks.js +47 -0
  190. package/src/evasions/navigator.plugins/index.js +98 -0
  191. package/src/evasions/navigator.plugins/magicArray.js +145 -0
  192. package/src/evasions/navigator.plugins/mimeTypes.js +18 -0
  193. package/src/evasions/navigator.plugins/package.json +5 -0
  194. package/src/evasions/navigator.plugins/plugins.js +18 -0
  195. package/src/evasions/navigator.vendor/README.md +36 -0
  196. package/src/evasions/navigator.vendor/index.js +64 -0
  197. package/src/evasions/navigator.vendor/package.json +5 -0
  198. package/src/evasions/navigator.webdriver/README.md +17 -0
  199. package/src/evasions/navigator.webdriver/index.js +48 -0
  200. package/src/evasions/navigator.webdriver/package.json +5 -0
  201. package/src/evasions/sourceurl/README.md +17 -0
  202. package/src/evasions/sourceurl/_fixtures/test.html +35 -0
  203. package/src/evasions/sourceurl/index.js +82 -0
  204. package/src/evasions/sourceurl/package.json +5 -0
  205. package/src/evasions/user-agent-override/README.md +53 -0
  206. package/src/evasions/user-agent-override/index.js +206 -0
  207. package/src/evasions/user-agent-override/package.json +5 -0
  208. package/src/evasions/webgl.vendor/README.md +20 -0
  209. package/src/evasions/webgl.vendor/index.js +57 -0
  210. package/src/evasions/webgl.vendor/package.json +5 -0
  211. package/src/evasions/window.outerdimensions/README.md +17 -0
  212. package/src/evasions/window.outerdimensions/index.js +42 -0
  213. package/src/evasions/window.outerdimensions/package.json +5 -0
  214. package/src/index.d.ts +111 -0
  215. package/src/index.ts +188 -0
  216. package/test/cat-and-mouse.test.ts +170 -0
  217. package/test/evasions/_utils/index.test.ts +710 -0
  218. package/test/evasions/chrome.app/index.test.ts +69 -0
  219. package/test/evasions/chrome.csi/index.test.ts +46 -0
  220. package/test/evasions/chrome.loadTimes/index.test.ts +61 -0
  221. package/test/evasions/chrome.runtime/index.test.ts +282 -0
  222. package/test/evasions/defaultArgs/index.test.ts +36 -0
  223. package/test/evasions/iframe.contentWindow/index.test.js +450 -0
  224. package/test/evasions/media.codecs/index.test.js +103 -0
  225. package/test/evasions/navigator.hardwareConcurrency/index.test.js +58 -0
  226. package/test/evasions/navigator.languages/index.test.js +101 -0
  227. package/test/evasions/navigator.permissions/index.test.js +104 -0
  228. package/test/evasions/navigator.plugins/index.test.js +55 -0
  229. package/test/evasions/navigator.plugins/mimeTypes.test.js +220 -0
  230. package/test/evasions/navigator.plugins/plugins.test.js +181 -0
  231. package/test/evasions/navigator.vendor/index.test.js +68 -0
  232. package/test/evasions/navigator.webdriver/index.test.js +47 -0
  233. package/test/evasions/sourceurl/_fixtures/test.html +35 -0
  234. package/test/evasions/sourceurl/index.test.js +62 -0
  235. package/test/evasions/user-agent-override/index.test.js +338 -0
  236. package/test/evasions/webgl.vendor/index.test.js +220 -0
  237. package/test/fixtures/dummy-with-service-worker.html +22 -0
  238. package/test/fixtures/dummy.html +11 -0
  239. package/test/fixtures/sw.js +1 -0
  240. package/test/fpscanner.test.ts +54 -0
  241. package/test/index.test.ts +51 -0
  242. package/test/service-worker.test.ts +112 -0
  243. package/test/stealth/_results/_thumbs/headful-chrome-stealth.js.png +0 -0
  244. package/test/stealth/_results/_thumbs/headful-chrome-vanilla.js.png +0 -0
  245. package/test/stealth/_results/_thumbs/headful-chromium-stealth.js.png +0 -0
  246. package/test/stealth/_results/_thumbs/headful-chromium-vanilla.js.png +0 -0
  247. package/test/stealth/_results/_thumbs/headless-chrome-stealth.js.png +0 -0
  248. package/test/stealth/_results/_thumbs/headless-chrome-vanilla.js.png +0 -0
  249. package/test/stealth/_results/_thumbs/headless-chromium-stealth.js.png +0 -0
  250. package/test/stealth/_results/_thumbs/headless-chromium-vanilla.js.png +0 -0
  251. package/test/stealth/_results/headful-chrome-stealth.js.png +0 -0
  252. package/test/stealth/_results/headful-chrome-vanilla.js.png +0 -0
  253. package/test/stealth/_results/headful-chromium-stealth.js.png +0 -0
  254. package/test/stealth/_results/headful-chromium-vanilla.js.png +0 -0
  255. package/test/stealth/_results/headless-chrome-stealth.js.png +0 -0
  256. package/test/stealth/_results/headless-chrome-vanilla.js.png +0 -0
  257. package/test/stealth/_results/headless-chromium-stealth.js.png +0 -0
  258. package/test/stealth/_results/headless-chromium-vanilla.js.png +0 -0
  259. package/test/stealth/headful-chrome-stealth.js +25 -0
  260. package/test/stealth/headful-chrome-vanilla.js +23 -0
  261. package/test/stealth/headful-chromium-stealth.js +22 -0
  262. package/test/stealth/headful-chromium-vanilla.js +20 -0
  263. package/test/stealth/headless-chrome-stealth.js +25 -0
  264. package/test/stealth/headless-chrome-vanilla.js +23 -0
  265. package/test/stealth/headless-chromium-stealth.js +22 -0
  266. package/test/stealth/headless-chromium-vanilla.js +20 -0
  267. package/test/util.js +82 -0
  268. package/tsconfig.json +10 -0
  269. package/tsconfig.tsbuildinfo +1 -0
  270. 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
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }