feat: buildGStackLaunchArgs — Pack 1 cmdline-switch construction for gbrowser

New stealth.ts export that turns the GSTACK_* env vars (already populated
by gbrowser's gbd from host_profile.go) into the --gstack-* cmdline
switches the Pack 1 Chromium patches read at WebGL getParameter,
NavigatorUA::userAgentData, NavigatorConcurrentHardware::hardwareConcurrency,
and NavigatorDeviceMemory::deviceMemory time.

Wired into all three launchArgs sites: launch() (headless), launchHeaded()
(real product path), and handoff() (headless → headed re-launch).

Mapping:
  GSTACK_GPU_VENDOR      → --gstack-gpu-vendor
  GSTACK_GPU_RENDERER    → --gstack-gpu-renderer
  GSTACK_PLATFORM        → --gstack-ua-platform (with mapping:
                            MacARM/MacIntel → macOS, Win32 → Windows,
                            Linux x86_64 → Linux)
  GSTACK_GPU_CHIPSET     → --gstack-ua-model
  GSTACK_HW_CONCURRENCY  → --gstack-hw-concurrency
  GSTACK_DEVICE_MEMORY   → --gstack-device-memory

Each switch is emitted only when its env var is non-empty — empty
values fall through to the patch's "no override" path, which returns
the real Chromium native value. Safe to ship on Chromium builds
without the Pack 1 patches applied (zero behavior change).

The patches themselves live in the gbrowser repo at chromium/patches/
{webgl-vendor-spoof,ua-client-hints-stealth,worker-navigator-stealth}.patch.
Both halves (gstack arg construction + gbrowser C++ patches) must
land + Chromium rebuild before the spoof reaches the WebGL/UA-CH/
hardware accessors. Currently dormant until then.

Tests (browse/test/stealth-layer-c.test.ts):
  7 new buildGStackLaunchArgs cases — empty env, all-populated, partial,
  platform mapping (MacARM/MacIntel/Win32/Linux), unrecognized platform
  fallthrough, vendor-with-spaces escape-safety.
  All 32 stealth/browser-manager tests pass.

For GBrowser specifically: gstack-side half of the Pack 1 flag plumbing.
gbrowser repo will bump the submodule pointer to this commit, then re-run
bun run test/anti-bot/evidence-run.ts to verify creepjs's "33% headless"
score drops after Pack 1 + Chromium rebuild.
This commit is contained in:
Garry Tan
2026-05-19 11:45:45 -07:00
parent ea961e4faa
commit 13f7e351da
3 changed files with 159 additions and 3 deletions
+56
View File
@@ -270,6 +270,62 @@ export const STEALTH_LAUNCH_ARGS = [
'--disable-blink-features=AutomationControlled',
];
/**
* Build the `--gstack-*=` cmdline switches that the Pack 1 Chromium
* patches read (webgl-vendor-spoof, ua-client-hints-stealth, worker-
* navigator-stealth). Values come from the GSTACK_* env vars that
* gbd populates from host_profile.go at startup.
*
* Each switch is only emitted when its env var is non-empty — empty
* env values fall through to the patch's "no override" path, which
* returns the real Chromium native value. This keeps the helper safe
* on builds that DO NOT have the C++ patches applied (gbrowser
* pre-Pack-1) and on hosts where gbd hasn't yet populated some
* fields (legacy installs).
*
* Mapping (gbd env → Chromium cmdline switch → C++ patch consumer):
* GSTACK_GPU_VENDOR → --gstack-gpu-vendor → webgl-vendor-spoof.patch
* GSTACK_GPU_RENDERER → --gstack-gpu-renderer → webgl-vendor-spoof.patch
* GSTACK_PLATFORM → --gstack-ua-platform → ua-client-hints-stealth.patch
* (maps MacARM/MacIntel → "macOS")
* GSTACK_GPU_CHIPSET → --gstack-ua-model → ua-client-hints-stealth.patch
* GSTACK_HW_CONCURRENCY → --gstack-hw-concurrency → worker-navigator-stealth.patch
* GSTACK_DEVICE_MEMORY → --gstack-device-memory → worker-navigator-stealth.patch
*/
export function buildGStackLaunchArgs(): string[] {
const env = (globalThis as any).process?.env ?? {};
const args: string[] = [];
const vendor = env.GSTACK_GPU_VENDOR;
if (vendor) args.push(`--gstack-gpu-vendor=${vendor}`);
const renderer = env.GSTACK_GPU_RENDERER;
if (renderer) args.push(`--gstack-gpu-renderer=${renderer}`);
// Map gbd's "MacARM"/"MacIntel" classification to the UA-CH "macOS"
// platform string Chromium emits natively. Other future platforms
// would map similarly (Win32 → "Windows", Linux → "Linux").
const platform = env.GSTACK_PLATFORM;
if (platform === 'MacARM' || platform === 'MacIntel') {
args.push('--gstack-ua-platform=macOS');
} else if (platform === 'Win32') {
args.push('--gstack-ua-platform=Windows');
} else if (platform && platform.startsWith('Linux')) {
args.push('--gstack-ua-platform=Linux');
}
const chipset = env.GSTACK_GPU_CHIPSET;
if (chipset) args.push(`--gstack-ua-model=${chipset}`);
const hw = env.GSTACK_HW_CONCURRENCY;
if (hw) args.push(`--gstack-hw-concurrency=${hw}`);
const memory = env.GSTACK_DEVICE_MEMORY;
if (memory) args.push(`--gstack-device-memory=${memory}`);
return args;
}
/**
* Playwright default args to strip via ignoreDefaultArgs.
*