diff --git a/research/creepjs-probe.ts b/research/creepjs-probe.ts deleted file mode 100644 index a770c1d..0000000 --- a/research/creepjs-probe.ts +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env bun -// CreepJS evaluation of the managed BetterChromium fork. -// -// Launches the fork through the normal BetterWright stack (guard proxy, -// launch identity, no OS masquerade) and opens abrahamjuliot.github.io/creepjs, -// then scrapes the trust score, "lies" totals, and section text, and saves a -// screenshot of the verdict area. -// -// Usage: -// xvfb-run -a bun research/creepjs-probe.ts # headed under Xvfb -// bun research/creepjs-probe.ts --headless # headless - -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { BetterWright } from "../dist/src/index.js"; - -const args = process.argv.slice(2); -const HEADLESS = args.includes("--headless"); -// --no-gpu simulates a GPU-less host: no NVIDIA EGL/GLX forcing and no GPU -// launch flags, so Chromium falls back to its software rasterizer. Default -// (omitted) targets a host with a real GPU. -const NO_GPU = args.includes("--no-gpu"); -// --software forces the software rasterizer even on a GPU host, to exercise -// the fork's GPU-less spoof path under a display that does have a GPU. -const FORCE_SOFTWARE = args.includes("--software"); - -// Force the NVIDIA EGL/GLX vendor so Chromium's GL context binds to the RTX -// 4090 instead of Mesa's llvmpipe software rasterizer under the X display. -if (!NO_GPU) { - process.env.__EGL_VENDOR_LIBRARY_FILENAMES = - "/usr/share/glvnd/egl_vendor.d/10_nvidia.json"; - process.env.__GLX_VENDOR_LIBRARY_NAME = "nvidia"; -} - -const home = fs.mkdtempSync(path.join(os.tmpdir(), "bw-creepjs-")); -const browser = new BetterWright({ - home, - headless: HEADLESS, - launchIdentity: true, - defaultTimeout: 90, - // On a GPU host, bind the real GPU via EGL. On a GPU-less host (--no-gpu) - // pass nothing and let the fork's software-renderer path run. - chromiumArgs: FORCE_SOFTWARE - ? ["--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] - : NO_GPU - ? [] - : [ - "--enable-gpu", - "--use-gl=angle", - "--use-angle=gl-egl", - "--enable-webgl", - "--ignore-gpu-blocklist", - "--enable-gpu-rasterization", - ], -}); - -try { - const result = await browser.run( - `await page.goto("https://abrahamjuliot.github.io/creepjs/", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // Give CreepJS's staged computation time to fill in; the page sometimes - // never finishes on a blocked network, so don't gate on its verdict. - await page.waitForTimeout(60000); - await page.evaluate(() => window.scrollTo(0, 0)); - const shot = await screenshot({ kind: "debug", fullPage: true }); - return await page.evaluate((shotPath) => { - const body = document.body ? document.body.innerText : ""; - // Pull the headless/automation section's individual check rows. CreepJS - // renders each detector as a labelled row; the "like headless" figure is - // a weighted blend of these, so we need the per-check truth values. - const headlessSection = [...document.querySelectorAll("*")] - .filter((el) => { - const cls = String(el.className || ""); - return (cls.includes("headless") || cls.includes("stealth") || cls.includes("automation")) && el.children.length; - }) - .map((el) => (el.innerText || "").trim()) - .filter(Boolean) - .slice(0, 10); - // Automation tells CreepJS looks for across the whole document. - const lower = body.toLowerCase(); - const tells = {}; - for (const probe of ["webdriver", "cdc_", "__webdriver", "domautomation", "phantom", "selenium"]) { - const idx = lower.indexOf(probe); - if (idx !== -1) tells[probe] = body.slice(Math.max(0, idx - 20), idx + 40).split(String.fromCharCode(10)).join(" ").trim(); - } - return { - title: document.title, - bodyLength: body.length, - screenshotPath: shotPath, - webdriver: navigator.webdriver, - plugins: navigator.plugins.length, - languages: navigator.languages, - hasChrome: Boolean(window.chrome), - webglRenderer: (() => { - try { - const c = document.createElement("canvas"); - const gl = c.getContext("webgl") || c.getContext("experimental-webgl"); - if (!gl) return "no-context"; - const ext = gl.getExtension("WEBGL_debug_renderer_info"); - return ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER); - } catch (e) { return "error:" + e.message; } - })(), - headlessSection, - tells, - detectors: { - webdriver: navigator.webdriver === true, - // The 16 likeHeadless booleans from CreepJS's headless module, each - // reproduced so the rating math is transparent. - noChrome: !("chrome" in window), - noPlugins: navigator.plugins.length === 0, - noMimeTypes: Object.keys({ ...navigator.mimeTypes }).length === 0, - notificationIsDenied: ("Notification" in window) && Notification.permission === "denied", - prefersLightColor: matchMedia("(prefers-color-scheme: light)").matches, - pdfIsDisabled: ("pdfViewerEnabled" in navigator) && navigator.pdfViewerEnabled === false, - noTaskbar: screen.height === screen.availHeight && screen.width === screen.availWidth, - hasVvpScreenRes: - (window.innerWidth === screen.width && window.outerHeight === screen.height) || - (("visualViewport" in window) && visualViewport.width === screen.width && visualViewport.height === screen.height), - noWebShare: !("share" in navigator) || !("canShare" in navigator), - noContentIndex: !("ContentIndexProvider" in window), - noContactsManager: !("contacts" in navigator) && !("ContactsManager" in window), - hasKnownBgColor: (() => { - const el = document.createElement("div"); - el.setAttribute("style", "background-color: ActiveText"); - document.body.appendChild(el); - const bg = getComputedStyle(el).backgroundColor; - document.body.removeChild(el); - return bg === "rgb(255, 0, 0)"; - })(), - screenVsOuter: [screen.width, screen.height, screen.availWidth, screen.availHeight], - outerDimensions: [window.outerWidth, window.outerHeight], - innerDimensions: [window.innerWidth, window.innerHeight], - devicePixelRatio: window.devicePixelRatio, - connectionRtt: (navigator.connection && navigator.connection.rtt), - downlinkMax: (navigator.connection && navigator.connection.downlinkMax), - hardwareConcurrency: navigator.hardwareConcurrency, - deviceMemory: navigator.deviceMemory, - notificationPerm: (typeof Notification !== "undefined" ? Notification.permission : "n/a"), - }, - fullBody: body, - }; - }, shot.path);`, - { timeout: 180 }, - ); - if (!result?.ok) { - console.error("run failed:", JSON.stringify(result)?.slice(0, 800)); - process.exitCode = 1; - } else { - const { fullBody, ...rest } = result.result || {}; - console.log(JSON.stringify(rest, null, 2)); - console.log("=== full body ==="); - console.log(fullBody); - // The artifact lives under the per-run home (removed below); copy it out. - if (result.result?.screenshotPath) { - const target = "/tmp/bw-creepjs/creepjs.png"; - fs.mkdirSync("/tmp/bw-creepjs", { recursive: true }); - fs.copyFileSync(result.result.screenshotPath, target); - console.log("screenshot copied to:", target); - } - } -} finally { - await browser.close().catch(() => {}); - fs.rmSync(home, { recursive: true, force: true }); -} diff --git a/research/headless-tells.ts b/research/headless-tells.ts deleted file mode 100644 index 3ba2e31..0000000 --- a/research/headless-tells.ts +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bun -// Headless-tell diagnostic: dump the exact browser signals CreepJS's -// headless-likeness heuristic reads, so we can see which ones are flagging. -// -// xvfb-run -a bun research/headless-tells.ts - -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { BetterWright } from "../dist/src/index.js"; - -const home = fs.mkdtempSync(path.join(os.tmpdir(), "bw-tells-")); -const browser = new BetterWright({ home, headless: false, defaultTimeout: 60 }); - -const TELLS = ` -const out = {}; -const nav = navigator; -out["navigator.webdriver"] = nav.webdriver; -out["window.chrome present"] = !!window.chrome; -out["window.chrome.csi"] = !!(window.chrome && window.chrome.csi); -out["window.chrome.loadTimes"] = !!(window.chrome && window.chrome.loadTimes); -out["navigator.plugins.length"] = nav.plugins.length; -out["navigator.plugins names"] = [...nav.plugins].map((p) => p.name); -out["navigator.languages"] = nav.languages; -out["navigator.hardwareConcurrency"] = nav.hardwareConcurrency; -out["navigator.deviceMemory"] = nav.deviceMemory; -out["navigator.platform"] = nav.platform; -out["navigator.vendor"] = nav.vendor; -out["navigator.maxTouchPoints"] = nav.maxTouchPoints; -out["navigator.pdfViewerEnabled"] = nav.pdfViewerEnabled; -out["window.outerWidth/innerWidth"] = window.outerWidth + "/" + window.innerWidth; -out["window.outerHeight/innerHeight"] = window.outerHeight + "/" + window.innerHeight; -out["screen.colorDepth"] = screen.colorDepth; -out["Notification.permission"] = (typeof Notification !== "undefined") ? Notification.permission : "n/a"; -// WebGL renderer / vendor — the biggest headless tell of all. -try { - const canvas = document.createElement("canvas"); - const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); - if (gl) { - const dbg = gl.getExtension("WEBGL_debug_renderer_info"); - out["webgl UNMASKED_RENDERER"] = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : "no dbg ext"; - out["webgl UNMASKED_VENDOR"] = dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : "no dbg ext"; - out["webgl RENDERER"] = gl.getParameter(gl.RENDERER); - out["webgl VENDOR"] = gl.getParameter(gl.VENDOR); - } else { - out["webgl"] = "context null"; - } -} catch (error) { - out["webgl"] = "error: " + error.message; -} -// Headless-only tells CreepJS probes. -out["navigator.connection.rtt"] = nav.connection ? nav.connection.rtt : "n/a"; -out["chrome.runtime"] = !!(window.chrome && window.chrome.runtime); -out["Function.prototype.toString proxy leak"] = /\\{\\s*\\[native code\\]\\s*\\}/.test(Function.prototype.toString.toString()); -// Permissions API quirk: headless used to resolve notifications as denied-instantly. -try { - const perm = await navigator.permissions.query({ name: "notifications" }); - out["permissions.notifications.state"] = perm.state; -} catch (error) { - out["permissions.notifications"] = "error: " + error.message; -} -// Plugins/mimetypes headless absence. -out["navigator.mimeTypes.length"] = nav.mimeTypes.length; -// UA-CH. -try { - const uaData = await nav.userAgentData.getHighEntropyValues(["platform", "platformVersion", "model", "uaFullVersion"]); - out["userAgentData"] = JSON.stringify(uaData); -} catch (error) { - out["userAgentData"] = "error: " + error.message; -} -return out; -`; - -try { - const result = await browser.run( - `await page.goto("https://bot.sannysoft.com/", { waitUntil: "load", timeout: 30000 }).catch(() => {}); - return await page.evaluate(async () => { ${TELLS} });`, - { timeout: 90 }, - ); - if (!result?.ok) { - console.error("run failed:", JSON.stringify(result)?.slice(0, 600)); - process.exitCode = 1; - } else { - for (const [key, value] of Object.entries(result.result)) { - console.log(`${key.padEnd(42)} ${JSON.stringify(value)}`); - } - } -} finally { - await browser.close().catch(() => {}); - fs.rmSync(home, { recursive: true, force: true }); -} diff --git a/research/pixelscan-probe.ts b/research/pixelscan-probe.ts deleted file mode 100644 index c1c0c8a..0000000 --- a/research/pixelscan-probe.ts +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bun -// PixelScan evaluation of the managed BetterChromium fork. -// -// Launches the fork through the normal BetterWright stack and opens -// pixelscan.net, waits for the scan to compute, then dumps the full report -// text plus the raw consistency signals PixelScan cross-references, so the -// exact "inconsistent fingerprint" drivers are visible. -// -// Usage: -// DISPLAY=:99 bun research/pixelscan-probe.ts # headed on Xorg -// bun research/pixelscan-probe.ts --headless # headless - -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { BetterWright } from "../dist/src/index.js"; - -const args = process.argv.slice(2); -const HEADLESS = args.includes("--headless"); -const NO_GPU = args.includes("--no-gpu"); - -if (!NO_GPU) { - process.env.__EGL_VENDOR_LIBRARY_FILENAMES = - "/usr/share/glvnd/egl_vendor.d/10_nvidia.json"; - process.env.__GLX_VENDOR_LIBRARY_NAME = "nvidia"; -} - -const home = fs.mkdtempSync(path.join(os.tmpdir(), "bw-pixelscan-")); -const browser = new BetterWright({ - home, - headless: HEADLESS, - launchIdentity: true, - defaultTimeout: 90, - chromiumArgs: NO_GPU - ? [] - : [ - "--enable-gpu", - "--use-gl=angle", - "--use-angle=gl-egl", - "--enable-webgl", - "--ignore-gpu-blocklist", - "--enable-gpu-rasterization", - ], -}); - -try { - const result = await browser.run( - `await page.goto("https://pixelscan.net/", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // PixelScan runs its checks automatically on load; give it time to finish. - await page.waitForTimeout(20000); - // Try to expand any "details"/"show more" controls so the full report text - // is in the DOM, then let any revealed content settle. - try { - const buttons = await page.$$("button, a, [role=button]"); - for (const b of buttons) { - const t = ((await b.innerText().catch(() => "")) || "").toLowerCase(); - if (/detail|more|expand|show|why|inconsist/.test(t)) { - await b.click().catch(() => {}); - } - } - } catch {} - await page.waitForTimeout(4000); - await page.evaluate(() => window.scrollTo(0, 0)); - const shot = await screenshot({ kind: "debug", fullPage: true }); - return await page.evaluate((shotPath) => { - const text = (el) => (el ? (el.innerText || "").trim() : ""); - const body = document.body ? document.body.innerText : ""; - // Collect likely verdict/flag elements by scanning for status keywords. - const flagged = []; - const kw = /inconsist|suspicious|fail|mask|mismatch|leak|bot|automat|headless|anomal|risk|warn/i; - for (const el of document.querySelectorAll("*")) { - if (el.children.length === 0) { - const t = text(el); - if (t && t.length < 200 && kw.test(t)) flagged.push(t); - } - } - const lower = body.toLowerCase(); - const verdict = /\\b(inconsistent|consistent|suspicious|trustworthy|bot|human|masked|natural)\\b/.test(lower) - ? (body.match(/[^\\n]*(inconsistent|consistent|suspicious|trustworthy|masked|natural)[^\\n]*/i) || [""])[0] - : ""; - return { - title: document.title, - url: location.href, - screenshotPath: shotPath, - verdictLine: verdict.trim().slice(0, 300), - // Raw signals PixelScan cross-references for consistency. - signals: { - userAgent: navigator.userAgent, - platform: navigator.platform, - uaDataPlatform: (navigator.userAgentData && navigator.userAgentData.platform) || null, - uaDataMobile: (navigator.userAgentData && navigator.userAgentData.mobile) || null, - uaDataBrands: (navigator.userAgentData && navigator.userAgentData.brands) || null, - webdriver: navigator.webdriver, - languages: navigator.languages, - language: navigator.language, - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - timezoneOffset: new Date().getTimezoneOffset(), - hardwareConcurrency: navigator.hardwareConcurrency, - deviceMemory: navigator.deviceMemory, - screen: [screen.width, screen.height], - avail: [screen.availWidth, screen.availHeight], - outer: [window.outerWidth, window.outerHeight], - inner: [window.innerWidth, window.innerHeight], - devicePixelRatio: window.devicePixelRatio, - colorScheme: matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light", - touchPoints: navigator.maxTouchPoints, - webglVendor: (() => { - try { - const gl = document.createElement("canvas").getContext("webgl"); - if (!gl) return "no-context"; - const ext = gl.getExtension("WEBGL_debug_renderer_info"); - return ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR); - } catch (e) { return "error:" + e.message; } - })(), - webglRenderer: (() => { - try { - const gl = document.createElement("canvas").getContext("webgl"); - if (!gl) return "no-context"; - const ext = gl.getExtension("WEBGL_debug_renderer_info"); - return ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER); - } catch (e) { return "error:" + e.message; } - })(), - }, - flagged: [...new Set(flagged)].slice(0, 60), - fullBody: body, - }; - }, shot.path);`, - { timeout: 120 }, - ); - if (!result?.ok) { - console.error("run failed:", JSON.stringify(result)?.slice(0, 800)); - process.exitCode = 1; - } else { - const { fullBody, ...rest } = result.result || {}; - console.log(JSON.stringify(rest, null, 2)); - console.log("=== full body ==="); - console.log(fullBody); - if (result.result?.screenshotPath) { - const target = "/tmp/bw-pixelscan/pixelscan.png"; - fs.mkdirSync("/tmp/bw-pixelscan", { recursive: true }); - fs.copyFileSync(result.result.screenshotPath, target); - console.log("screenshot copied to:", target); - } - } -} finally { - await browser.close().catch(() => {}); - fs.rmSync(home, { recursive: true, force: true }); -} diff --git a/tsconfig.harness.json b/tsconfig.harness.json index e52d64c..1703f32 100644 --- a/tsconfig.harness.json +++ b/tsconfig.harness.json @@ -37,9 +37,6 @@ }, "include": [ "research/cdpfree-probe.ts", - "research/creepjs-probe.ts", - "research/headless-tells.ts", - "research/pixelscan-probe.ts", "research/stealth-report.ts", "research/warm-profile.ts", "research/verify-local-ai-catalog.ts",