#!/usr/bin/env python3 """Reference fingerprint-check harness for the 2026 ADI Lab benchmark. This is a sanitized reference implementation, not the exact internal build. It connects to a browser over CDP, runs a subset of the 47 checks, and writes JSONL records that aggregate.py can summarize. Requirements: playwright>=1.45 """ from __future__ import annotations import argparse import asyncio import json import statistics import time from pathlib import Path from playwright.async_api import async_playwright CHECKS = json.loads(Path(__file__).with_name("checks.json").read_text(encoding="utf-8"))["checks"] CHECK_KEYS = [c["key"] for c in CHECKS] COLLECT_JS = """ () => { const canvas = document.createElement('canvas'); canvas.width = 220; canvas.height = 60; const ctx = canvas.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillStyle = '#f60'; ctx.fillRect(0, 0, 220, 60); ctx.fillStyle = '#069'; ctx.fillText('ADI Lab fingerprint probe', 4, 4); const gl = document.createElement('canvas').getContext('webgl'); const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info'); return { ua: navigator.userAgent, platform: navigator.platform, languages: navigator.languages, language: navigator.language, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, screen: [screen.width, screen.height, screen.colorDepth, devicePixelRatio], viewport: [innerWidth, innerHeight], hardwareConcurrency: navigator.hardwareConcurrency, deviceMemory: navigator.deviceMemory || null, touch: navigator.maxTouchPoints || 0, webglVendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : null, webglRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null, canvasHash: canvas.toDataURL().slice(-64), webdriver: navigator.webdriver === true, chrome: typeof window.chrome !== 'undefined', plugins: Array.from(navigator.plugins || []).map(p => p.name), mimeTypes: Array.from(navigator.mimeTypes || []).map(m => m.type), }; } """ async def run_profile(playwright, profile: dict, timeout: float) -> dict: browser = await playwright.chromium.connect_over_cdp(profile["cdp_url"], timeout=timeout * 1000) started = time.perf_counter() context = browser.contexts[0] if browser.contexts else await browser.new_context() page = await context.new_page() await page.goto("about:blank", wait_until="domcontentloaded") data = await page.evaluate(COLLECT_JS) launch_ms = round((time.perf_counter() - started) * 1000) results = {} results["ua_string"] = bool(data["ua"]) results["platform_os"] = bool(data["platform"]) results["screen_resolution"] = all(isinstance(x, (int, float)) and x > 0 for x in data["screen"][:2]) results["device_pixel_ratio"] = data["screen"][3] > 0 results["timezone"] = bool(data["timezone"]) results["language"] = bool(data["language"]) results["languages"] = isinstance(data["languages"], list) and len(data["languages"]) > 0 results["webgl_vendor"] = bool(data["webglVendor"]) results["webgl_renderer"] = bool(data["webglRenderer"]) results["canvas_image"] = bool(data["canvasHash"]) results["hardware_concurrency"] = (data["hardwareConcurrency"] or 0) > 0 results["device_memory"] = data["deviceMemory"] is not None results["touch_support"] = data["touch"] >= 0 results["max_touch_points"] = data["touch"] >= 0 results["webdriver_flag"] = data["webdriver"] is False results["plugins"] = isinstance(data["plugins"], list) results["mime_types"] = isinstance(data["mimeTypes"], list) # Checks that require a proxy capture or a second profile are reported as # "not_run" in this reference build rather than silently passed. for key in CHECK_KEYS: results.setdefault(key, None) passed = sum(1 for v in results.values() if v is True) await context.close() await browser.close() return { "profile_id": profile["profile_id"], "browser": profile["browser"], "platform": profile.get("platform"), "proxy_country": profile.get("proxy_country"), "launch_ms": launch_ms, "checks_passed": passed, "full_pass": passed == len(CHECK_KEYS), "checks": results, } async def main_async(args) -> None: manifest = json.loads(Path(args.manifest).read_text(encoding="utf-8")) out = Path(args.out) out.parent.mkdir(parents=True, exist_ok=True) async with async_playwright() as playwright: semaphore = asyncio.Semaphore(args.workers) async def one(profile): async with semaphore: try: return await run_profile(playwright, profile, args.timeout) except Exception as exc: # noqa: BLE001 return {"profile_id": profile.get("profile_id"), "error": str(exc)} tasks = [one(profile) for profile in manifest] with out.open("w", encoding="utf-8") as handle: for coro in asyncio.as_completed(tasks): record = await coro handle.write(json.dumps(record, ensure_ascii=False) + "\n") print(f"Wrote {out}") def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--manifest", required=True) parser.add_argument("--out", required=True) parser.add_argument("--workers", type=int, default=4) parser.add_argument("--timeout", type=float, default=30.0) args = parser.parse_args() asyncio.run(main_async(args)) if __name__ == "__main__": main()