Your browser leaks identifying information every time you visit a website. Even in incognito mode.
CreepJS exposes these leaks by analyzing your browser's digital fingerprint. This open-source tool reveals exactly what tracking scripts can detect about your device, browser, and system configuration.
In this guide, you'll learn how to use CreepJS effectively for fingerprint analysis, testing browser automation tools, and validating anti-detection setups.
What is CreepJS and Why Use It?
CreepJS is an open-source browser fingerprinting analysis tool that detects privacy leaks in anti-fingerprinting extensions and browsers. It probes JavaScript APIs, Canvas rendering, WebGL parameters, and audio fingerprints to create unique browser identifiers. CreepJS helps developers test automation tools like Puppeteer, Playwright, and Selenium by revealing which browser attributes expose bot activity to anti-scraping systems.
Browser fingerprinting collects dozens of data points about your system. Screen resolution, installed fonts, GPU model, timezone, language settings—these combine into a unique ID.
CreepJS makes this process visible. It shows exactly which APIs leak information and how detectable your browser appears to tracking systems.
Understanding CreepJS Fingerprinting Techniques
CreepJS doesn't just collect basic browser data. It implements sophisticated detection methods that reveal even stealth automation tools.
Canvas Fingerprinting
Canvas fingerprinting draws invisible graphics using HTML5 Canvas API. Different hardware and drivers render pixels slightly differently.
CreepJS captures this rendering output and creates a hash. Two identical browsers on different machines produce different hashes due to hardware variations.
This technique exposes:
- Graphics card model
- Driver version differences
- Operating system rendering variations
- Anti-aliasing implementation
WebGL Fingerprinting
WebGL accesses your GPU directly for 3D rendering. It reveals detailed hardware information that's nearly impossible to spoof convincingly.
CreepJS extracts WebGL parameters including GPU vendor, renderer model, and supported extensions. These details remain consistent across sessions.
Headless browsers often use software rendering instead of hardware acceleration. This creates detectable inconsistencies.
Audio Context Fingerprinting
The Web Audio API processes sound differently on each device. Hardware variations, audio drivers, and DSP implementations all affect output.
CreepJS generates audio signals and measures processing characteristics. The results form another unique identifier.
This works even when no actual sound plays. The processing happens entirely in memory.
Navigator Object Analysis
The navigator object exposes browser and system details through JavaScript. CreepJS reads dozens of properties from this object.
Key properties include:
- User agent string
- Platform and OS
- Hardware concurrency (CPU cores)
- Device memory
- WebDriver presence flag
Automation tools often modify these properties incorrectly. CreepJS detects inconsistencies between claimed values and actual behavior.
Setting Up CreepJS for Local Testing
You can test CreepJS three ways: using the live demo, running it locally, or integrating it into automation scripts.
Using the Live Demo
The simplest method is visiting the official CreepJS site at https://abrahamjuliot.github.io/creepjs/.
Wait 5-10 seconds for all tests to complete. The page displays fingerprint data, trust scores, and detected anomalies.
This works for quick checks but doesn't integrate with automation tools.
Installing CreepJS Locally
Clone the GitHub repository to run CreepJS on your own server:
git clone https://github.com/abrahamjuliot/creepjs.git
cd creepjs
pnpm install
pnpm build:dev
pnpm start
The local server runs on localhost:8000. You can now test against your own CreepJS instance.
Local hosting lets you modify detection rules or add custom tests. It also works offline for development environments.
Running CreepJS in Node.js
For programmatic testing, you can import CreepJS as a module:
npm install creepjs
However, CreepJS primarily runs in browser environments. Server-side usage requires a headless browser to execute the JavaScript.
How to Use CreepJS with Browser Automation Tools
Testing automation tools with CreepJS reveals which browser properties leak information. Each tool has different detection signatures.
The standard workflow:
- Launch automated browser
- Navigate to CreepJS test page
- Wait for tests to complete
- Capture results screenshot
- Analyze trust scores and detected lies
Let's implement this for each major automation framework.
Testing Puppeteer with CreepJS
Puppeteer controls Chrome or Chromium through the DevTools Protocol. Without modifications, it's easily detected by CreepJS.
Basic Puppeteer Test
Install Puppeteer first:
npm install puppeteer
Here's a basic test script:
const puppeteer = require('puppeteer');
async function testCreepJS() {
const browser = await puppeteer.launch({
headless: false
});
const page = await browser.newPage();
await page.goto('https://abrahamjuliot.github.io/creepjs/');
// Wait for tests to complete
await page.waitForTimeout(10000);
// Take screenshot
await page.screenshot({
path: 'puppeteer-basic.png',
fullPage: true
});
await browser.close();
}
testCreepJS();
This script launches Chromium, navigates to CreepJS, and captures results after 10 seconds.
The screenshot will show 100% headless detection and multiple red flags. Puppeteer's default configuration is highly detectable.
Adding Stealth Plugin
The puppeteer-extra-plugin-stealth improves detection evasion significantly:
npm install puppeteer-extra puppeteer-extra-plugin-stealth
Updated script with stealth:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function testCreepJSStealth() {
const browser = await puppeteer.launch({
headless: false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-blink-features=AutomationControlled'
]
});
const page = await browser.newPage();
// Set realistic viewport
await page.setViewport({
width: 1920,
height: 1080
});
await page.goto('https://abrahamjuliot.github.io/creepjs/', {
waitUntil: 'networkidle2'
});
await page.waitForTimeout(10000);
await page.screenshot({
path: 'puppeteer-stealth.png',
fullPage: true
});
await browser.close();
}
testCreepJSStealth();
The stealth plugin patches dozens of detection vectors. It removes the navigator.webdriver flag, fixes Chrome runtime properties, and normalizes permission APIs.
Results improve dramatically. Headless detection drops from 100% to around 30-40%.
Testing Playwright with CreepJS
Playwright supports Chromium, Firefox, and WebKit. Its fingerprint differs from Puppeteer's but is still detectable without modifications.
Basic Playwright Implementation
Install Playwright:
pip install playwright
playwright install chromium
Python test script:
from playwright.sync_api import sync_playwright
import time
def test_creepjs_playwright():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080}
)
page = context.new_page()
page.goto('https://abrahamjuliot.github.io/creepjs/')
time.sleep(10)
page.screenshot(path='playwright-basic.png', full_page=True)
browser.close()
test_creepjs_playwright()
This script produces similar detection rates as basic Puppeteer. CreepJS identifies Playwright through automation flags and inconsistent property values.
Using Playwright-Stealth
The playwright-stealth library applies evasion techniques:
pip install playwright-stealth
Enhanced script:
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
import time
def test_creepjs_stealth():
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
args=[
'--disable-blink-features=AutomationControlled',
'--disable-dev-shm-usage'
]
)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
)
page = context.new_page()
stealth_sync(page)
page.goto('https://abrahamjuliot.github.io/creepjs/')
time.sleep(10)
page.screenshot(path='playwright-stealth.png', full_page=True)
browser.close()
test_creepjs_stealth()
The stealth_sync function patches WebDriver flags, Chrome runtime, and navigator properties before page load.
Detection rates drop but don't disappear entirely. Advanced analysis still reveals automation signatures.
Testing Selenium with CreepJS
Selenium is the oldest browser automation framework. It's also the most detectable without proper configuration.
Standard Selenium Test
Install dependencies:
pip install selenium
Download ChromeDriver matching your Chrome version from the official site.
Basic test:
from selenium import webdriver
import time
def test_creepjs_selenium():
driver = webdriver.Chrome()
driver.get('https://abrahamjuliot.github.io/creepjs/')
time.sleep(10)
driver.save_screenshot('selenium-basic.png')
driver.quit()
test_creepjs_selenium()
CreepJS immediately detects Selenium. The navigator.webdriver property is set to true, and multiple Chrome DevTools flags expose automation.
Results show 100% headless detection with extensive red warnings.
Undetected ChromeDriver
The undetected-chromedriver package specifically targets detection bypass:
pip install undetected-chromedriver
Improved implementation:
import undetected_chromedriver as uc
import time
def test_creepjs_undetected():
options = uc.ChromeOptions()
options.add_argument('--disable-blink-features=AutomationControlled')
driver = uc.Chrome(options=options, use_subprocess=True)
driver.get('https://abrahamjuliot.github.io/creepjs/')
time.sleep(10)
driver.save_screenshot('selenium-undetected.png')
driver.quit()
test_creepjs_undetected()
This package applies extensive patches including:
- Removing webdriver flags
- Modifying Chrome DevTools protocol
- Normalizing navigator properties
- Fixing permission API responses
Detection drops significantly. Many sites that block standard Selenium allow undetected-chromedriver through.
Analyzing CreepJS Results and Trust Scores
Understanding CreepJS output helps identify which fingerprint components need improvement.
Trust Score Calculation
CreepJS assigns a trust score from 0-100%. Higher scores indicate more natural browser fingerprints.
The score considers:
- Consistency between claimed and actual properties
- Detection of JavaScript tampering
- Presence of automation flags
- Unusual API responses
- Fingerprint uniqueness
Scores above 90% suggest a convincing human-like fingerprint. Below 50% indicates obvious automation or spoofing.
Lie Detection
CreepJS highlights "lies" when detected values don't match claimed values.
Common lies include:
- User agent claims Windows but APIs reveal Linux
- Headless flag present despite browser window visible
- Canvas rendering inconsistent with claimed GPU
- Screen dimensions don't match reported values
Each lie increases suspicion and lowers trust scores.
Crowd Blending Score
This metric compares your fingerprint against CreepJS's database of known fingerprints.
Highly unique fingerprints stand out as unusual. Common fingerprints blend with the crowd.
Perfect spoofing requires looking common, not unique. Avoid rare configurations that draw attention.
Advanced Fingerprint Spoofing Techniques
Basic stealth plugins help but don't eliminate detection. Advanced techniques require deeper modifications.
Custom User Agent Rotation
Different browsers have distinct fingerprint signatures. Rotating user agents creates variety but requires matching all related properties.
Example configuration:
const profiles = [
{
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
platform: 'Win32',
vendor: 'Google Inc.',
deviceMemory: 8,
hardwareConcurrency: 4
},
{
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
platform: 'MacIntel',
vendor: 'Apple Computer, Inc.',
deviceMemory: 8,
hardwareConcurrency: 8
}
];
function applyProfile(page, profile) {
await page.setUserAgent(profile.userAgent);
await page.evaluateOnNewDocument((profile) => {
Object.defineProperty(navigator, 'platform', {
get: () => profile.platform
});
Object.defineProperty(navigator, 'vendor', {
get: () => profile.vendor
});
Object.defineProperty(navigator, 'deviceMemory', {
get: () => profile.deviceMemory
});
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => profile.hardwareConcurrency
});
}, profile);
}
This ensures all properties match the claimed user agent. Inconsistencies between user agent and actual properties trigger lie detection.
Canvas Noise Injection
Adding subtle noise to Canvas output makes fingerprints less consistent while maintaining visual accuracy:
await page.evaluateOnNewDocument(() => {
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
HTMLCanvasElement.prototype.toDataURL = function(...args) {
const data = originalToDataURL.apply(this, args);
// Add minimal random variation
return data.slice(0, -5) + Math.random().toString(36).substr(2, 5);
};
CanvasRenderingContext2D.prototype.getImageData = function(...args) {
const imageData = originalGetImageData.apply(this, args);
// Modify 0.1% of pixels randomly
for (let i = 0; i < imageData.data.length; i += 40) {
if (Math.random() < 0.1) {
imageData.data[i] += Math.floor(Math.random() * 3) - 1;
}
}
return imageData;
};
});
The noise is invisible to human eyes but prevents exact fingerprint matching across sessions.
WebGL Parameter Spoofing
Changing WebGL parameters requires consistency with the claimed GPU model:
await page.evaluateOnNewDocument(() => {
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// UNMASKED_VENDOR_WEBGL
if (parameter === 37445) {
return 'Intel Inc.';
}
// UNMASKED_RENDERER_WEBGL
if (parameter === 37446) {
return 'Intel(R) UHD Graphics 620';
}
return getParameter.call(this, parameter);
};
});
Match these values to realistic GPU configurations. Claiming NVIDIA but having Intel rendering characteristics creates obvious lies.
Timezone and Geolocation Alignment
Your timezone should match your claimed location. Mismatches appear suspicious:
const config = {
timezone: 'America/New_York',
latitude: 40.7128,
longitude: -74.0060
};
await page.evaluateOnNewDocument((config) => {
// Override timezone
const originalDateTimeFormat = Intl.DateTimeFormat;
Intl.DateTimeFormat = function(...args) {
if (!args[1] || !args[1].timeZone) {
args[1] = args[1] || {};
args[1].timeZone = config.timezone;
}
return new originalDateTimeFormat(...args);
};
// Override geolocation
const originalGetCurrentPosition = navigator.geolocation.getCurrentPosition;
navigator.geolocation.getCurrentPosition = function(success) {
success({
coords: {
latitude: config.latitude,
longitude: config.longitude,
accuracy: 10
}
});
};
}, config);
await page.setGeolocation(config);
Consistent location data across all APIs strengthens the fingerprint's believability.
Integrating Proxies for Complete Anonymity
Fingerprint spoofing handles browser-level detection. Proxies handle network-level identification.
Why Proxies Matter with CreepJS
CreepJS performs WebRTC leak tests that can expose your real IP address even behind VPNs. Proper proxy configuration prevents these leaks.
Network fingerprinting also examines:
- IP address location
- TCP/IP stack signatures
- HTTP header ordering
- TLS fingerprints
Proxies provide different IP addresses and network characteristics for each session.
Residential Proxy Configuration
Residential proxies use real residential IP addresses from ISPs. They appear as genuine home internet connections.
For fingerprint testing and bypassing, residential proxies from Roundproxies.com offer:
- Rotating residential IPs
- Geographic targeting
- HTTP/HTTPS support
- SOCKS5 protocol support
Integration with Puppeteer:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function testWithProxy() {
const proxyServer = 'residential.roundproxies.com:12345';
const proxyUsername = 'your-username';
const proxyPassword = 'your-password';
const browser = await puppeteer.launch({
headless: false,
args: [
`--proxy-server=${proxyServer}`,
'--no-sandbox'
]
});
const page = await browser.newPage();
// Authenticate proxy
await page.authenticate({
username: proxyUsername,
password: proxyPassword
});
await page.goto('https://abrahamjuliot.github.io/creepjs/');
await page.waitForTimeout(10000);
await page.screenshot({
path: 'creepjs-with-proxy.png',
fullPage: true
});
await browser.close();
}
testWithProxy();
The proxy provides a legitimate residential IP address. Combined with fingerprint spoofing, this creates a convincing human-like signature.
Datacenter vs Residential Proxies
Datacenter proxies use IP addresses from data centers. They're faster and cheaper but more easily detected.
CreepJS doesn't directly test proxy type. However, sites using comprehensive fingerprinting often maintain databases of datacenter IP ranges.
For maximum authenticity during CreepJS testing:
- Use residential proxies for realistic network fingerprints
- Rotate IPs between test sessions
- Match proxy location to timezone and geolocation settings
Roundproxies.com offers both residential and datacenter proxies. Residential proxies work best for fingerprint testing because they match typical user network profiles.
Mobile Proxies for Mobile Fingerprints
Testing mobile browser fingerprints requires mobile-specific network signatures. Mobile proxies route traffic through real mobile devices on carrier networks.
Mobile proxy setup:
const mobileProxy = 'mobile.roundproxies.com:12345';
const browser = await puppeteer.launch({
args: [`--proxy-server=${mobileProxy}`]
});
const page = await browser.newPage();
// Mobile user agent
await page.setUserAgent(
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15'
);
// Mobile viewport
await page.setViewport({
width: 375,
height: 812,
isMobile: true,
hasTouch: true
});
Mobile networks have different TCP/IP signatures than residential broadband. Using mobile proxies ensures network-level consistency with mobile browser fingerprints.
Common CreepJS Detection Patterns
Understanding what CreepJS detects helps you avoid these patterns.
Automation Flags
The most obvious detection vector is the navigator.webdriver flag. Modern browsers set this to true when controlled by automation.
Basic stealth plugins remove this flag. However, removing it creates its own pattern—Chrome 89+ should have this property even when false.
The solution is setting it to false rather than removing it entirely.
Inconsistent Navigator Properties
Properties like navigator.platform, navigator.userAgent, and navigator.vendor must align logically.
Wrong:
navigator.platform = 'Win32'
navigator.userAgent = '...Macintosh; Intel Mac OS X...'
This obvious lie triggers immediate detection. Platform should be MacIntel for macOS user agents.
Chrome Runtime Leaks
Automation tools add properties to window.chrome that don't exist in normal browsers. CreepJS checks for these.
Proper stealth plugins normalize the chrome object structure to match genuine Chrome installations.
Permission API Responses
Automated browsers handle permission requests differently than normal browsers. CreepJS tests permissions for notifications, geolocation, and other APIs.
Spoofing must implement realistic permission behaviors matching the claimed browser configuration.
Troubleshooting Failed Fingerprint Tests
Even with stealth plugins, some configurations still fail CreepJS tests. Here's how to diagnose and fix issues.
High Headless Detection
If CreepJS reports high headless percentage despite using stealth plugins:
- Verify plugin installation and initialization
- Check for outdated plugin versions
- Review browser launch arguments
- Test with
headless: falseto confirm detection sources
Some detection vectors only appear in headless mode. Testing with visible browser helps isolate issues.
Low Trust Score
Trust scores below 50% indicate multiple problems:
- Check for property inconsistencies (lies)
- Verify user agent matches all related properties
- Ensure timezone aligns with geolocation
- Review canvas and WebGL configurations
Address each detected lie individually. Fixing lies systematically improves the trust score.
WebRTC Leaks
WebRTC can expose your real IP even when using proxies. CreepJS tests for these leaks.
Disable WebRTC entirely if not needed:
await page.goto('about:config');
await page.evaluate(() => {
// Disable WebRTC
RTCPeerConnection = undefined;
RTCDataChannel = undefined;
});
Alternatively, configure WebRTC to only use proxy IPs through browser extensions or launch arguments.
Canvas Fingerprint Uniqueness
Highly unique canvas fingerprints indicate hardware-specific rendering that's difficult to spoof.
Solutions:
- Use canvas noise injection
- Test on different hardware configurations
- Implement software rendering modes
The goal is consistency, not randomness. Random canvas values change on each load, which itself appears suspicious.
Building Custom CreepJS Testing Scripts
For production use, build automated testing pipelines that regularly validate fingerprint configurations.
Automated Test Suite
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const fs = require('fs');
puppeteer.use(StealthPlugin());
async function runCreepJSTest(config) {
const browser = await puppeteer.launch({
headless: false,
args: [
`--proxy-server=${config.proxy}`,
'--no-sandbox',
'--disable-setuid-sandbox'
]
});
const page = await browser.newPage();
if (config.proxyAuth) {
await page.authenticate(config.proxyAuth);
}
// Apply custom fingerprint
await applyFingerprint(page, config.fingerprint);
await page.goto('https://abrahamjuliot.github.io/creepjs/', {
waitUntil: 'networkidle2'
});
await page.waitForTimeout(12000);
// Extract results
const results = await page.evaluate(() => {
const trustScore = document.querySelector('.trust-score');
const lies = Array.from(document.querySelectorAll('.lies'));
return {
trustScore: trustScore ? trustScore.textContent : 'N/A',
liesDetected: lies.length,
timestamp: new Date().toISOString()
};
});
// Save screenshot
await page.screenshot({
path: `test-${config.name}-${Date.now()}.png`,
fullPage: true
});
await browser.close();
return results;
}
async function applyFingerprint(page, fingerprint) {
await page.setUserAgent(fingerprint.userAgent);
await page.setViewport(fingerprint.viewport);
await page.setGeolocation(fingerprint.geolocation);
await page.evaluateOnNewDocument((fp) => {
// Apply all custom properties
Object.defineProperty(navigator, 'platform', {
get: () => fp.platform
});
Object.defineProperty(navigator, 'deviceMemory', {
get: () => fp.deviceMemory
});
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => fp.hardwareConcurrency
});
}, fingerprint);
}
// Run tests
const testConfigs = [
{
name: 'windows-chrome',
proxy: 'residential.roundproxies.com:12345',
proxyAuth: { username: 'user', password: 'pass' },
fingerprint: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
platform: 'Win32',
deviceMemory: 8,
hardwareConcurrency: 4,
viewport: { width: 1920, height: 1080 },
geolocation: { latitude: 40.7128, longitude: -74.0060 }
}
},
// Add more configurations
];
async function runAllTests() {
const results = [];
for (const config of testConfigs) {
console.log(`Testing configuration: ${config.name}`);
const result = await runCreepJSTest(config);
results.push({ config: config.name, ...result });
}
// Save results to file
fs.writeFileSync(
'creepjs-test-results.json',
JSON.stringify(results, null, 2)
);
console.log('All tests complete. Results saved.');
}
runAllTests();
This script tests multiple fingerprint configurations automatically and saves results for analysis.
Continuous Integration Testing
Integrate CreepJS testing into CI/CD pipelines:
# .github/workflows/fingerprint-test.yml
name: Fingerprint Test
on:
schedule:
- cron: '0 0 * * *' # Daily
push:
branches: [main]
jobs:
test-fingerprints:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Run CreepJS tests
run: node test-creepjs.js
- name: Upload results
uses: actions/upload-artifact@v2
with:
name: creepjs-results
path: creepjs-test-results.json
Regular testing catches fingerprint degradation when browser versions update or detection methods evolve.
Final Thoughts
CreepJS reveals the extensive data your browser exposes to fingerprinting scripts. Testing with CreepJS helps validate privacy setups and anti-detection configurations.
Key takeaways:
Browser fingerprinting combines dozens of data points into unique identifiers. CreepJS makes this process visible.
Basic automation tools are immediately detected. Stealth plugins significantly improve results but don't eliminate detection entirely.
Advanced spoofing requires consistency across all browser properties. Inconsistencies between claimed and actual values trigger lie detection.
Combining fingerprint spoofing with quality proxies from Roundproxies.com creates more convincing browser signatures.
Regular testing catches configuration drift as browsers and detection methods evolve.
Use CreepJS as a diagnostic tool to identify and fix fingerprint leaks. The goal is blending with common fingerprints, not creating unique ones.