CreepJS is an open-source project designed to demonstrate vulnerabilities and leaks in extensions or browsers that users use to avoid being fingerprinted. It’s one of the newest projects in the browser fingerprinting scene, and it uses an advanced combination of techniques such as JavaScript tampering detection and finding inconsistencies between the detected user agent and the expected feature set.
In this tutorial, we’ll see how the most popular headless browsers stack up against each other in an all-out battle to pass CreepJS’s “Headless” and “Stealth” detection scores.
TL;DR: The winner is Camoufox
How Does CreepJS Work?
Like most browser fingerprinting tools, CreepJS first collects data about the browser and OS environment using JavaScript. It only differs from similar tools in the sheer comprehensiveness of the data it collects. It uses hashing algorithms on the collected data to anonymize it and produce standard-length strings that can be easily stored and compared. These hashes are then used to create unique fingerprints for the user’s device. This fingerprint is used for further analysis, such as associating certain fingerprint characters with browser characteristics, such as automation and privacy protections. Finally, it renders the result in a neat, comprehensive web page containing various sections. A partial screenshot of this page is shown below; you can try the tool by visiting its URL .
Bypassing CreepJS: Why and How?
Though the project author did not intend for CreepJS to be used as a fingerprinting solution, it might be deployed to detect browser automation tools such as Puppeteer, Selenium, and Playwright. For scraping data from the web, we’d be interested in observing how our setup is perceived by fingerprinting tools such as CreepJS and how we can bypass fingerprint-based anti-bot measures. This exercise can be useful with websites that do not deploy CreepJS for bot detection too. Even if the website uses other techniques to detect bot activity, we can use CreepJS to benchmark the effectiveness of our browser fingerprint spoofing.
🤖 If you want more on how to circumvent anti-scraping technology, check out our ultimate guide on How to scrape a website without getting blocked
In this tutorial, we’ll be looking at how CreepJS perceives commonly used browser automation tools such as Playwright, Selenium, Puppeteer, and some patched versions of these tools which are supposed to avoid being detected. For each of these tools, we’ll check how much we’re able to spoof CreepJS. Specifically, we’ll be aiming to get 0% on the ‘headless’ and 'stealth’ scores in the Headless section of the results. A screenshot of this section for a regular human-operated browser is shown below:
NOTE: We’ll be running all the tests in ‘headless’ mode, unless otherwise specified. This is to ensure our code can run on a server, without a monitor setup.
Bypassing CreepJS With Playwright
Playwright is an open-source browser automation tool developed by Microsoft. It provides a unified API to control Chromium, WebKit, and Firefox, with support for multiple programming languages such as Python, Node.js, Java, and .NET. For spoofing to hide the browser fingerprint, we’ll attempt to use a Playwright patch, Patchright . Patchright is a drop-in replacement for Playwright that applies some tweaks to the default Playwright launch configuration to make it undetectable. We’ll compare the scores for regular Playwright with the patch.
Installation Steps
For this tutorial, we’ll be using Playwright with Python and Chromium. We can complete the required installation steps as follows:
$ pip install playwright
$ playwright install chromium
We’ll install Patchright for Python as follows:
$ pip install patchright
$ patchright install chromium
Test Code
For each scenario, we’ll be visiting the CreepJS result page with the automated browser, taking a screenshot of the whole page, and saving it to disk. The code snippets for the two scenarios are below:
Regular Playwright:
from playwright.sync_api import sync_playwright
import time
URL = 'https://abrahamjuliot.github.io/creepjs/'
with sync_playwright() as p:
# Open Browser and Page
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Visit the CreepJS URL
page.goto(URL)
# Wait for 10 Seconds and Capture the Page
time.sleep(10)
el = page.locator('#headless-resistance-detection-results>div:first-child')
el.screenshot(path="pw-regular.png")
# Close the Browser
browser.close()
Patchright:
from patchright.sync_api import sync_playwright
import time
URL = 'https://abrahamjuliot.github.io/creepjs/'
with sync_playwright() as p:
# Open Browser and Page
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Visit the CreepJS URL
page.goto(URL)
# Wait for 10 Seconds and Capture the Page
time.sleep(10)
el = page.locator('#headless-resistance-detection-results>div:first-child')
el.screenshot(path="pw-patchright.png")
# Close the Browser
browser.close()
Results
From the CreepJS headless scores above, we can see that regular Playwright is detected as 100% headless while using Patchright brings it down to 67%. The stealth score stays at 0% in both cases. Further, we were able to use Patchright as a drop-in replacement for Playwright, with minimal changes to the code. Ideally, we would want to get 0% headless, but this setup could work in some cases where the checks aren’t very robust.
Playwright is a hugely popular web scraping framework with lots of amazing features, check out these guides to become a Playwright expert:
- Scrapy Playwright Tutorial: How to Scrape Dynamic Websites
- Scraping the web with Playwright in Javascript
- Playwright for Python Web Scraping Tutorial with Examples
Bypassing CreepJS With Selenium
Selenium is the classic browser automation toolkit that dates way back to 2004. It can be used in a wide variety of programming languages, including Python. CreepJS is capable of detecting Selenium too. Therefore, it is important to look into how we can bypass this detection. In this section, we’ll test CreepJS scores with default Selenium and a patched version known as undetected_chromedriver .
Installation Steps
We can install Selenium for Python as follows:
$ pip install selenium
Similarly, we can install undetected_chromdriver too:
$ pip install undetected-chromedriver
Test Code
Regular Selenium Setup:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import time
# Start Driver in Headless Mode
options = Options()
options.add_argument('--headless=new')
driver = webdriver.Chrome(options=options)
URL = 'https://abrahamjuliot.github.io/creepjs/'
# Visit the Page
driver.get(URL)
# Wait for 10 Seconds and Capture Results
time.sleep(10)
el = driver.find_element(
By.CSS_SELECTOR,
'#headless-resistance-detection-results>div:first-child',
)
el.screenshot("sl-default.png")
Undetected Chromedriver Setup:
import undetected_chromedriver as webdriver
from undetected_chromedriver.options import ChromeOptions as Options
from selenium.webdriver.common.by import By
import time
# Start Driver in Headless Mode
options = Options()
options.add_argument('--headless=new')
driver = webdriver.Chrome(options=options)
URL = 'https://abrahamjuliot.github.io/creepjs/'
# Visit the Page
driver.get(URL)
# Wait for 10 Seconds and Capture Results
time.sleep(10)
el = driver.find_element(
By.CSS_SELECTOR,
'#headless-resistance-detection-results>div:first-child',
)
el.screenshot("sl-uc.png")
Results
We observe from the scores that CreepJS can detect regular Selenium as a headless browser with a score of 100%, while we’re able to bring the score down to 67% using the undetected_chromedriver patch. We only changed 2 lines of code! The results are similar to Playwright and we could use this patch for websites with limited bot detection capabilities.
Selenium is a staple for web scraping professionals everywhere, to learn more on how to use it and its array of features check out these guides:
- Web Scraping Tutorial Using Selenium & Python (+ examples)
- How To Set Up A Rotating Proxy in Selenium with Python
- Getting Started with RSelenium
- How to use undetected_chromedriver (plus working alternatives)
- Scraping with Nodriver: Step-by-Step Tutorial with Examples
Bypassing CreepJS With Puppeteer
Puppeteer is Google’s open-source browser automation framework. Its initial release predates that of Playwright by a good number of years. While Playwright supports multiple browsers and programming languages, Puppeteer officially supports only JavaScript and is primarily focused on Chrome (though Firefox is also supported). There is an unofficial Python port of Puppeteer called Pyppeteer . However, for this tutorial, we’ll be using the official NodeJS library with all the code in JavaScript because the core library and the patches that we will be using are more actively maintained for JavaScript. The patch we will be trying out here is puppeteer-extra-plugin-stealth , which is in fact a plugin for puppeteer-extra . The latter is a drop-in replacement for Puppeteer, which simply augments it with the ability to add plugins.
Installation Steps
Assuming we have NodeJS and NPM installed, we can proceed to install Puppeteer as follows:
$ npm install puppeteer
Next, we’ll install the libraries needed for the patch:
$ npm install puppeteer-extra
$ npm install puppeteer-extra-plugin-stealth
Test Code
Regular Puppeteer:
const puppeteer = require("puppeteer");
// define sleep function for waiting
const sleep = (s) => new Promise(resolve => setTimeout(resolve, 1000*s));
async function main() {
let URL = 'https://abrahamjuliot.github.io/creepjs/'
// Open Browser and Page
const browser = await puppeteer.launch({
headless: true,
executablePath: '/usr/bin/chromium',
});
let page = await browser.newPage();
// Visit the CreepJS URL
await page.goto(URL, {
waitUntil: "domcontentloaded",
});
// Wait for 10 Seconds and Capture Result
await sleep(10);
const el = await page.waitForSelector('#headless-resistance-detection-results>div:first-child');
await el.screenshot({ path: "pp-default.png" });
// Close Browser
await page.close();
await browser.close();
}
main();
Puppeteer Extra Plugin Stealth:
// Use Puppeteer Extra Instead of Regular Version
const puppeteer = require("puppeteer-extra");
// Apply the Stealth Plugin
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
// define sleep function for waiting
const sleep = (s) => new Promise(resolve => setTimeout(resolve, 1000*s));
async function main() {
let URL = 'https://abrahamjuliot.github.io/creepjs/'
// Open Browser and Page
const browser = await puppeteer.launch({
headless: true,
executablePath: '/usr/bin/chromium',
});
let page = await browser.newPage();
// Visit the CreepJS URL
await page.goto(URL, {
waitUntil: "domcontentloaded",
});
// Wait for 10 Seconds and Capture Result
await sleep(10);
const el = await page.waitForSelector('#headless-resistance-detection-results>div:first-child');
await el.screenshot({ path: "pp-extra.png" });
// Close Browser
await page.close();
await browser.close();
}
main();
Results
CreepJS detects a regular Puppeteer setup as 100% headless, while with the puppeteer-extra-stealth-plugin, we’re able to bring it down to 33%, which is better than what we could achieve for Playwright and Selenium. However, the downside is that CreepJS detects the patch as 80% stealth. This would not be completely effective either, but it might work on websites that don’t check for ‘stealth’.
If you want to learn more about Puppeteer, check out our plethora of tutorials on it:
- Web Scraping with JavaScript, Node.js
- Puppeteer Stealth Tutorial; How to Set Up & Use (+ Working Alternatives)
- Pyppeteer: the Puppeteer for Python Developers
Bypassing CreepJS With Nodriver
Nodriver is an actively developed web automation framework for Python that claims to be the successor of the undetected_chromedriver that we used earlier on. It works independently of Selenium and Webdriver, and it claims to provide better resistance against bot-detection software. Its API is fully asynchronous, allowing us to do multitasking easily. Let’s see how this fares against CreepJS.
Installation Steps
Install it easily using pip
:
$ pip install nodriver
Test Code
import nodriver as uc
import time
URL = 'https://abrahamjuliot.github.io/creepjs/'
async def main():
# Open Browser and Page
browser = await uc.start(browser_args=['--headless=new'])
page = await browser.get(URL)
# Wait for 10 Seconds and Capture Result
time.sleep(10)
el = await page.select('#headless-resistance-detection-results>div:first-child')
await el.save_screenshot(filename="nodriver.png")
if __name__ == '__main__':
uc.loop().run_until_complete(main())
Results
With Nodriver running in headless mode, we’re able to achieve a headless score of 67%. The score is the same as undetected_chromdriver. However, the developer claims a performance boost, and since there is no selenium or webdriver involved, we can also expect faster fixes against popular anit-bot tools.
Bypassing CreepJS In Headful Mode Using Virtual Displays
So far, we looked at bypassing CreepJS using various tools and patches, but we ran all the tests in headless mode. Operating a browser in headless mode enables us to run scrapers on the servers; at scale and unattended. However, we were not able to achieve a 0% headless score with any of the patches we saw above. As a seemingly last resort, we would have to try running the tools in a windowed (headful) mode. Luckily, we have another tool in our arsenal: virtual display. Using a virtual display, we can simulate an additional display device and have our browser open there (in headful mode) and do its thing, while all of this is hidden from the PC monitor. It also works on cloud servers and VPS environments, where there is no monitor connected. The only downside is that it could be more resource-intensive to run our scrapers this way, compared to headless mode.
NOTE: The libraries that we’ll be using will work only on Linux systems.
Installation Steps
First, we need to install the core virtual display library. We’ll be using xvfb , which stands for X-Virtual Framebuffer. It can be installed using the OS package manager on most Linux distributions. For example, on Ubuntu 24.04 we can install it as follows:
$ sudo apt install xvfb
We can ensure it has been installed, using the xvfb-run
command:
$ xvfb-run --help
Run COMMAND (usually an X client) in a virtual X server environment.
Options:
...
Next, let’s install the Python and NodeJS wrappers for xvfb. For Python, we’ll be using the PyVirtualDisplay package, while the Node package is called xvfb itself.
pip install pyvirtualdisplay
npm install xvbf
Test Code
Patchright
from pyvirtualdisplay import Display
from patchright.sync_api import sync_playwright
import time
# Run Code Within a Virtual Display Context
with Display() as disp:
URL = 'https://abrahamjuliot.github.io/creepjs/'
with sync_playwright() as p:
# Open Browser In Headful Mode
browser = p.chromium.launch(headless=True)
'''
REST OF THE CODE IS SAME AS IN PREVIOUS SECTION
'''
Undetected Chromedriver
from pyvirtualdisplay import Display
import undetected_chromedriver as webdriver
from undetected_chromedriver.options import ChromeOptions as Options
from selenium.webdriver.common.by import By
import time
with Display() as disp:
# Start The Driver Without Headless Options
driver = webdriver.Chrome()
'''
REST OF THE CODE IS SAME AS IN PREVIOUS SECTION
'''
Nodriver
from pyvirtualdisplay import Display
import nodriver as uc
import time
with Display() as disp:
URL = 'https://abrahamjuliot.github.io/creepjs/'
async def main():
# Open Browser Without Headless Options
browser = await uc.start()
'''
CODE FROM PREVIOUS SECTION
'''
uc.loop().run_until_complete(main())
Puppeteer Extra Stealth Plugin
const puppeteer = require("puppeteer-extra");
const Xvfb = require('xvfb');
const xvfb = new Xvfb();
//
// CODE FROM PREVIOUS SECTION
//
async function main() {
// Start Virtual Display
xvfb.startSync();
let URL = 'https://abrahamjuliot.github.io/creepjs/'
// Open Browser In Headful Mode
const browser = await puppeteer.launch({
headless: false,
executablePath: '/usr/bin/chromium',
});
//
// CODE FROM PREVIOUS SECTION
//
xvfb.stopSync();
}
main();
Results
Using a virtual display and running headful mode, we’re able to get a 0% headless score on all the techniques we tried earlier. The stealth scores are zero, except for puppeteer-extra-plugin-stealth which is detected as 80% stealth.
Bypassing CreepJS With Camoufox
Camoufox is a custom build of Firefox meant for web scraping. It is mostly open-source and claims to be stealthy and minimal. It bundles several advanced anti-bot evasion techniques such as fingerprint spoofing and stealth patches. It also handles the virtual display technique we discussed earlier internally.
While we mostly used Chrome/Chromium in the earlier methods, Camoufox uses Firefox citing multiple reasons, such as Firefox being open-source and easier to patch over the closed-source Chrome, and most anti-bot tools being designed to target Chromium-based bots. Some advanced features in Camoufox such as canvas fingerprint rotation have been kept closed-source with the intention to prevent them from being reverse-engineered by anti-bot tool providers.
For this test, we’ll be using the Python interface provided by Camoufox. This serves as a drop-in replacement for Playwright. We’ll be testing it in headless mode, as well as in headful mode using a virtual display.
Installation Steps
Let’s install the Camoufox python package and download the browser using its fetch command:
$ pip install camoufox
$ python -m camoufox fetch
Test Code
from camoufox.sync_api import Camoufox
import time
URL = 'https://abrahamjuliot.github.io/creepjs/'
with Camoufox(headless=True) as browser:
# To Use Virtual Display:
# pass headless='virtual' above instead of headless=True
# Open Page
page = browser.new_page()
# Visit the CreepJS URL
page.goto(URL)
# Wait for 10 Seconds and Capture Result
time.sleep(10)
el = page.locator('#headless-resistance-detection-results>div:first-child')
el.screenshot(path="pw-camoufox.png")
Results
Using Camoufox, we’re finally able to achieve 0% headless and stealth scores, in headless mode. Even the ‘like headless’ score is at 6%, which is significantly less than all the other methods we explored earlier. Using a virtual display, we’re able to send it further down to 0%.
Summary of Results
Tool/Patch | Headless Mode | Like Headless | Headless | Stealth |
---|---|---|---|---|
Playwright | Headless | 88% | 100% | 0% |
Patchright | Headless | 88% | 67% | 0% |
Headful, Virtual Display | 44% | 0% | 0% | |
Selenium | Headless | 44% | 100% | 0% |
Undetected Chromedriver | Headless | 44% | 67% | 0% |
Headful, Virtual Display | 44% | 0% | 0% | |
Puppeteer | Headless | 50% | 100% | 0% |
Puppeteer Extra Stealth Plugin | Headless | 44% | 33% | 80% |
Headful, Virtual Display | 44% | 0% | 80% | |
Nodriver | Headless | 44% | 67% | 0% |
Headful, Virtual Display | 44% | 0% | 0% | |
Camoufox | Headless | 6% | 0% | 0% |
Headful, Virtual Display | 0% | 0% | 0% |
Other Browser Fingerprinting Tools
At the time of writing, CreepJS seems to be the most comprehensive open-source browser fingerprinting tool, which is why we discussed bypassing it in detail. However, there are other open-source (and commercial, closed-source) browser fingerprinting solutions available. FingerprintJS and Broprint.js are two such source-available tools worth mentioning. Let’s briefly discuss them below.
FingerprintJS
FingerprintJS is a source-available browser fingerprinting library maintained by a commercial venture, Fingerprint . It positions itself as an easy-to-use fingerprinting library, primarily focused on identifying individual users, even between regular and incognito/private browsing sessions. Bot detection seems like a minor offering, and their blog has a description of their bot detection solution .
Broprint.js
Broprint.js is an open-source project that describes itself as “the world's easiest, smallest, and powerful visitor identifier for browsers.” It uses a combination of data from the canvas and audio features of the browser, which is a subset of the features used by CreepJS. It does not seem to be focused on bot detection, as there are no mentions of words such as "bot,” "headless,” or “stealth” on the project page or issues board on GitHub.
Conclusion
In the ongoing cat-and-mouse game between web scraping and anti-bot measures, CreepJS is one of the most comprehensive browser fingerprinting solutions that can detect headless browsers. In this tutorial, we tried out some popular browser automation tools and patches to check if CreepJS is able to detect them. Independent of the framework or the patch, we were unable to bring down the headless scores to 0% while running Chrome/Chromium in headless mode. With regard to stealth score, we were detected as stealth only with the puppeteer-extra-stealth-plugin. We could bring the headless score to 0% only while running in headful mode while using virtual displays as a workaround; or by using Camoufox. Camoufox is a Firefox-based browser custom-built for web scraping. Using Camoufox, the headless and stealth scores were 0%, with both headless and virtual display modes.
While CreepJS is not primarily intended by the project authors to be used as an anti-bot measure, being able to bypass it still provides us a valuable benchmark for our scraping setup. We can then use a tested setup against other anti-bot tools that use browser environment features to detect bots, by fingerprinting or other means.