diff --git a/src/proxy/captcha-happy.test.ts b/src/proxy/captcha-happy.test.ts index be615d9..a5acc4a 100644 --- a/src/proxy/captcha-happy.test.ts +++ b/src/proxy/captcha-happy.test.ts @@ -251,3 +251,25 @@ describe("alias lifecycle (install/remove descriptor contract)", () => { expect(Object.getOwnPropertyDescriptor(g, "setTimeout")?.get).toBeUndefined(); }); }); + +describe("browser globals the guest SDK dereferences bare", () => { + test("requestAnimationFrame/cancelAnimationFrame resolve during a solve", () => { + // Bun has no native rAF. While these sat in HOST_CRITICAL_GLOBALS the + // alias pass skipped them, so the FeiLin bundle's 9 bare + // `requestAnimationFrame` references (only one `typeof`-guarded) were + // unresolvable — the same silent fingerprint degradation `print` caused, + // visible only as a lower solve rate. + const g: Record = {}; + const w: Record = { + requestAnimationFrame: (cb: () => void) => 1, + cancelAnimationFrame: () => {}, + }; + installGlobalWindowAlias(g, w, 15); + try { + expect(typeof g.requestAnimationFrame).toBe("function"); + expect(typeof g.cancelAnimationFrame).toBe("function"); + } finally { + removeGlobalWindowAlias(g, w); + } + }); +}); diff --git a/src/proxy/captcha-happy.ts b/src/proxy/captcha-happy.ts index c82f300..926fcce 100644 --- a/src/proxy/captcha-happy.ts +++ b/src/proxy/captcha-happy.ts @@ -630,7 +630,13 @@ function installNativeToString(w) { try { const v = desc.get.call(obj); if (typeof v === "function") mask(v); - } catch (_) {} + // Probing a getter can hand back an already-rejected promise (WHATWG + // stream `closed`/`ready` reject when the receiver is the prototype + // rather than an instance). Nobody awaits these, so without a sink + // each probe surfaced as an unhandledRejection on every solve — + // noise that buried real diagnostics. + else if (v && typeof v.then === "function") v.catch(() => {}); + } catch {} } if (depth < 3) { try { @@ -1774,7 +1780,12 @@ async function createDom(region, prefix) { const HOST_CRITICAL_GLOBALS = new Set([ "process", "Bun", "console", "performance", "crypto", "fetch", "queueMicrotask", "structuredClone", "TextEncoder", "TextDecoder", - "requestAnimationFrame", "cancelAnimationFrame", + // NOTE: requestAnimationFrame/cancelAnimationFrame were removed from this + // list (2026-09-06). Bun has no native rAF, so skipping the alias left a + // bare `requestAnimationFrame` in the FeiLin bundle unresolvable (9 call + // sites, only one `typeof`-guarded) — the same silent fingerprint + // degradation that `print` caused. Aliasing the window's implementation + // shadows nothing on the host. // NOTE: `print` was removed from this list (2026-08-29). The polyfill // defines a harmless no-op on the window, but the alias pass skipped it, // so under Bun (guest scripts run in the HOST realm) the Aliyun pe risk