Manual fingerprint checklists fail at scale. This script launches a Multilogin profile, collects browser signals in-page, and diffs against a profile manifest — run nightly or pre-deploy in CI. Catches proxy geo drift, timezone leaks, and WebRTC exposure before platforms do.
Profile manifest (expected values)
{
"profile_id": "uuid-here",
"timezone": "Europe/Berlin",
"languages": ["de-DE", "de"],
"platform": "Win32",
"webgl_vendor": "Google Inc.",
"proxy_geo": "DE",
"webrtc_policy": "disable"
}
Collect script (runs inside profile)
COLLECT_JS = """
() => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');
return {
userAgent: navigator.userAgent,
platform: navigator.platform,
languages: [...navigator.languages],
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
webglVendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : null,
webglRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
canvasHash: canvas.toDataURL().slice(-32)
};
}
"""
Python audit runner
import json
import sys
import httpx
from playwright.sync_api import sync_playwright
MLX = "https://api.multilogin.com"
TOKEN = "Bearer ..."
PROFILE_ID = "uuid"
MANIFEST = json.load(open("profiles/acme_de.json"))
def start_profile():
r = httpx.post(f"{MLX}/profile/start",
json={"profile_id": PROFILE_ID, "headless": False},
headers={"Authorization": TOKEN}, timeout=90)
r.raise_for_status()
return r.json()["cdp_url"]
def stop_profile():
httpx.post(f"{MLX}/profile/stop",
json={"profile_id": PROFILE_ID},
headers={"Authorization": TOKEN})
def audit():
cdp = start_profile()
errors = []
try:
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(cdp, timeout=60_000)
page = browser.contexts[0].pages[0]
page.goto("about:blank")
actual = page.evaluate(COLLECT_JS)
if actual["timezone"] != MANIFEST["timezone"]:
errors.append(f"timezone: {actual['timezone']} != {MANIFEST['timezone']}")
if MANIFEST["languages"][0] not in actual["languages"]:
errors.append(f"languages: {actual['languages']}")
if actual["platform"] != MANIFEST["platform"]:
errors.append(f"platform leak: {actual['platform']}")
# optional: fetch egress IP via test page
page.goto("https://api.ipify.org?format=json", wait_until="domcontentloaded")
ip_data = page.evaluate("() => document.body.innerText")
# compare ip geo to MANIFEST["proxy_geo"] via your geo-IP service
browser.close()
finally:
stop_profile()
if errors:
print("FINGERPRINT AUDIT FAILED:", errors, file=sys.stderr)
sys.exit(1)
print("audit ok")
if __name__ == "__main__":
audit()
Signals to gate in CI
| Signal | Common leak | Fix |
|---|---|---|
| Timezone vs IP geo | DE proxy, America/New_York TZ | Align profile TZ to proxy country |
| WebRTC | Local IP exposed | Disable or class-B in profile settings |
| Client Hints | UA mismatch with platform | Mimic engine + consistent OS preset |
| Canvas/WebGL | Random per launch | Enable persistent noise in anti-detect |
| DNS | Resolver outside proxy geo | Proxy-side DNS or profile DNS policy |
Integrate with pre-launch checklist
Run this script as step 4 in the pre-launch checklist before enabling automation on a new profile tier. Pair with manual review on fingerprint deep dive for TLS/JA3 (requires external tools).
Related
Disclosure: MLX-MMO affiliated with Multilogin.