Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

puppeteer-extra-plugin-stealth

Package Overview
Dependencies
Maintainers
1
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

puppeteer-extra-plugin-stealth - npm Package Compare versions

Comparing version 2.6.6 to 2.6.7

84

evasions/_utils/index.js

@@ -14,2 +14,6 @@ /**

utils.init = () => {
utils.preloadCache()
}
/**

@@ -27,3 +31,3 @@ * Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw.

traps.forEach(trap => {
newHandler[trap] = function() {
newHandler[trap] = function () {
try {

@@ -137,2 +141,5 @@ // Forward the call to the defined proxy handler

*
* Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,
* by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).
*
* This is evaluated once per execution context (e.g. window)

@@ -164,5 +171,2 @@ */

*
* Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,
* by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).
*
* @example

@@ -174,4 +178,2 @@ * makeNativeString('foobar') // => `function foobar() { [native code] }`

utils.makeNativeString = (name = '') => {
// Cache (per-window) the original native toString or use that if available
utils.preloadCache()
return utils.cache.nativeToStringStr.replace('toString', name || '')

@@ -195,6 +197,4 @@ }

utils.patchToString = (obj, str = '') => {
utils.preloadCache()
const toStringProxy = new Proxy(Function.prototype.toString, {
apply: function(target, ctx) {
const handler = {
apply: function (target, ctx) {
// This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`

@@ -220,3 +220,8 @@ if (ctx === Function.prototype.toString) {

}
})
}
const toStringProxy = new Proxy(
Function.prototype.toString,
utils.stripProxyFromErrors(handler)
)
utils.replaceProperty(Function.prototype, 'toString', {

@@ -243,6 +248,4 @@ value: toStringProxy

utils.redirectToString = (proxyObj, originalObj) => {
utils.preloadCache()
const toStringProxy = new Proxy(Function.prototype.toString, {
apply: function(target, ctx) {
const handler = {
apply: function (target, ctx) {
// This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`

@@ -276,3 +279,8 @@ if (ctx === Function.prototype.toString) {

}
})
}
const toStringProxy = new Proxy(
Function.prototype.toString,
utils.stripProxyFromErrors(handler)
)
utils.replaceProperty(Function.prototype, 'toString', {

@@ -297,3 +305,2 @@ value: toStringProxy

utils.replaceWithProxy = (obj, propName, handler) => {
utils.preloadCache()
const originalObj = obj[propName]

@@ -307,3 +314,23 @@ const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))

}
/**
* All-in-one method to replace a getter with a JS Proxy using the provided Proxy handler with traps.
*
* @example
* replaceGetterWithProxy(Object.getPrototypeOf(navigator), 'vendor', proxyHandler)
*
* @param {object} obj - The object which has the property to replace
* @param {string} propName - The name of the property to replace
* @param {object} handler - The JS Proxy handler to use
*/
utils.replaceGetterWithProxy = (obj, propName, handler) => {
const fn = Object.getOwnPropertyDescriptor(obj, propName).get
const fnStr = fn.toString() // special getter function string
const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))
utils.replaceProperty(obj, propName, { get: proxyObj })
utils.patchToString(proxyObj, fnStr)
return true
}
/**

@@ -323,3 +350,2 @@ * All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps.

utils.mockWithProxy = (obj, propName, pseudoTarget, handler) => {
utils.preloadCache()
const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))

@@ -347,3 +373,2 @@

utils.createProxy = (pseudoTarget, handler) => {
utils.preloadCache()
const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))

@@ -366,6 +391,3 @@ utils.patchToString(proxyObj)

// Remove last dot entry (property) ==> `HTMLMediaElement.prototype`
objName: objPath
.split('.')
.slice(0, -1)
.join('.'),
objName: objPath.split('.').slice(0, -1).join('.'),
// Extract last dot entry ==> `canPlayType`

@@ -467,2 +489,18 @@ propName: objPath.split('.').slice(-1)[0]

// Proxy handler templates for re-usability
utils.makeHandler = () => ({
// Used by simple `navigator` getter evasions
getterValue: value => ({
apply(target, ctx, args) {
// Let's fetch the value first, to trigger and escalate potential errors
// Illegal invocations like `navigator.__proto__.vendor` will throw here
const ret = utils.cache.Reflect.apply(...arguments)
if (args && args.length === 0) {
return value
}
return ret
}
})
})
// --

@@ -469,0 +507,0 @@ // Stuff starting below this line is NodeJS specific.

@@ -19,2 +19,3 @@ const test = require('ava')

test('makeNativeString: will do what it says', async t => {
utils.init()
t.is(utils.makeNativeString('bob'), 'function bob() { [native code] }')

@@ -227,7 +228,8 @@ t.is(

)}
- Function.prototype.valueOf.call(obj) + "": ${Function.prototype.valueOf.call(
obj
) + ''}
- obj.toString === Function.prototype.toString: ${obj.toString ===
Function.prototype.toString}
- Function.prototype.valueOf.call(obj) + "": ${
Function.prototype.valueOf.call(obj) + ''
}
- obj.toString === Function.prototype.toString: ${
obj.toString === Function.prototype.toString
}
`.trim()

@@ -237,3 +239,3 @@ }

test('patchToString: passes all toString tests', async t => {
const toStringVanilla = await (async function() {
const toStringVanilla = await (async function () {
const browser = await vanillaPuppeteer.launch({ headless: true })

@@ -243,3 +245,3 @@ const page = await browser.newPage()

})()
const toStringStealth = await (async function() {
const toStringStealth = await (async function () {
const browser = await vanillaPuppeteer.launch({ headless: true })

@@ -246,0 +248,0 @@ const page = await browser.newPage()

@@ -12,3 +12,3 @@ const utils = require('./index')

*/
evaluate: async function(mainFunction, ...args) {
evaluate: async function (mainFunction, ...args) {
return page.evaluate(

@@ -20,3 +20,3 @@ ({ _utilsFns, _mainFunction, _args }) => {

)
utils.preloadCache()
utils.init()
return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval

@@ -34,3 +34,3 @@ },

*/
evaluateOnNewDocument: async function(mainFunction, ...args) {
evaluateOnNewDocument: async function (mainFunction, ...args) {
return page.evaluateOnNewDocument(

@@ -42,3 +42,3 @@ ({ _utilsFns, _mainFunction, _args }) => {

)
utils.preloadCache()
utils.init()
return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval

@@ -45,0 +45,0 @@ },

@@ -5,2 +5,4 @@ 'use strict'

const withUtils = require('../_utils/withUtils')
/**

@@ -24,18 +26,26 @@ * Set the hardwareConcurrency to 4 (optionally configurable with `hardwareConcurrency`)

get defaults() {
return {
hardwareConcurrency: 4
}
}
async onPageCreated(page) {
await page.evaluateOnNewDocument(opts => {
Object.defineProperty(
Object.getPrototypeOf(navigator),
'hardwareConcurrency',
{
value: opts.hardwareConcurrency || 4,
writable: false
}
)
}, this.opts)
await withUtils(page).evaluateOnNewDocument(
(utils, { opts }) => {
utils.replaceGetterWithProxy(
Object.getPrototypeOf(navigator),
'hardwareConcurrency',
utils.makeHandler().getterValue(opts.hardwareConcurrency)
)
},
{
opts: this.opts
}
)
}
}
module.exports = function(pluginConfig) {
module.exports = function (pluginConfig) {
return new Plugin(pluginConfig)
}
const test = require('ava')
const os = require('os')
const { vanillaPuppeteer, addExtra } = require('../../test/util')
const {

@@ -12,3 +14,3 @@ getVanillaFingerPrint,

test('vanilla: navigator.hardwareConcurrency matches real core count', async t => {
test('vanilla: matches real core count', async t => {
const { pageFnResult } = await getVanillaFingerPrint(fingerprintFn)

@@ -18,3 +20,3 @@ t.is(pageFnResult, os.cpus().length)

test('stealth: navigator.hardwareConcurrency is set to 4', async t => {
test('stealth: default is set to 4', async t => {
const { pageFnResult } = await getStealthFingerPrint(Plugin, fingerprintFn)

@@ -24,3 +26,3 @@ t.is(pageFnResult, 4)

test('stealth: navigator.hardwareConcurrency customized value', async t => {
test('stealth: will override value correctly', async t => {
const { pageFnResult } = await getStealthFingerPrint(Plugin, fingerprintFn, {

@@ -31,1 +33,31 @@ hardwareConcurrency: 8

})
test('stealth: does patch getters properly', async t => {
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
const results = await page.evaluate(() => {
const hasInvocationError = (() => {
try {
// eslint-disable-next-line dot-notation
Object['seal'](Object.getPrototypeOf(navigator)['hardwareConcurrency'])
return false
} catch (err) {
return true
}
})()
return {
hasInvocationError,
toString: Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(navigator),
'hardwareConcurrency'
).get.toString()
}
})
t.deepEqual(results, {
hasInvocationError: true,
toString: 'function get hardwareConcurrency() { [native code] }'
})
})

@@ -21,14 +21,29 @@ 'use strict'

// Overwrite the `languages` property to use a custom getter.
get defaults() {
return {
languages: [] // Empty default, otherwise this would be merged with user defined array override
}
}
async onPageCreated(page) {
await withUtils(page).evaluateOnNewDocument((utils, opts) => {
utils.replaceProperty(Object.getPrototypeOf(navigator), 'languages', {
get: () => opts.languages || ['en-US', 'en']
})
}, this.opts)
await withUtils(page).evaluateOnNewDocument(
(utils, { opts }) => {
const languages = opts.languages.length
? opts.languages
: ['en-US', 'en']
utils.replaceGetterWithProxy(
Object.getPrototypeOf(navigator),
'languages',
utils.makeHandler().getterValue(Object.freeze([...languages]))
)
},
{
opts: this.opts
}
)
}
}
module.exports = function(pluginConfig) {
module.exports = function (pluginConfig) {
return new Plugin(pluginConfig)
}

@@ -62,1 +62,42 @@ const test = require('ava')

})
test('stealth: does patch getters properly', async t => {
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
const results = await page.evaluate(() => {
const hasInvocationError = (() => {
try {
// eslint-disable-next-line dot-notation
Object['seal'](Object.getPrototypeOf(navigator)['languages'])
return false
} catch (err) {
return true
}
})()
const hasPushError = (() => {
try {
// eslint-disable-next-line dot-notation
navigator.languages.push(null)
return false
} catch (err) {
return true
}
})()
return {
hasInvocationError,
hasPushError,
toString: Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(navigator),
'languages'
).get.toString()
}
})
t.deepEqual(results, {
hasInvocationError: true,
hasPushError: true,
toString: 'function get languages() { [native code] }'
})
})

@@ -5,2 +5,4 @@ 'use strict'

const withUtils = require('../_utils/withUtils')
/**

@@ -45,12 +47,18 @@ * By default puppeteer will have a fixed `navigator.vendor` property.

async onPageCreated(page) {
this.debug('onPageCreated - Will set these user agent options', {
this.debug('onPageCreated', {
opts: this.opts
})
await page.evaluateOnNewDocument(v => {
// Overwrite the `vendor` property to use a custom getter.
Object.defineProperty(Object.getPrototypeOf(navigator), 'vendor', {
get: () => v
})
}, this.opts.vendor || 'Google Inc.')
await withUtils(page).evaluateOnNewDocument(
(utils, { opts }) => {
utils.replaceGetterWithProxy(
Object.getPrototypeOf(navigator),
'vendor',
utils.makeHandler().getterValue(opts.vendor)
)
},
{
opts: this.opts
}
)
} // onPageCreated

@@ -57,0 +65,0 @@ }

@@ -11,3 +11,3 @@ const test = require('ava')

const vendor = await page.evaluate(() => navigator.vendor)
t.true(vendor === 'Google Inc.')
t.is(vendor, 'Google Inc.')
})

@@ -23,3 +23,3 @@

const vendor = await page.evaluate(() => navigator.vendor)
t.true(vendor === 'Apple Computer, Inc.')
t.is(vendor, 'Apple Computer, Inc.')
})

@@ -42,1 +42,31 @@

})
test('stealth: does patch getters properly', async t => {
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
const results = await page.evaluate(() => {
const hasInvocationError = (() => {
try {
// eslint-disable-next-line dot-notation
Object['seal'](Object.getPrototypeOf(navigator)['vendor'])
return false
} catch (err) {
return true
}
})()
return {
hasInvocationError,
toString: Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(navigator),
'vendor'
).get.toString()
}
})
t.deepEqual(results, {
hasInvocationError: true,
toString: 'function get vendor() { [native code] }'
})
})
{
"name": "puppeteer-extra-plugin-stealth",
"version": "2.6.6",
"version": "2.6.7",
"description": "Stealth mode: Applies various techniques to make detection of headless puppeteer harder.",

@@ -20,3 +20,3 @@ "main": "index.js",

"test": "run-p test:js lint",
"test-ci": "run-s test",
"test-ci": "run-s test:js",
"types": "npx --package typescript@3.7 tsc --emitDeclarationOnly --declaration --allowJs index.js"

@@ -58,3 +58,3 @@ },

},
"gitHead": "0abcb1e7084d02e499dd979dc6f07b78940e301c"
"gitHead": "f184a797a4de14ec2383fc0cb2957b1ac451e4da"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc