UNPKG

puppeteer-extra-plugin-stealth

Version:

Stealth mode: Applies various techniques to make detection of headless puppeteer harder.

223 lines (189 loc) 7.19 kB
const test = require('ava') const { getVanillaFingerPrint, getStealthFingerPrint } = require('../../test/util') const { vanillaPuppeteer, addExtra } = require('../../test/util') const Plugin = require('.') const { errors } = require('puppeteer') // FIXME: This changed in more recent chrome versions // test('vanilla: videoCard is Google Inc', async t => { // const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line // const { videoCard } = await getVanillaFingerPrint(pageFn) // t.deepEqual(videoCard, ['Google Inc.', 'Google SwiftShader']) // }) test('stealth: videoCard is Intel Inc', async t => { const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line const { videoCard } = await getStealthFingerPrint(Plugin, pageFn) t.deepEqual(videoCard, ['Intel Inc.', 'Intel Iris OpenGL Engine']) }) test('stealth: customized values', async t => { const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line const { videoCard } = await getStealthFingerPrint(Plugin, pageFn, { vendor: 'foo', renderer: 'bar' }) t.deepEqual(videoCard, ['foo', 'bar']) }) /* global WebGLRenderingContext */ async function extendedTests() { const results = {} async function test(name, fn) { const detectionPassed = await fn() if (detectionPassed) console.log(`Chrome headless detected via ${name}`) results[name] = detectionPassed } const canvas = document.createElement('canvas') const context = canvas.getContext('webgl') await test('descriptorsOK', _ => { const descriptors = Object.getOwnPropertyDescriptors( WebGLRenderingContext.prototype ) const str = descriptors.getParameter.toString() return str === `[object Object]` }) await test('toStringOK', _ => { const str = context.getParameter.toString() return str === `function getParameter() { [native code] }` }) await test('toStringOK2', _ => { const str = WebGLRenderingContext.prototype.getParameter.toString() return str === `function getParameter() { [native code] }` }) // Make sure we not reveal our proxy through errors await test('errorOK', _ => { try { return context.getParameter() } catch (err) { return !err.stack.includes(`at Object.apply`) } }) // Should not throw (that was old stealth behavior) await test('elementOK', _ => { try { return context.getParameter(123) === null } catch (_) { return false } }) return results } test('vanilla: webgl is native', async t => { const pageFn = async page => { // page.on('console', msg => { // console.log('Page console: ', msg.text()) // }) return await page.evaluate(extendedTests) // eslint-disable-line } const { pageFnResult: result } = await getVanillaFingerPrint(pageFn) const wasHeadlessDetected = Object.values(result).some(e => e === false) if (wasHeadlessDetected) { console.log(result) } t.false(wasHeadlessDetected) }) test('stealth: webgl is native', async t => { const pageFn = async page => await page.evaluate(extendedTests) // eslint-disable-line const { pageFnResult: result } = await getStealthFingerPrint(Plugin, pageFn) const wasHeadlessDetected = Object.values(result).some(e => e === false) if (wasHeadlessDetected) { console.log(result) } t.false(wasHeadlessDetected) }) /** * A very simple method to retrieve the name of the default videocard of the system * using webgl. * * Example (Apple Retina MBP 13): {vendor: "Intel Inc.", renderer: "Intel(R) Iris(TM) Graphics 6100"} * * @see https://stackoverflow.com/questions/49267764/how-to-get-the-video-card-driver-name-using-javascript-browser-side * @returns {Object} */ function getVideoCardInfo(context = 'webgl') { const gl = document.createElement('canvas').getContext(context) if (!gl) { return { error: 'no webgl' } } const debugInfo = gl.getExtension('WEBGL_debug_renderer_info') if (debugInfo) { return { vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL), renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) } } return { error: 'no WEBGL_debug_renderer_info' } } test('stealth: handles WebGLRenderingContext', async t => { const puppeteer = addExtra(vanillaPuppeteer).use(Plugin()) const browser = await puppeteer.launch({ headless: true }) const page = await browser.newPage() const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl') t.is(videoCardInfo.error, undefined) t.is(videoCardInfo.vendor, 'Intel Inc.') t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine') }) test('stealth: handles WebGL2RenderingContext', async t => { const puppeteer = addExtra(vanillaPuppeteer).use(Plugin()) const browser = await puppeteer.launch({ headless: true }) const page = await browser.newPage() const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl2') t.is(videoCardInfo.error, undefined) t.is(videoCardInfo.vendor, 'Intel Inc.') t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine') }) test('vanilla: normal toString stuff', async t => { const browser = await vanillaPuppeteer.launch({ headless: true }) const page = await browser.newPage() const test1 = await page.evaluate(() => { return WebGLRenderingContext.prototype.getParameter.toString + '' }) t.is(test1, 'function toString() { [native code] }') const test2 = await page.evaluate(() => { return WebGLRenderingContext.prototype.getParameter.toString() }) t.is(test2, 'function getParameter() { [native code] }') }) test('stealth: will not leak toString stuff', async t => { const puppeteer = addExtra(vanillaPuppeteer).use(Plugin()) const browser = await puppeteer.launch({ headless: true }) const page = await browser.newPage() const test1 = await page.evaluate(() => { return WebGLRenderingContext.prototype.getParameter.toString + '' }) t.is(test1, 'function toString() { [native code] }') // returns function () { [native code] } const test2 = await page.evaluate(() => { return WebGLRenderingContext.prototype.getParameter.toString() }) t.is(test2, 'function getParameter() { [native code] }') }) test('stealth: sets user opts correctly', async t => { const puppeteer = addExtra(vanillaPuppeteer).use( Plugin({ vendor: 'alice', renderer: 'bob' }) ) const browser = await puppeteer.launch({ headless: true }) const page = await browser.newPage() const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl') t.is(videoCardInfo.error, undefined) t.is(videoCardInfo.vendor, 'alice') t.is(videoCardInfo.renderer, 'bob') }) test('stealth: does not affect protoype', async t => { const puppeteer = addExtra(vanillaPuppeteer).use( Plugin({ vendor: 'alice', renderer: 'bob' }) ) const browser = await puppeteer.launch({ headless: true }) const page = await browser.newPage() const result = await page.evaluate(() => { try { return WebGLRenderingContext.prototype.getParameter(37445) } catch (err) { return err.message } }) t.is(result, 'Illegal invocation') })