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

SignalCommon leakFix
Timezone vs IP geoDE proxy, America/New_York TZAlign profile TZ to proxy country
WebRTCLocal IP exposedDisable or class-B in profile settings
Client HintsUA mismatch with platformMimic engine + consistent OS preset
Canvas/WebGLRandom per launchEnable persistent noise in anti-detect
DNSResolver outside proxy geoProxy-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.