From 356fd5d6061f08da2b19f11a0cee6bf755d49845 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 9 Aug 2026 09:53:39 -0300 Subject: [PATCH] fix(executors): repair DuckDuckGo AI Chat challenge solver (418 ERR_CHALLENGE) (#9866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every duckduckgo-web chat request failed with HTTP 418 ERR_CHALLENGE while duck.ai worked normally in a browser from the same IP. Ground truth was established by driving a real headful Chromium at duck.ai from that IP (it returned 200), so the environment was never the problem — the anti-abuse challenge solver was. Six independent defects were found; the first alone disabled the solver completely. 1. Module syntax inside the vm sandbox source. CHALLENGE_STUBS is executed with vm.runInContext, which compiles in SCRIPT mode. A refactor mass-added `export` to the five `function` declarations inside that template literal (they read as ordinary top-level TS functions), so every solve threw SyntaxError. The executor swallows solve failures and posts the raw unsolved challenge, which upstream answers with 418. 2. Double-escaped regex in a String.raw template. `\\s` in __parseCssDisplay reached the sandbox as a literal backslash, so the display regex never matched and a getComputedStyle probe silently read empty. 3. buildHtmlLookup undercounted descendants by one. `count` backs el.querySelectorAll('*').length; that returns DESCENDANTS and countHtmlElements already skips the #document-fragment root, so the `- 1` was wrong. Chromium reports 3 for '
  • HTMLElement -> Element), NodeList identity, a live body.children HTMLCollection, native-code toString, and sloppy-mode `this === window`. Nine of thirteen failed. Notably Math must NOT be sealed — Chromium reports Object.isSealed(Math) === false, and sealing it made our vector differ by one. 5. The solved payload dropped meta.origin / meta.stack / meta.duration. The duck.ai bundle always sends all three; captured browser requests confirm it. Without them upstream returns 418 even when every client_hash is correct. 6. reasoningEffort is now mandatory on duckchat/v1/chat. An otherwise byte-identical payload returns 200 with the field and 400 ERR_BAD_REQUEST without it (A/B verified live, repeated). Also removes the throwaway "seed" chat POST that ran before every real request. It existed to coax a usable challenge out of the upstream while the solver was broken; it only doubled chat calls against an IP-rate-limited endpoint, showing up as spurious 429 ERR_RATE_LIMIT. Verification: the solver now reproduces real Chromium's probe vectors exactly for all 8 captured challenge variants, and the executor returns 200 end-to-end live (non-streaming, streaming, claude-haiku-4-5, and a math prompt returning "42"). Tests: tests/unit/duckduckgo-challenge-solver-regression.test.ts (32 tests) and tests/unit/duckduckgo-reasoning-effort-required.test.ts (5 tests), backed by tests/fixtures/duckduckgo/challenge-variants.json — real captured challenge programs plus the probe vectors a real browser produced for them, so the suite asserts against recorded browser behaviour rather than our own output. Each fix was confirmed to fail its test when individually reverted. Co-authored-by: Mynacol --- open-sse/executors/duckduckgo-web.ts | 52 +--- .../executors/duckduckgo-web/challenge.ts | 180 +++++++++++- .../duckduckgo/challenge-variants.json | 106 +++++++ ...duckgo-challenge-solver-regression.test.ts | 258 ++++++++++++++++++ tests/unit/duckduckgo-challenge-split.test.ts | 79 ++++++ ...ckduckgo-reasoning-effort-required.test.ts | 134 +++++++++ 6 files changed, 757 insertions(+), 52 deletions(-) create mode 100644 tests/fixtures/duckduckgo/challenge-variants.json create mode 100644 tests/unit/duckduckgo-challenge-solver-regression.test.ts create mode 100644 tests/unit/duckduckgo-reasoning-effort-required.test.ts diff --git a/open-sse/executors/duckduckgo-web.ts b/open-sse/executors/duckduckgo-web.ts index 6b7eba2dce..3b066d3c0f 100644 --- a/open-sse/executors/duckduckgo-web.ts +++ b/open-sse/executors/duckduckgo-web.ts @@ -266,11 +266,14 @@ export function normalizeDuckDuckGoModel(model: string | undefined): string { } function getDuckDuckGoModelCapabilities(model: string): DuckDuckGoModelCapabilities { - // Per duckchat/v1/models (2026-07-22): claude-haiku-4-5 and gpt-oss-120b take a "low" - // reasoningEffort on the free tier; the others omit it (duck.ai applies its own default). + // `reasoningEffort` is REQUIRED on every duckchat/v1/chat request. Omitting it + // returns 400 ERR_BAD_REQUEST — A/B verified live against duck.ai with an + // otherwise byte-identical payload (200 with the field, 400 without, repeated). + // The live duck.ai bundle always sends one, so there is no "let the server + // pick a default" path any more. if (model === "claude-haiku-4-5") return { reasoningEffort: "low" }; if (model === "tinfoil/gpt-oss-120b") return { reasoningEffort: "low" }; - return { reasoningEffort: null }; + return { reasoningEffort: "none" }; } function extractDuckDuckGoFeVersion(html: string): string | null { @@ -368,7 +371,6 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { } private warmed = false; - private seeded = false; private feVersion = DEFAULT_FE_VERSION; private pendingVqdHash1: string | null = null; private readonly cookieJar = new Map(); @@ -574,7 +576,12 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { } await this.warmSession(mergedSignal); - await this.seedChallengeChain(upstreamModel, mergedSignal); + // NOTE: the throwaway "seed" chat POST that used to run here has been removed. + // It existed to coax a usable challenge out of the upstream while the solver + // was broken; now that the solver reproduces a real browser's probe vectors + // exactly, the first real request succeeds on its own. Keeping it only doubled + // the chat calls per user request against an IP-rate-limited endpoint, which + // showed up as spurious 429 ERR_RATE_LIMIT. const vqdHeaders = await this.acquireAuthHeaders(mergedSignal); if (!vqdHeaders.vqd4 && !vqdHeaders.vqdHash1) { clearTimeout(timeout); @@ -783,41 +790,6 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { ); } - private async seedChallengeChain(model: string, signal: AbortSignal): Promise { - if (this.seeded || signal.aborted) return; - this.seeded = true; - const seedMessages = [{ role: "user", content: "hi" }]; - const previousPending = this.pendingVqdHash1; - try { - const vqdHeaders = await this.acquireAuthHeaders(signal); - if (!vqdHeaders.vqd4 && !vqdHeaders.vqdHash1) { - this.pendingVqdHash1 = previousPending; - return; - } - const response = await fetch(CHAT_URL, { - method: "POST", - headers: mergeHeadersCaseInsensitive(this.buildRequestHeaders(), { - Accept: "text/event-stream", - "Content-Type": "application/json", - "x-ddg-journey-id": randomUUID().replaceAll("-", ""), - "x-fe-signals": makeDuckDuckGoFeSignals(), - "x-fe-version": this.feVersion, - ...(vqdHeaders.vqd4 ? { "x-vqd-4": vqdHeaders.vqd4 } : {}), - ...(vqdHeaders.vqdHash1 ? { "x-vqd-hash-1": vqdHeaders.vqdHash1 } : {}), - }), - body: JSON.stringify(buildDuckDuckGoPayload(model, seedMessages, false)), - signal, - }); - this.rememberResponseCookies(response); - if (response.ok) this.rememberChallengeHeader(response); - else this.pendingVqdHash1 = previousPending; - await response.body?.cancel().catch(() => {}); - } catch (error) { - void error; - this.pendingVqdHash1 = previousPending; - } - } - private async processResponse( response: Response, streaming: boolean, diff --git a/open-sse/executors/duckduckgo-web/challenge.ts b/open-sse/executors/duckduckgo-web/challenge.ts index 3c0159ba3a..8b4ea22feb 100644 --- a/open-sse/executors/duckduckgo-web/challenge.ts +++ b/open-sse/executors/duckduckgo-web/challenge.ts @@ -5,12 +5,38 @@ import { createHash } from "node:crypto"; import vm from "node:vm"; import { parseFragment, serialize } from "parse5"; +// WARNING: the contents of this template literal are NOT TypeScript — they are plain +// script-mode JavaScript executed via `vm.runInContext`. `vm.runInContext` compiles in +// script (non-module) mode, so an `export` keyword anywhere in here is a hard +// SyntaxError that kills the whole solver. A refactor that mass-added `export` to the +// five `function` declarations below silently broke every DuckDuckGo chat request +// (solve threw -> unsolved challenge sent -> HTTP 418 ERR_CHALLENGE). Do not add +// `export`/`import` to this string; `duckduckgo-challenge-split.test.ts` guards this. export const CHALLENGE_STUBS = String.raw` var __ua = __DDG_REAL_UA__; var __HTML_LOOKUP = __DDG_HTML_LOOKUP__; -export function __makeHtmlElement(tag) { +// Browser-fidelity shims for the DDG "am I a real browser" probes. +// In a browser every built-in stringifies as native code; under a plain vm +// context the user-land re-declarations below would otherwise leak their source. +function __nativeFn(fn, name){ + Object.defineProperty(fn, 'name', { value: name, configurable: true }); + fn.toString = function(){ return 'function ' + name + '() { [native code] }'; }; + return fn; +} +__nativeFn(parseInt, 'parseInt'); +__nativeFn(parseFloat, 'parseFloat'); +__nativeFn(isNaN, 'isNaN'); +__nativeFn(encodeURIComponent, 'encodeURIComponent'); +__nativeFn(decodeURIComponent, 'decodeURIComponent'); +// NOTE: do NOT seal Math. Real Chromium reports Object.isSealed(Math) === false, +// and at least one challenge variant probes exactly that; sealing it here made +// the vector differ from the browser by one and failed the challenge. +function __makeHtmlElement(tag) { var state = { _innerHTML: '', _qsaCount: 0, _cssText: '' }; - var el = { + // Instantiate against the real per-tag constructor so + // document.createElement('div') instanceof HTMLDivElement holds. + var el = Object.create(__ctorForTag(tag).prototype); + Object.assign(el, { tagName: String(tag).toUpperCase(), nodeName: String(tag).toUpperCase(), nodeType: 1, children: [], childNodes: [], classList: [], dataset: {}, offsetWidth: 1, offsetHeight: 1, clientWidth: 1, clientHeight: 1, scrollHeight: 1, scrollWidth: 1, @@ -19,9 +45,9 @@ export function __makeHtmlElement(tag) { getAttribute: function(a){ if(a==='srcdoc') return state._srcdoc||''; return null; }, hasAttribute: function(){ return false; }, appendChild: function(c){ return c; }, removeChild: function(c){ return c; }, addEventListener: function(){}, removeEventListener: function(){}, querySelector: function(){ return null; }, - querySelectorAll: function(s){ if (s === '*') { var arr = []; arr.length = state._qsaCount; return arr; } return []; }, + querySelectorAll: function(s){ if (s === '*') { return __makeNodeList(state._qsaCount); } return __makeNodeList(0); }, cloneNode: function(){ return __makeHtmlElement(tag); } - }; + }); Object.defineProperty(el, 'style', { value: new Proxy({}, { set: function(t, k, v){ t[k] = v; if (k === 'cssText') state._cssText = String(v); return true; }, get: function(t, k){ if (k === 'cssText') return state._cssText; return t[k] || ''; } }), enumerable: true, configurable: true }); Object.defineProperty(el, 'innerHTML', { get: function(){ return state._innerHTML; }, set: function(v){ var key = String(v); var entry = __HTML_LOOKUP && __HTML_LOOKUP[key]; if (entry) { state._innerHTML = String(entry.html); state._qsaCount = entry.count|0; } else { state._innerHTML = key; state._qsaCount = 0; } }, enumerable: true, configurable: true }); Object.defineProperty(el, 'outerHTML', { get: function(){ return '<' + tag + '>' + state._innerHTML + ''; }, enumerable: true }); @@ -30,7 +56,7 @@ export function __makeHtmlElement(tag) { Object.defineProperty(el, 'contentDocument', { get: function(){ return __ifDoc; }, enumerable: true }); return el; } -export function __mkObj(name, base) { +function __mkObj(name, base) { base = base || {}; return new Proxy(base, { get: function(t, k) { @@ -54,18 +80,105 @@ export function __mkObj(name, base) { has: function(t, k){ return k in t; }, set: function(t, k, v){ t[k] = v; return true; } }); } -export function __parseCssDisplay(cssText){ if(!cssText) return ''; var m = String(cssText).match(/(?:^|;)\\s*display\\s*:\\s*([^;]+)/i); return m ? String(m[1]).trim() : ''; } -export function __getComputedStyle(el){ var cssText = el && el.style && el.style.cssText || ''; var display = __parseCssDisplay(cssText); return { getPropertyValue: function(name){ if(String(name).toLowerCase()==='display') return display; return ''; }, cssText: cssText, display: display }; } +function __parseCssDisplay(cssText){ if(!cssText) return ''; var m = String(cssText).match(/(?:^|;)\s*display\s*:\s*([^;]+)/i); return m ? String(m[1]).trim() : ''; } +function __getComputedStyle(el){ var cssText = el && el.style && el.style.cssText || ''; var display = __parseCssDisplay(cssText); return { getPropertyValue: function(name){ if(String(name).toLowerCase()==='display') return display; return ''; }, cssText: cssText, display: display }; } var __ifMeta = __mkObj('meta', { getAttribute: function(a){ return a==='content' ? "default-src 'none'; script-src 'unsafe-inline';" : null; }, hasAttribute: function(a){ return a==='content'; }, tagName: 'META', nodeName: 'META' }); var __ifDoc = __mkObj('iframeDoc', { querySelector: function(s){ if (s && s.indexOf('Content-Security-Policy') !== -1) return __ifMeta; if (s === 'meta') return __ifMeta; return null; }, querySelectorAll: function(s){ if (s && s.indexOf('Content-Security-Policy') !== -1) return [__ifMeta]; if (s === 'meta') return [__ifMeta]; return []; }, getElementsByTagName: function(t){ return t && t.toLowerCase()==='meta' ? [__ifMeta] : []; }, body: __mkObj('iframeBody'), head: __mkObj('iframeHead'), documentElement: __mkObj('iframeRoot'), createElement: function(){ return __mkObj('elem', {setAttribute:function(){}, appendChild:function(){}, removeChild:function(){}, getAttribute:function(){return null;}, hasAttribute:function(){return false;}}); }, cookie: '', readyState: 'complete' }); var __iframeEl = __mkObj('iframe', { contentDocument: __ifDoc, contentWindow: __mkObj('iframeWin', { document: __ifDoc, top: undefined, parent: undefined }), document: __ifDoc, getAttribute: function(a){ if (a==='sandbox') return 'allow-scripts allow-same-origin'; if (a==='srcdoc') return ''; if (a==='id') return 'jsa'; return null; }, hasAttribute: function(a){ return a==='sandbox'||a==='id'; }, tagName: 'IFRAME', nodeName: 'IFRAME', id: 'jsa' }); -var document = __mkObj('document', { querySelector: function(s){ if (s === '#jsa') return __iframeEl; if (s && s.indexOf('Content-Security-Policy') !== -1) return __ifMeta; return null; }, querySelectorAll: function(s){ if (s === '#jsa') return [__iframeEl]; if (s && s.indexOf('Content-Security-Policy') !== -1) return [__ifMeta]; return []; }, getElementById: function(id){ return id==='jsa' ? __iframeEl : null; }, getElementsByTagName: function(t){ if(t&&t.toLowerCase()==='iframe') return [__iframeEl]; return []; }, getElementsByClassName: function(){ return []; }, body: __mkObj('body', {appendChild:function(){}, removeChild:function(){}, querySelector:function(s){return s==='#jsa'?__iframeEl:null;}, querySelectorAll:function(s){return s==='#jsa'?[__iframeEl]:[];}}), head: __mkObj('head'), documentElement: __mkObj('root'), createElement: function(tag){ return __makeHtmlElement(tag||'div'); }, createTextNode: function(t){ return {nodeType:3, nodeValue:String(t||''), textContent:String(t||'')}; }, cookie: '', readyState: 'complete', title: '', addEventListener: function(){}, removeEventListener: function(){} }); +// document.body keeps a LIVE children collection: challenges append a node and +// assert body.children.length grew by exactly 1, then remove it again. +var __bodyKids = []; +Object.defineProperty(__bodyKids, 'constructor', { value: HTMLCollection, enumerable: false, configurable: true }); +var __body = __mkObj('body', { + appendChild: function(c){ __bodyKids.push(c); return c; }, + removeChild: function(c){ var i = __bodyKids.indexOf(c); if (i !== -1) __bodyKids.splice(i, 1); return c; }, + contains: function(c){ return __bodyKids.indexOf(c) !== -1; }, + querySelector: function(s){ return s === '#jsa' ? __iframeEl : null; }, + querySelectorAll: function(s){ return s === '#jsa' ? [__iframeEl] : __makeNodeList(0); }, + children: __bodyKids, childNodes: __bodyKids, + tagName: 'BODY', nodeName: 'BODY', nodeType: 1 +}); +var document = __mkObj('document', { querySelector: function(s){ if (s === '#jsa') return __iframeEl; if (s && s.indexOf('Content-Security-Policy') !== -1) return __ifMeta; return null; }, querySelectorAll: function(s){ if (s === '#jsa') return [__iframeEl]; if (s && s.indexOf('Content-Security-Policy') !== -1) return [__ifMeta]; return __makeNodeList(__bodyKids.length + 3); }, getElementById: function(id){ return id==='jsa' ? __iframeEl : null; }, getElementsByTagName: function(t){ if(t&&t.toLowerCase()==='iframe') return [__iframeEl]; return []; }, getElementsByClassName: function(){ return []; }, body: __body, head: __mkObj('head'), documentElement: __mkObj('root'), createElement: function(tag){ return __makeHtmlElement(tag||'div'); }, createTextNode: function(t){ return {nodeType:3, nodeValue:String(t||''), textContent:String(t||'')}; }, cookie: '', readyState: 'complete', title: '', addEventListener: function(){}, removeEventListener: function(){} }); var window = __mkObj('window', { document: document, __DDG_BE_VERSION__: 1, __DDG_FE_CHAT_HASH__: 1, navigator: __mkObj('navigator', { userAgent: __ua, webdriver: false, language: 'en-US', languages: ['en-US','en'], platform: 'Linux x86_64', vendor: 'Google Inc.', appVersion: '5.0 (X11)', cookieEnabled: true, onLine: true, hardwareConcurrency: 8, deviceMemory: 8 }), innerWidth: 1280, innerHeight: 800, outerWidth: 1280, outerHeight: 800, devicePixelRatio: 1, screen: __mkObj('screen', { width:1920, height:1080, availWidth:1920, availHeight:1080, colorDepth:24, pixelDepth:24 }), location: __mkObj('location', { href:'https://duck.ai/', origin:'https://duck.ai', host:'duck.ai', hostname:'duck.ai', protocol:'https:', pathname:'/' }), performance: __mkObj('perf', { now: function(){ return 0; }, timeOrigin: 0 }), history: __mkObj('history', { length: 1, state: null }), addEventListener: function(){}, removeEventListener: function(){}, dispatchEvent: function(){return true;}, setTimeout: function(fn){ try{fn();}catch(e){} return 0; }, clearTimeout: function(){}, hasOwnProperty: function(k){ if (k==='__DDG_BE_VERSION__'||k==='__DDG_FE_CHAT_HASH__') return true; return Object.prototype.hasOwnProperty.call(this,k); } }); window.top = window; window.self = window; window.window = window; window.parent = window; window.globalThis = window; +// Object.prototype.toString.call(window) must be "[object Window]". +try { window[Symbol.toStringTag] = 'Window'; } catch (e) {} +// In a browser a sloppy-mode function called with no receiver gets the global +// object, and challenges assert (function(){return this;})() === window. +// In a vm context that is the context's own global, so alias it to window. +try { + var __g = (function(){ return this; })(); + if (__g && __g !== window) { + Object.defineProperty(__g, Symbol.toStringTag, { value: 'Window', configurable: true }); + // Copy by VALUE, not via accessors. Two reasons: + // 1) the var top/self/navigator/... declarations further down are hoisted, + // so those names already exist on the vm global and an "in" guard would + // skip them, leaving window.navigator undefined; + // 2) accessors closing over the window binding would recurse once it is + // rebound to __g below. + // The stub window is static, so a value copy is equivalent. + var __winStub = window; + for (var __k in __winStub) { + try { __g[__k] = __winStub[__k]; } catch (e) {} + } + // hasOwnProperty is probed for the __DDG_* markers; keep the stub's version. + try { __g.hasOwnProperty = function(k){ return __winStub.hasOwnProperty(k); }; } catch (e) {} + window = __g; + window.top = window; window.self = window; window.window = window; window.parent = window; window.globalThis = window; + } +} catch (e) {} var top = window, self = window, parent = window, navigator = window.navigator, location = window.location, screen = window.screen, performance = window.performance, history = window.history; var __R = null, __E = null; -export function __HTMLClass(name){ var c = function(){}; c.prototype = __mkObj(name+'.proto'); return c; } -var HTMLElement = __HTMLClass('HTMLElement'), HTMLDivElement = __HTMLClass('HTMLDivElement'), HTMLIFrameElement = __HTMLClass('HTMLIFrameElement'), HTMLDocument = __HTMLClass('HTMLDocument'), Document = __HTMLClass('Document'), Element = __HTMLClass('Element'), Node = __HTMLClass('Node'), Window = __HTMLClass('Window'), Event = __HTMLClass('Event'), MouseEvent = __HTMLClass('MouseEvent'), KeyboardEvent = __HTMLClass('KeyboardEvent'), TouchEvent = __HTMLClass('TouchEvent'), XMLHttpRequest = __HTMLClass('XMLHttpRequest'), WebSocket = __HTMLClass('WebSocket'), Image = __HTMLClass('Image'), FormData = __HTMLClass('FormData'), Blob = __HTMLClass('Blob'), File = __HTMLClass('File'), FileReader = __HTMLClass('FileReader'), URL = __HTMLClass('URL'), URLSearchParams = __HTMLClass('URLSearchParams'), Headers = __HTMLClass('Headers'), Request = __HTMLClass('Request'), Response = __HTMLClass('Response'); +// Real DOM constructor chain. Some DDG challenge variants assert +// HTMLDivElement.prototype instanceof HTMLElement and +// HTMLElement.prototype instanceof Element, so these cannot be flat +// unrelated stubs — the prototype links have to be real. +function __DomClass(name, parent){ + var c = function(){}; + if (parent) c.prototype = Object.create(parent.prototype); + c.prototype.constructor = c; + Object.defineProperty(c, 'name', { value: name, configurable: true }); + c.toString = function(){ return 'function ' + name + '() { [native code] }'; }; + return c; +} +var EventTarget = __DomClass('EventTarget', null); +var Node = __DomClass('Node', EventTarget); +var Element = __DomClass('Element', Node); +var HTMLElement = __DomClass('HTMLElement', Element); +var HTMLDivElement = __DomClass('HTMLDivElement', HTMLElement); +var HTMLIFrameElement = __DomClass('HTMLIFrameElement', HTMLElement); +var HTMLLIElement = __DomClass('HTMLLIElement', HTMLElement); +var HTMLUnknownElement = __DomClass('HTMLUnknownElement', HTMLElement); +var Document = __DomClass('Document', Node); +var HTMLDocument = __DomClass('HTMLDocument', Document); +var NodeList = __DomClass('NodeList', null); +var HTMLCollection = __DomClass('HTMLCollection', null); +// Map a tag name to the constructor a browser would use, so +// document.createElement('div') instanceof HTMLDivElement holds. +function __ctorForTag(tag){ + var t = String(tag||'div').toLowerCase(); + if (t === 'div') return HTMLDivElement; + if (t === 'iframe') return HTMLIFrameElement; + if (t === 'li') return HTMLLIElement; + return HTMLElement; +} +// A NodeList-like: array-shaped but NOT a real Array, with .constructor.name +// === 'NodeList' — challenges check both !Array.isArray(x) and the ctor name. +function __makeNodeList(length){ + var nl = Object.create(NodeList.prototype); + var n = length|0; + for (var i = 0; i < n; i++) nl[i] = __makeHtmlElement('div'); + Object.defineProperty(nl, 'length', { value: n, enumerable: false, configurable: true }); + nl.item = function(i){ return this[i] || null; }; + nl.forEach = function(fn, thisArg){ for (var i = 0; i < n; i++) fn.call(thisArg, this[i], i, this); }; + nl[Symbol.iterator] = function(){ var i = 0, self = this; return { next: function(){ return i < n ? { value: self[i++], done: false } : { value: undefined, done: true }; } }; }; + return nl; +} +function __HTMLClass(name){ var c = function(){}; c.prototype = __mkObj(name+'.proto'); return c; } +// NOTE: HTMLElement / HTMLDivElement / HTMLIFrameElement / Element / Node / +// Document / HTMLDocument / NodeList are defined above via __DomClass with a +// REAL prototype chain — do not redeclare them here or the instanceof probes break. +var Window = __HTMLClass('Window'), Event = __HTMLClass('Event'), MouseEvent = __HTMLClass('MouseEvent'), KeyboardEvent = __HTMLClass('KeyboardEvent'), TouchEvent = __HTMLClass('TouchEvent'), XMLHttpRequest = __HTMLClass('XMLHttpRequest'), WebSocket = __HTMLClass('WebSocket'), Image = __HTMLClass('Image'), FormData = __HTMLClass('FormData'), Blob = __HTMLClass('Blob'), File = __HTMLClass('File'), FileReader = __HTMLClass('FileReader'), URL = __HTMLClass('URL'), URLSearchParams = __HTMLClass('URLSearchParams'), Headers = __HTMLClass('Headers'), Request = __HTMLClass('Request'), Response = __HTMLClass('Response'); var fetch = function(){ return Promise.resolve(__mkObj('resp', {ok:true, status:200, json:function(){return Promise.resolve({});}, text:function(){return Promise.resolve('');}})); }; var getComputedStyle = __getComputedStyle; `; @@ -90,9 +203,16 @@ export function buildHtmlLookup(js: string): Record
  • { // SECURITY NOTE: This function executes base64-decoded JavaScript from duck.ai via vm.runInContext. // The challenge code is upstream-supplied (supply-chain surface). It is sandboxed with a 5s timeout @@ -121,14 +260,31 @@ export async function solveDuckDuckGoChallenge( ); const context = vm.createContext({}); vm.runInContext(stubs, context, { timeout: 5000 }); + const startedAt = Date.now(); const result = (await vm.runInContext(js, context, { timeout: 5000, })) as DuckDuckGoChallengeResult; + const elapsedMs = Date.now() - startedAt; const clientHashes = Array.isArray(result.client_hashes) ? result.client_hashes : []; if (clientHashes.length === 0) throw new Error("DuckDuckGo challenge returned empty client_hashes"); clientHashes[0] = userAgent; result.client_hashes = clientHashes.map((hash) => sha256Base64(String(hash))); + + // The real frontend augments the challenge's own `meta` with origin / stack / + // duration before sending it back. Omitting them yields 418 ERR_CHALLENGE even + // when every client_hash is correct (confirmed by capturing a real browser's + // x-vqd-hash-1 header, which always carries all three). + const origin = options.origin ?? DUCKDUCKGO_CHALLENGE_ORIGIN; + const bundlePath = options.bundlePath ?? "/dist/duckai-dist/entry.duckai.js"; + const meta = (result.meta ?? {}) as Record; + result.meta = { + ...meta, + origin, + stack: buildChallengeStack(origin, bundlePath), + duration: String(elapsedMs), + }; + return Buffer.from(JSON.stringify(result), "utf8").toString("base64"); } diff --git a/tests/fixtures/duckduckgo/challenge-variants.json b/tests/fixtures/duckduckgo/challenge-variants.json new file mode 100644 index 0000000000..5da2505311 --- /dev/null +++ b/tests/fixtures/duckduckgo/challenge-variants.json @@ -0,0 +1,106 @@ +{ + "variant-0.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MTk2YjdiPV8weDJiMmM7KGZ1bmN0aW9uKF8weDMyOTEwYyxfMHgzOTY5NDMpe2NvbnN0IF8weDI5MDJiMj1fMHgyYjJjLF8weDIyOGNiZj1fMHgzMjkxMGMoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4MjA2NzE4PS1wYXJzZUludChfMHgyOTAyYjIoMHgxZjMpKS8weDEqKC1wYXJzZUludChfMHgyOTAyYjIoMHgxZDIpKS8weDIpKy1wYXJzZUludChfMHgyOTAyYjIoMHgxZDQpKS8weDMrLXBhcnNlSW50KF8weDI5MDJiMigweDFkYykpLzB4NCooLXBhcnNlSW50KF8weDI5MDJiMigweDFlNykpLzB4NSkrLXBhcnNlSW50KF8weDI5MDJiMigweDFjZCkpLzB4NiooLXBhcnNlSW50KF8weDI5MDJiMigweDFmMikpLzB4NykrcGFyc2VJbnQoXzB4MjkwMmIyKDB4MWNhKSkvMHg4KihwYXJzZUludChfMHgyOTAyYjIoMHgxZDcpKS8weDkpKy1wYXJzZUludChfMHgyOTAyYjIoMHgxZjgpKS8weGEqKC1wYXJzZUludChfMHgyOTAyYjIoMHgxZDUpKS8weGIpKy1wYXJzZUludChfMHgyOTAyYjIoMHgxZTkpKS8weGMqKHBhcnNlSW50KF8weDI5MDJiMigweDFjOCkpLzB4ZCk7aWYoXzB4MjA2NzE4PT09XzB4Mzk2OTQzKWJyZWFrO2Vsc2UgXzB4MjI4Y2JmWydwdXNoJ10oXzB4MjI4Y2JmWydzaGlmdCddKCkpO31jYXRjaChfMHg4NWIxNjApe18weDIyOGNiZlsncHVzaCddKF8weDIyOGNiZlsnc2hpZnQnXSgpKTt9fX0oXzB4M2NhOSwweGQ3YmNmKSk7ZnVuY3Rpb24gXzB4MmIyYyhfMHg0MWI4ODUsXzB4Y2RlN2RlKXtjb25zdCBfMHgzY2E5ZTg9XzB4M2NhOSgpO3JldHVybiBfMHgyYjJjPWZ1bmN0aW9uKF8weDJiMmNhMixfMHgxMTk4NTIpe18weDJiMmNhMj1fMHgyYjJjYTItMHgxYzg7bGV0IF8weDQ0NGMwMj1fMHgzY2E5ZThbXzB4MmIyY2EyXTtyZXR1cm4gXzB4NDQ0YzAyO30sXzB4MmIyYyhfMHg0MWI4ODUsXzB4Y2RlN2RlKTt9Y29uc3QgXzB4MzFlNTliPVtbJ3VhJywhW11dLFtfMHgxOTZiN2IoMHgxZTEpLCFbXV0sW18weDE5NmI3YigweDFmNiksIVtdXV0sXzB4NWI5NWM2PWF3YWl0IFByb21pc2VbJ2FsbCddKFtuYXZpZ2F0b3JbXzB4MTk2YjdiKDB4MWVjKV0sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4NTUwZjBjPV8weDE5NmI3YixfMHg1ODA4ZWQ9ZG9jdW1lbnRbXzB4NTUwZjBjKDB4MWVlKV0oXzB4NTUwZjBjKDB4MWQ4KSk7cmV0dXJuIF8weDU4MDhlZFsnaW5uZXJIVE1MJ109JzxsaT48ZGl2PjwvbGk+PGxpPjwvZGl2JyxTdHJpbmcoMHg3YTgrXzB4NTgwOGVkW18weDU1MGYwYygweDFmNSldWydsZW5ndGgnXSpfMHg1ODA4ZWRbXzB4NTUwZjBjKDB4MWNjKV0oJyonKVtfMHg1NTBmMGMoMHgxZjApXSk7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHgxNTlkMzg9XzB4MTk2YjdiO3JldHVybiBTdHJpbmcoW25hdmlnYXRvcltfMHgxNTlkMzgoMHgxZDApXT09PSEhW10sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MzMxZWIxPV8weDE1OWQzOCxfMHhjMjgxMjI9ZG9jdW1lbnRbXzB4MzMxZWIxKDB4MWVlKV0oXzB4MzMxZWIxKDB4MWNiKSk7XzB4YzI4MTIyWydzcmNkb2MnXT1fMHgzMzFlYjEoMHgxZWEpLGRvY3VtZW50W18weDMzMWViMSgweDFkZildW18weDMzMWViMSgweDFkMSldKF8weGMyODEyMik7bGV0IF8weDU3MzI5ZDtyZXR1cm4gXzB4YzI4MTIyWydjb250ZW50V2luZG93J10mJl8weGMyODEyMltfMHgzMzFlYjEoMHgxZTYpXVtfMHgzMzFlYjEoMHgxY2UpXSYmXzB4YzI4MTIyW18weDMzMWViMSgweDFlNildW18weDMzMWViMSgweDFjZSldW18weDMzMWViMSgweDFkMyldP18weDU3MzI5ZD1fMHhjMjgxMjJbXzB4MzMxZWIxKDB4MWU2KV1bXzB4MzMxZWIxKDB4MWNlKV1bXzB4MzMxZWIxKDB4MWQzKV1bJ3RvU3RyaW5nJ10oKTpfMHg1NzMyOWQ9dW5kZWZpbmVkLGRvY3VtZW50W18weDMzMWViMSgweDFkZildWydyZW1vdmVDaGlsZCddKF8weGMyODEyMiksISFfMHg1NzMyOWQ7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHg0ZTI5MjQ9XzB4MTU5ZDM4LF8weDIzN2I5MT1bJ0FycmF5JyxfMHg0ZTI5MjQoMHgxZDYpLCdQcm9taXNlJyxfMHg0ZTI5MjQoMHgxZGEpLF8weDRlMjkyNCgweDFlZiksJ0pTT04nLF8weDRlMjkyNCgweDFlMCldLF8weDM0OTBhZD1PYmplY3RbXzB4NGUyOTI0KDB4MWRlKV0od2luZG93Wyd0b3AnXSlbXzB4NGUyOTI0KDB4MWY0KV0oXzB4NTBjOGE0PT5fMHgyMzdiOTFbXzB4NGUyOTI0KDB4MWNmKV0oXzB4NDYyMWUwPT5fMHg1MGM4YTQhPT1fMHg0NjIxZTAmJl8weDUwYzhhNFtfMHg0ZTI5MjQoMHgxZjkpXSgnXycrXzB4NDYyMWUwKSYmd2luZG93W18weDRlMjkyNCgweDFlMildW18weDUwYzhhNF09PT13aW5kb3dbJ3RvcCddW18weDQ2MjFlMF0pKTtyZXR1cm4gXzB4MzQ5MGFkW18weDRlMjkyNCgweDFmMCldPjB4MDt9KCkpXVtfMHgxNTlkMzgoMHgxZTMpXShOdW1iZXIpW18weDE1OWQzOCgweDFkOSldKChfMHgzYzU3OGIsXzB4NGMxMDA4KT0+XzB4M2M1NzhiK18weDRjMTAwOCwweDE4ZGYpKTt9KCkpXSksXzB4NDlmOTllPVtdLF8weDMxN2NkNj17fSxfMHg0NjRlOGU9Jzg1OWJlYzU3ZWVlYWYxMjYnO2ZvcihsZXQgXzB4MmE4YWI1PTB4MDtfMHgyYThhYjU8XzB4NWI5NWM2W18weDE5NmI3YigweDFmMCldO18weDJhOGFiNSsrKXtjb25zdCBfMHg0NDdlNTQ9XzB4NWI5NWM2W18weDJhOGFiNV07QXJyYXlbXzB4MTk2YjdiKDB4MWVkKV0oXzB4NDQ3ZTU0KT8oXzB4NDlmOTllW18weDE5NmI3YigweDFjOSldKF8weDQ0N2U1NFsweDBdKSxfMHg0NDdlNTRbJ2xlbmd0aCddPjB4MSYmXzB4MzFlNTliW18weDJhOGFiNV1bMHgxXSYmKF8weDMxN2NkNltfMHgzMWU1OWJbXzB4MmE4YWI1XVsweDBdXT1fMHg0NDdlNTRbMHgxXSkpOl8weDQ5Zjk5ZVtfMHgxOTZiN2IoMHgxYzkpXShfMHg0NDdlNTQpO31jb25zdCBfMHg3NjY2MDQ9QXJyYXlbJ2Zyb20nXShKU09OW18weDE5NmI3YigweDFlYildKF8weDMxN2NkNikpWydtYXAnXSgoXzB4MTJmNjA4LF8weDMwMjc1Myk9PlN0cmluZ1tfMHgxOTZiN2IoMHgxZjEpXShfMHgxMmY2MDhbXzB4MTk2YjdiKDB4MWY3KV0oMHgwKV5fMHg0NjRlOGVbJ2NoYXJDb2RlQXQnXShfMHgzMDI3NTMlXzB4NDY0ZThlW18weDE5NmI3YigweDFmMCldKSkpW18weDE5NmI3YigweDFlOCldKCcnKTtyZXR1cm57J3NlcnZlcl9oYXNoZXMnOltfMHgxOTZiN2IoMHgxZTQpLF8weDE5NmI3YigweDFkYiksXzB4MTk2YjdiKDB4MWU1KV0sJ2NsaWVudF9oYXNoZXMnOl8weDQ5Zjk5ZSwnc2lnbmFscyc6e30sJ21ldGEnOnsndic6JzQnLCdjaGFsbGVuZ2VfaWQnOidlOTgwMzlkOTI0ZWUyMDNiOTI3NWVlNGE4MTRkMmQ0NGIxMmZjYTU0ODhlYzc5ZTQ3OWYzMzJhYTg5MDZmMmQ5aDhqYnQnLCd0aW1lc3RhbXAnOl8weDE5NmI3YigweDFkZCksJ2RlYnVnJzpfMHg3NjY2MDR9fTtmdW5jdGlvbiBfMHgzY2E5KCl7Y29uc3QgXzB4NGJjN2JiPVsnMTc4NjA5MzU3NzM5MScsJ2tleXMnLCdib2R5JywnV2luZG93JywnaDhqYnQnLCd0b3AnLCdtYXAnLCdsK00vblRsbFk4bTAvNEtFMDNHUFhMNEZ2UHQxMmY0Y0xMaGE0YTI4V0ZvPScsJy8rMHB6TGNZdlJzMkpoRzFHWE91RGlSV2RxRzh2MWJlNm1kOUc4T2ptSk09JywnY29udGVudFdpbmRvdycsJzM3ODA5NXhlTXN5dScsJ2pvaW4nLCc2NDIwMHBmZHJBSScsJ0R1Y2tEdWNrR29ceDIwRnJhdWRceDIwJlx4MjBBYnVzZScsJ3N0cmluZ2lmeScsJ3VzZXJBZ2VudCcsJ2lzQXJyYXknLCdjcmVhdGVFbGVtZW50JywnU3ltYm9sJywnbGVuZ3RoJywnZnJvbUNoYXJDb2RlJywnMjFwdVB0d1AnLCcxMzk2NjF2aVBadmYnLCdmaWx0ZXInLCdpbm5lckhUTUwnLCdpM2pwMCcsJ2NoYXJDb2RlQXQnLCczMjYwYnVSaG14JywnZW5kc1dpdGgnLCc2MjUzdnpPa0xnJywncHVzaCcsJzE2MEpuTUNjUicsJ2lmcmFtZScsJ3F1ZXJ5U2VsZWN0b3JBbGwnLCc5Mzc2NjJSQklBUG4nLCdzZWxmJywnc29tZScsJ3dlYmRyaXZlcicsJ2FwcGVuZENoaWxkJywnMTJjV29FWU8nLCdnZXQnLCczMDM0ODQyVWhSbkZTJywnNDc1MzFCYXlld1EnLCdPYmplY3QnLCcyNDQ0NzZyZW1ZemonLCdkaXYnLCdyZWR1Y2UnLCdQcm94eScsJ1lFeGk1Z1dDcGJTNnliazV5YUdsQlgwRG9RMmlNTC9xSmQ3cU9pRGJqdHM9JywnNjRsaEtJbXknXTtfMHgzY2E5PWZ1bmN0aW9uKCl7cmV0dXJuIF8weDRiYzdiYjt9O3JldHVybiBfMHgzY2E5KCk7fX0pKCk=", + "browserProbes": ["2047", "6367"], + "browserReduceVectors": [ + { + "seed": 6367, + "booleans": [0, 0, 0] + } + ] + }, + "variant-1.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MjcyMTk0PV8weDE1NDQ7ZnVuY3Rpb24gXzB4MTU0NChfMHgyYWZhN2MsXzB4NTlkM2NiKXtjb25zdCBfMHg1NWYzYzg9XzB4NTVmMygpO3JldHVybiBfMHgxNTQ0PWZ1bmN0aW9uKF8weDE1NDRkYyxfMHg0ZmJjMWYpe18weDE1NDRkYz1fMHgxNTQ0ZGMtMHgxZjM7bGV0IF8weGY4MjkwMT1fMHg1NWYzYzhbXzB4MTU0NGRjXTtyZXR1cm4gXzB4ZjgyOTAxO30sXzB4MTU0NChfMHgyYWZhN2MsXzB4NTlkM2NiKTt9KGZ1bmN0aW9uKF8weDM5ZDVlZixfMHgyNTJlM2Qpe2NvbnN0IF8weDU4NjdjYz1fMHgxNTQ0LF8weDIxMWNhNz1fMHgzOWQ1ZWYoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4ODIxMTc9cGFyc2VJbnQoXzB4NTg2N2NjKDB4MjAwKSkvMHgxK3BhcnNlSW50KF8weDU4NjdjYygweDIyMCkpLzB4MitwYXJzZUludChfMHg1ODY3Y2MoMHgyMTgpKS8weDMqKHBhcnNlSW50KF8weDU4NjdjYygweDIwYSkpLzB4NCkrcGFyc2VJbnQoXzB4NTg2N2NjKDB4MjFhKSkvMHg1KigtcGFyc2VJbnQoXzB4NTg2N2NjKDB4MWY2KSkvMHg2KStwYXJzZUludChfMHg1ODY3Y2MoMHgyMDQpKS8weDcrLXBhcnNlSW50KF8weDU4NjdjYygweDIwZCkpLzB4OCstcGFyc2VJbnQoXzB4NTg2N2NjKDB4MjA3KSkvMHg5KihwYXJzZUludChfMHg1ODY3Y2MoMHgxZmIpKS8weGEpO2lmKF8weDgyMTE3PT09XzB4MjUyZTNkKWJyZWFrO2Vsc2UgXzB4MjExY2E3WydwdXNoJ10oXzB4MjExY2E3WydzaGlmdCddKCkpO31jYXRjaChfMHhkZGFlN2Mpe18weDIxMWNhN1sncHVzaCddKF8weDIxMWNhN1snc2hpZnQnXSgpKTt9fX0oXzB4NTVmMywweDliNzI5KSk7Y29uc3QgXzB4MjkxMjk2PVtbJ3VhJywhW11dLFtfMHgyNzIxOTQoMHgyMjYpLCFbXV0sW18weDI3MjE5NCgweDIxMSksIVtdXV0sXzB4MjczMWEzPWF3YWl0IFByb21pc2VbJ2FsbCddKFtuYXZpZ2F0b3JbXzB4MjcyMTk0KDB4MjEwKV0sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MmU2NjhlPV8weDI3MjE5NCxfMHg0NjEwZjk9W10sXzB4NDVjMThhPXdpbmRvd1tfMHgyZTY2OGUoMHgyMTUpXTtfMHg0NjEwZjlbXzB4MmU2NjhlKDB4MjBlKV0oXzB4NDVjMThhW18weDJlNjY4ZSgweDIyNCldKClbXzB4MmU2NjhlKDB4MjBmKV0oXzB4MmU2NjhlKDB4MjEzKSkpO2NsYXNzIF8weDU3OTIxMyBleHRlbmRzIEFycmF5e31jb25zdCBfMHgxZTM4M2Y9bmV3IF8weDU3OTIxMygweDEsMHgyLDB4MyksXzB4MzMxYjFhPV8weDFlMzgzZltfMHgyZTY2OGUoMHgxZjcpXShfMHgyYTQ5OTA9Pl8weDJhNDk5MCoweDIpO18weDQ2MTBmOVtfMHgyZTY2OGUoMHgyMGUpXShfMHgzMzFiMWEgaW5zdGFuY2VvZiBfMHg1NzkyMTMpLF8weDQ2MTBmOVtfMHgyZTY2OGUoMHgyMGUpXShPYmplY3RbXzB4MmU2NjhlKDB4MWY0KV1bXzB4MmU2NjhlKDB4MjI0KV1bXzB4MmU2NjhlKDB4MWZjKV0od2luZG93KT09PSdbb2JqZWN0XHgyMFdpbmRvd10nKTtjb25zdCBfMHgzMjY0NTU9RXJyb3I7XzB4NDYxMGY5WydwdXNoJ10obmV3IF8weDMyNjQ1NSgpaW5zdGFuY2VvZiBFcnJvciksXzB4NDYxMGY5W18weDJlNjY4ZSgweDIwZSldKF8weDMyNjQ1NVtfMHgyZTY2OGUoMHgyMjkpXT09PXVuZGVmaW5lZHx8dHlwZW9mIF8weDMyNjQ1NVsnY2FwdHVyZVN0YWNrVHJhY2UnXT09PSdmdW5jdGlvbicpLF8weDQ2MTBmOVtfMHgyZTY2OGUoMHgyMGUpXShPYmplY3RbXzB4MmU2NjhlKDB4MjFkKV0oTWF0aCkpLF8weDQ2MTBmOVtfMHgyZTY2OGUoMHgyMGUpXSgoZnVuY3Rpb24oKXtyZXR1cm4gdGhpczt9KCkpPT09d2luZG93KTtjb25zdCBfMHgxNTYzMzM9ZG9jdW1lbnRbXzB4MmU2NjhlKDB4MjE3KV1bXzB4MmU2NjhlKDB4MjA1KV0sXzB4MzViNDMxPV8weDE1NjMzM1snbGVuZ3RoJ10sXzB4MWUwYmJkPWRvY3VtZW50W18weDJlNjY4ZSgweDIwOSldKF8weDJlNjY4ZSgweDIwNikpO2RvY3VtZW50Wydib2R5J11bJ2FwcGVuZENoaWxkJ10oXzB4MWUwYmJkKSxfMHg0NjEwZjlbXzB4MmU2NjhlKDB4MjBlKV0oXzB4MTU2MzMzW18weDJlNjY4ZSgweDIyYSldPT09XzB4MzViNDMxKzB4MSksZG9jdW1lbnRbXzB4MmU2NjhlKDB4MjE3KV1bXzB4MmU2NjhlKDB4MWY4KV0oXzB4MWUwYmJkKTtjb25zdCBfMHgzZjBjNTU9ZG9jdW1lbnRbJ3F1ZXJ5U2VsZWN0b3JBbGwnXSgnKicpO18weDQ2MTBmOVsncHVzaCddKCFBcnJheVtfMHgyZTY2OGUoMHgxZmEpXShfMHgzZjBjNTUpKSxfMHg0NjEwZjlbJ3B1c2gnXShfMHgzZjBjNTVbXzB4MmU2NjhlKDB4MjFmKV1bXzB4MmU2NjhlKDB4MjIxKV09PT1fMHgyZTY2OGUoMHgyMjcpKTtjb25zdCBfMHgzMzE2MTc9ZG9jdW1lbnRbXzB4MmU2NjhlKDB4MjA5KV0oXzB4MmU2NjhlKDB4MjA2KSk7cmV0dXJuIF8weDQ2MTBmOVsncHVzaCddKF8weDMzMTYxNyBpbnN0YW5jZW9mIEhUTUxEaXZFbGVtZW50KSxfMHg0NjEwZjlbXzB4MmU2NjhlKDB4MjBlKV0oSFRNTERpdkVsZW1lbnRbXzB4MmU2NjhlKDB4MWY0KV1pbnN0YW5jZW9mIEhUTUxFbGVtZW50KSxfMHg0NjEwZjlbXzB4MmU2NjhlKDB4MjBlKV0oSFRNTEVsZW1lbnRbJ3Byb3RvdHlwZSddaW5zdGFuY2VvZiBFbGVtZW50KSxTdHJpbmcoXzB4NDYxMGY5W18weDJlNjY4ZSgweDFmNyldKE51bWJlcilbXzB4MmU2NjhlKDB4MjI4KV0oKF8weDNlNjBmOSxfMHg0OGY2MTcpPT5fMHgzZTYwZjkrXzB4NDhmNjE3LDB4NTlhKSk7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHhiNTRlNDY9XzB4MjcyMTk0O3JldHVybiBTdHJpbmcoW25hdmlnYXRvclsnd2ViZHJpdmVyJ109PT0hIVtdLChmdW5jdGlvbigpe2NvbnN0IF8weDI2MjJjZD1fMHgxNTQ0LF8weDE0M2Y5Yj1kb2N1bWVudFtfMHgyNjIyY2QoMHgyMDkpXShfMHgyNjIyY2QoMHgyMDEpKTtfMHgxNDNmOWJbJ3NyY2RvYyddPSdEdWNrRHVja0dvXHgyMEZyYXVkXHgyMCZceDIwQWJ1c2UnLGRvY3VtZW50W18weDI2MjJjZCgweDIxNyldWydhcHBlbmRDaGlsZCddKF8weDE0M2Y5Yik7bGV0IF8weDNmNjYxMTtyZXR1cm4gXzB4MTQzZjliW18weDI2MjJjZCgweDIyMildJiZfMHgxNDNmOWJbXzB4MjYyMmNkKDB4MjIyKV1bJ3NlbGYnXSYmXzB4MTQzZjliW18weDI2MjJjZCgweDIyMildW18weDI2MjJjZCgweDIxOSldW18weDI2MjJjZCgweDFmMyldP18weDNmNjYxMT1fMHgxNDNmOWJbXzB4MjYyMmNkKDB4MjIyKV1bXzB4MjYyMmNkKDB4MjE5KV1bXzB4MjYyMmNkKDB4MWYzKV1bXzB4MjYyMmNkKDB4MjI0KV0oKTpfMHgzZjY2MTE9dW5kZWZpbmVkLGRvY3VtZW50W18weDI2MjJjZCgweDIxNyldW18weDI2MjJjZCgweDFmOCldKF8weDE0M2Y5YiksISFfMHgzZjY2MTE7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHgxNmM3MjI9XzB4MTU0NCxfMHg0NzFiOTg9W18weDE2YzcyMigweDIxYiksJ09iamVjdCcsXzB4MTZjNzIyKDB4MjAzKSxfMHgxNmM3MjIoMHgyMWUpLCdTeW1ib2wnLF8weDE2YzcyMigweDIxYyksXzB4MTZjNzIyKDB4MjIzKV0sXzB4NTMwMmIyPU9iamVjdFtfMHgxNmM3MjIoMHgyMDIpXSh3aW5kb3dbXzB4MTZjNzIyKDB4MWY1KV0pWydmaWx0ZXInXShfMHg0NDBkNzg9Pl8weDQ3MWI5OFtfMHgxNmM3MjIoMHgyMGIpXShfMHg0NDNiMjQ9Pl8weDQ0MGQ3OCE9PV8weDQ0M2IyNCYmXzB4NDQwZDc4W18weDE2YzcyMigweDIxNCldKCdfJytfMHg0NDNiMjQpJiZ3aW5kb3dbXzB4MTZjNzIyKDB4MWY1KV1bXzB4NDQwZDc4XT09PXdpbmRvd1sndG9wJ11bXzB4NDQzYjI0XSkpO3JldHVybiBfMHg1MzAyYjJbXzB4MTZjNzIyKDB4MjJhKV0+MHgwO30oKSldW18weGI1NGU0NigweDFmNyldKE51bWJlcilbXzB4YjU0ZTQ2KDB4MjI4KV0oKF8weDFiNWM5YyxfMHg4M2IzNjkpPT5fMHgxYjVjOWMrXzB4ODNiMzY5LDB4MjQwMCkpO30oKSldKSxfMHgyOWY5OTU9W10sXzB4MWIwZGRhPXt9LF8weDFjYjMzZD1fMHgyNzIxOTQoMHgxZmUpO2ZvcihsZXQgXzB4NWIzYTcwPTB4MDtfMHg1YjNhNzA8XzB4MjczMWEzW18weDI3MjE5NCgweDIyYSldO18weDViM2E3MCsrKXtjb25zdCBfMHgxMjg2YmE9XzB4MjczMWEzW18weDViM2E3MF07QXJyYXlbJ2lzQXJyYXknXShfMHgxMjg2YmEpPyhfMHgyOWY5OTVbXzB4MjcyMTk0KDB4MjBlKV0oXzB4MTI4NmJhWzB4MF0pLF8weDEyODZiYVsnbGVuZ3RoJ10+MHgxJiZfMHgyOTEyOTZbXzB4NWIzYTcwXVsweDFdJiYoXzB4MWIwZGRhW18weDI5MTI5NltfMHg1YjNhNzBdWzB4MF1dPV8weDEyODZiYVsweDFdKSk6XzB4MjlmOTk1W18weDI3MjE5NCgweDIwZSldKF8weDEyODZiYSk7fWNvbnN0IF8weDMzYjkwNT1BcnJheVtfMHgyNzIxOTQoMHgyMTIpXShKU09OW18weDI3MjE5NCgweDIyNSldKF8weDFiMGRkYSkpW18weDI3MjE5NCgweDFmNyldKChfMHgxZTNmNGEsXzB4NGIxYjMyKT0+U3RyaW5nW18weDI3MjE5NCgweDFmZCldKF8weDFlM2Y0YVtfMHgyNzIxOTQoMHgyMGMpXSgweDApXl8weDFjYjMzZFtfMHgyNzIxOTQoMHgyMGMpXShfMHg0YjFiMzIlXzB4MWNiMzNkWydsZW5ndGgnXSkpKVtfMHgyNzIxOTQoMHgyMDgpXSgnJyk7ZnVuY3Rpb24gXzB4NTVmMygpe2NvbnN0IF8weDQwYmRiYz1bJ0FycmF5JywnSlNPTicsJ2lzU2VhbGVkJywnUHJveHknLCdjb25zdHJ1Y3RvcicsJzExMzkyMzZ4YWJVYWMnLCduYW1lJywnY29udGVudFdpbmRvdycsJ1dpbmRvdycsJ3RvU3RyaW5nJywnc3RyaW5naWZ5JywncHhqenInLCdOb2RlTGlzdCcsJ3JlZHVjZScsJ2NhcHR1cmVTdGFja1RyYWNlJywnbGVuZ3RoJywnZ2V0JywncHJvdG90eXBlJywndG9wJywnODMyMjE4YVlIYU1LJywnbWFwJywncmVtb3ZlQ2hpbGQnLCczZ0RIaDJpTFRIMXkyRXBiTjd6NktyNllta3RNeU5wWm40SjlKN2NMZkRrPScsJ2lzQXJyYXknLCcxMFRjdVF1SycsJ2NhbGwnLCdmcm9tQ2hhckNvZGUnLCc3MWE5OTJmYjc2NTM1N2EwJywnMTc4NjA5MzU4NDYzMicsJzg5MjIwM01semVlZicsJ2lmcmFtZScsJ2tleXMnLCdQcm9taXNlJywnMTQ1MjYzM3d1Y29tUScsJ2NoaWxkcmVuJywnZGl2JywnMzcyNjk5RERVcFpqJywnam9pbicsJ2NyZWF0ZUVsZW1lbnQnLCc0SHNCQ29YJywnc29tZScsJ2NoYXJDb2RlQXQnLCc2OTczNDY0RHdyYllrJywncHVzaCcsJ2luY2x1ZGVzJywndXNlckFnZW50JywnaTNqcDAnLCdmcm9tJywnW25hdGl2ZVx4MjBjb2RlXScsJ2VuZHNXaXRoJywncGFyc2VJbnQnLCdvL0JiTEJZRHJ6OTlMNGVrUjlxUE1HQlV2WTYwYXNVeDhjOWxHTE0ya2dnPScsJ2JvZHknLCc4ODk3MjhMU0VtUXgnLCdzZWxmJywnMTVLR2dRZ0YnXTtfMHg1NWYzPWZ1bmN0aW9uKCl7cmV0dXJuIF8weDQwYmRiYzt9O3JldHVybiBfMHg1NWYzKCk7fXJldHVybnsnc2VydmVyX2hhc2hlcyc6W18weDI3MjE5NCgweDFmOSksJzY3OUJVOHFoL25jVlBMRkwvK2p2NXl4anJ6WkVMY2lWVFRDcThWSGs3SVU9JyxfMHgyNzIxOTQoMHgyMTYpXSwnY2xpZW50X2hhc2hlcyc6XzB4MjlmOTk1LCdzaWduYWxzJzp7fSwnbWV0YSc6eyd2JzonNCcsJ2NoYWxsZW5nZV9pZCc6JzM5NTkyYmQ2YzM5MWIwNzZjYTY5NDMzYzliMDA5NDIwMWQwOWY1NmY5ZmU0ODk3YjI5YzQ2OTJlNmY1NDFjMzFweGp6cicsJ3RpbWVzdGFtcCc6XzB4MjcyMTk0KDB4MWZmKSwnZGVidWcnOl8weDMzYjkwNX19O30pKCk=", + "browserProbes": ["1446", "9216"], + "browserReduceVectors": [ + { + "seed": 1434, + "booleans": [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1] + }, + { + "seed": 9216, + "booleans": [0, 0, 0] + } + ] + }, + "variant-2.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MWVjMGU3PV8weDEwZDQ7KGZ1bmN0aW9uKF8weDNjMDBlYyxfMHg1YjRiOWUpe2NvbnN0IF8weDI1NmNjMT1fMHgxMGQ0LF8weDEyMjgwOT1fMHgzYzAwZWMoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4MTc2N2YxPXBhcnNlSW50KF8weDI1NmNjMSgweDFkNCkpLzB4MStwYXJzZUludChfMHgyNTZjYzEoMHgxZDIpKS8weDIrcGFyc2VJbnQoXzB4MjU2Y2MxKDB4MWQ4KSkvMHgzK3BhcnNlSW50KF8weDI1NmNjMSgweDFkYSkpLzB4NCtwYXJzZUludChfMHgyNTZjYzEoMHgxYjMpKS8weDUqKHBhcnNlSW50KF8weDI1NmNjMSgweDFiNCkpLzB4NikrLXBhcnNlSW50KF8weDI1NmNjMSgweDFkNikpLzB4NyooLXBhcnNlSW50KF8weDI1NmNjMSgweDFlYykpLzB4OCkrLXBhcnNlSW50KF8weDI1NmNjMSgweDFlNCkpLzB4OTtpZihfMHgxNzY3ZjE9PT1fMHg1YjRiOWUpYnJlYWs7ZWxzZSBfMHgxMjI4MDlbJ3B1c2gnXShfMHgxMjI4MDlbJ3NoaWZ0J10oKSk7fWNhdGNoKF8weDQwZTBhMSl7XzB4MTIyODA5WydwdXNoJ10oXzB4MTIyODA5WydzaGlmdCddKCkpO319fShfMHg0NGExLDB4YjhmZTkpKTtjb25zdCBfMHgyMzM5ZDQ9W1sndWEnLCFbXV0sWydweGp6cicsIVtdXSxbJ2kzanAwJywhW11dXSxfMHg1NjQxNjM9YXdhaXQgUHJvbWlzZVtfMHgxZWMwZTcoMHgxZDcpXShbbmF2aWdhdG9yW18weDFlYzBlNygweDFkOSldLChmdW5jdGlvbigpe2NvbnN0IF8weDNmN2ZiMD1fMHgxZWMwZTcsXzB4MjA4YTA4PVtdLF8weDFiOGRkOT13aW5kb3dbXzB4M2Y3ZmIwKDB4MWM3KV07XzB4MjA4YTA4W18weDNmN2ZiMCgweDFjOCldKF8weDFiOGRkOVtfMHgzZjdmYjAoMHgxYjkpXSgpWydpbmNsdWRlcyddKCdbbmF0aXZlXHgyMGNvZGVdJykpO2NsYXNzIF8weDI1NGUyMiBleHRlbmRzIEFycmF5e31jb25zdCBfMHgyMDk0ZDM9bmV3IF8weDI1NGUyMigweDEsMHgyLDB4MyksXzB4MjJhNmJiPV8weDIwOTRkM1tfMHgzZjdmYjAoMHgxY2UpXShfMHgxYmRhZjk9Pl8weDFiZGFmOSoweDIpO18weDIwOGEwOFtfMHgzZjdmYjAoMHgxYzgpXShfMHgyMmE2YmIgaW5zdGFuY2VvZiBfMHgyNTRlMjIpLF8weDIwOGEwOFtfMHgzZjdmYjAoMHgxYzgpXShPYmplY3RbXzB4M2Y3ZmIwKDB4MWM5KV1bJ3RvU3RyaW5nJ11bXzB4M2Y3ZmIwKDB4MWUxKV0od2luZG93KT09PV8weDNmN2ZiMCgweDFlMCkpO2NvbnN0IF8weDVkYWZkZj1FcnJvcjtfMHgyMDhhMDhbXzB4M2Y3ZmIwKDB4MWM4KV0obmV3IF8weDVkYWZkZigpaW5zdGFuY2VvZiBFcnJvciksXzB4MjA4YTA4W18weDNmN2ZiMCgweDFjOCldKF8weDVkYWZkZltfMHgzZjdmYjAoMHgxY2QpXT09PXVuZGVmaW5lZHx8dHlwZW9mIF8weDVkYWZkZlsnY2FwdHVyZVN0YWNrVHJhY2UnXT09PV8weDNmN2ZiMCgweDFjYykpLF8weDIwOGEwOFtfMHgzZjdmYjAoMHgxYzgpXShPYmplY3RbJ2lzU2VhbGVkJ10oTWF0aCkpLF8weDIwOGEwOFtfMHgzZjdmYjAoMHgxYzgpXSgoZnVuY3Rpb24oKXtyZXR1cm4gdGhpczt9KCkpPT09d2luZG93KTtjb25zdCBfMHgyNjEwMWE9ZG9jdW1lbnRbXzB4M2Y3ZmIwKDB4MWM1KV1bXzB4M2Y3ZmIwKDB4MWJiKV0sXzB4MWYzMTU1PV8weDI2MTAxYVtfMHgzZjdmYjAoMHgxYjYpXSxfMHg1N2FkYjg9ZG9jdW1lbnRbXzB4M2Y3ZmIwKDB4MWJjKV0oJ2RpdicpO2RvY3VtZW50W18weDNmN2ZiMCgweDFjNSldW18weDNmN2ZiMCgweDFjYSldKF8weDU3YWRiOCksXzB4MjA4YTA4W18weDNmN2ZiMCgweDFjOCldKF8weDI2MTAxYVtfMHgzZjdmYjAoMHgxYjYpXT09PV8weDFmMzE1NSsweDEpLGRvY3VtZW50Wydib2R5J11bXzB4M2Y3ZmIwKDB4MWNmKV0oXzB4NTdhZGI4KTtjb25zdCBfMHg1NGIwODk9ZG9jdW1lbnRbXzB4M2Y3ZmIwKDB4MWI4KV0oJyonKTtfMHgyMDhhMDhbXzB4M2Y3ZmIwKDB4MWM4KV0oIUFycmF5W18weDNmN2ZiMCgweDFlOCldKF8weDU0YjA4OSkpLF8weDIwOGEwOFtfMHgzZjdmYjAoMHgxYzgpXShfMHg1NGIwODlbXzB4M2Y3ZmIwKDB4MWVhKV1bXzB4M2Y3ZmIwKDB4MWI3KV09PT1fMHgzZjdmYjAoMHgxZGMpKTtjb25zdCBfMHg5MTkzYT1kb2N1bWVudFtfMHgzZjdmYjAoMHgxYmMpXShfMHgzZjdmYjAoMHgxZTcpKTtyZXR1cm4gXzB4MjA4YTA4WydwdXNoJ10oXzB4OTE5M2EgaW5zdGFuY2VvZiBIVE1MRGl2RWxlbWVudCksXzB4MjA4YTA4W18weDNmN2ZiMCgweDFjOCldKEhUTUxEaXZFbGVtZW50W18weDNmN2ZiMCgweDFjOSldaW5zdGFuY2VvZiBIVE1MRWxlbWVudCksXzB4MjA4YTA4W18weDNmN2ZiMCgweDFjOCldKEhUTUxFbGVtZW50Wydwcm90b3R5cGUnXWluc3RhbmNlb2YgRWxlbWVudCksU3RyaW5nKF8weDIwOGEwOFtfMHgzZjdmYjAoMHgxY2UpXShOdW1iZXIpW18weDNmN2ZiMCgweDFlMildKChfMHg1ZTAyZTcsXzB4ZmMzMyk9Pl8weDVlMDJlNytfMHhmYzMzLDB4ZTAyKSk7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHg0YzJjNWQ9XzB4MWVjMGU3O3JldHVybiBTdHJpbmcoW25hdmlnYXRvcltfMHg0YzJjNWQoMHgxYzQpXT09PSEhW10sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MmIxZjM2PV8weDRjMmM1ZCxfMHgzN2Y4YTg9ZG9jdW1lbnRbXzB4MmIxZjM2KDB4MWJjKV0oXzB4MmIxZjM2KDB4MWQzKSk7XzB4MzdmOGE4W18weDJiMWYzNigweDFkMSldPV8weDJiMWYzNigweDFkNSksZG9jdW1lbnRbJ2JvZHknXVtfMHgyYjFmMzYoMHgxY2EpXShfMHgzN2Y4YTgpO2xldCBfMHgxYTkzMWY7cmV0dXJuIF8weDM3ZjhhOFsnY29udGVudFdpbmRvdyddJiZfMHgzN2Y4YThbJ2NvbnRlbnRXaW5kb3cnXVtfMHgyYjFmMzYoMHgxY2IpXSYmXzB4MzdmOGE4W18weDJiMWYzNigweDFkZSldW18weDJiMWYzNigweDFjYildW18weDJiMWYzNigweDFjMSldP18weDFhOTMxZj1fMHgzN2Y4YThbXzB4MmIxZjM2KDB4MWRlKV1bXzB4MmIxZjM2KDB4MWNiKV1bXzB4MmIxZjM2KDB4MWMxKV1bJ3RvU3RyaW5nJ10oKTpfMHgxYTkzMWY9dW5kZWZpbmVkLGRvY3VtZW50W18weDJiMWYzNigweDFjNSldWydyZW1vdmVDaGlsZCddKF8weDM3ZjhhOCksISFfMHgxYTkzMWY7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHgzYzljMjE9XzB4NGMyYzVkLF8weDNjYmY0NT1bXzB4M2M5YzIxKDB4MWU2KSxfMHgzYzljMjEoMHgxZTkpLCdQcm9taXNlJywnUHJveHknLF8weDNjOWMyMSgweDFjMyksXzB4M2M5YzIxKDB4MWQwKSxfMHgzYzljMjEoMHgxZWIpXSxfMHg1Nzg0MWI9T2JqZWN0W18weDNjOWMyMSgweDFjNildKHdpbmRvd1tfMHgzYzljMjEoMHgxZTMpXSlbXzB4M2M5YzIxKDB4MWJhKV0oXzB4NDBkMmUxPT5fMHgzY2JmNDVbXzB4M2M5YzIxKDB4MWJlKV0oXzB4MTMyODhhPT5fMHg0MGQyZTEhPT1fMHgxMzI4OGEmJl8weDQwZDJlMVtfMHgzYzljMjEoMHgxYzIpXSgnXycrXzB4MTMyODhhKSYmd2luZG93W18weDNjOWMyMSgweDFlMyldW18weDQwZDJlMV09PT13aW5kb3dbXzB4M2M5YzIxKDB4MWUzKV1bXzB4MTMyODhhXSkpO3JldHVybiBfMHg1Nzg0MWJbXzB4M2M5YzIxKDB4MWI2KV0+MHgwO30oKSldW18weDRjMmM1ZCgweDFjZSldKE51bWJlcilbXzB4NGMyYzVkKDB4MWUyKV0oKF8weDEyNjk1MixfMHgzMGY4Y2YpPT5fMHgxMjY5NTIrXzB4MzBmOGNmLDB4MWZmYSkpO30oKSldKSxfMHgxZmQzZTI9W10sXzB4MzU4OGVjPXt9LF8weDU2Y2UyMj0nMmQ2Nzg0MGFlZWViNjI0MSc7Zm9yKGxldCBfMHgxNmI2ODI9MHgwO18weDE2YjY4MjxfMHg1NjQxNjNbJ2xlbmd0aCddO18weDE2YjY4MisrKXtjb25zdCBfMHgyNTBkMDg9XzB4NTY0MTYzW18weDE2YjY4Ml07QXJyYXlbXzB4MWVjMGU3KDB4MWU4KV0oXzB4MjUwZDA4KT8oXzB4MWZkM2UyW18weDFlYzBlNygweDFjOCldKF8weDI1MGQwOFsweDBdKSxfMHgyNTBkMDhbXzB4MWVjMGU3KDB4MWI2KV0+MHgxJiZfMHgyMzM5ZDRbXzB4MTZiNjgyXVsweDFdJiYoXzB4MzU4OGVjW18weDIzMzlkNFtfMHgxNmI2ODJdWzB4MF1dPV8weDI1MGQwOFsweDFdKSk6XzB4MWZkM2UyW18weDFlYzBlNygweDFjOCldKF8weDI1MGQwOCk7fWNvbnN0IF8weDI2NDFjOD1BcnJheVtfMHgxZWMwZTcoMHgxZGIpXShKU09OW18weDFlYzBlNygweDFiZildKF8weDM1ODhlYykpW18weDFlYzBlNygweDFjZSldKChfMHg0YWY3NWUsXzB4MTZiYTc4KT0+U3RyaW5nW18weDFlYzBlNygweDFiZCldKF8weDRhZjc1ZVtfMHgxZWMwZTcoMHgxYzApXSgweDApXl8weDU2Y2UyMlsnY2hhckNvZGVBdCddKF8weDE2YmE3OCVfMHg1NmNlMjJbXzB4MWVjMGU3KDB4MWI2KV0pKSlbXzB4MWVjMGU3KDB4MWVlKV0oJycpO2Z1bmN0aW9uIF8weDEwZDQoXzB4MzQ5ZjRkLF8weDViYWNhMSl7Y29uc3QgXzB4NDRhMTE5PV8weDQ0YTEoKTtyZXR1cm4gXzB4MTBkND1mdW5jdGlvbihfMHgxMGQ0YjIsXzB4MjIyN2EzKXtfMHgxMGQ0YjI9XzB4MTBkNGIyLTB4MWIzO2xldCBfMHg0ZDY3NTA9XzB4NDRhMTE5W18weDEwZDRiMl07cmV0dXJuIF8weDRkNjc1MDt9LF8weDEwZDQoXzB4MzQ5ZjRkLF8weDViYWNhMSk7fWZ1bmN0aW9uIF8weDQ0YTEoKXtjb25zdCBfMHg1YzQ1OGI9WydjYXB0dXJlU3RhY2tUcmFjZScsJ21hcCcsJ3JlbW92ZUNoaWxkJywnSlNPTicsJ3NyY2RvYycsJzIyOTI1NzhlclZtcGcnLCdpZnJhbWUnLCc3MTQ0ODlhSEpWS28nLCdEdWNrRHVja0dvXHgyMEZyYXVkXHgyMCZceDIwQWJ1c2UnLCcyODAwMTI2T1VSSFVlJywnYWxsJywnMzY3MjQ5MnZMelZ3UycsJ3VzZXJBZ2VudCcsJzIyMjE2ODhXUERqS1YnLCdmcm9tJywnTm9kZUxpc3QnLCcxNzg2MDkzNTkxODg3JywnY29udGVudFdpbmRvdycsJ2pDOXdVKzRkU3ZKQ2JuSnNvNjhsTERKMmI0RWlUTkFLR2lFT3JFNE84VGs9JywnW29iamVjdFx4MjBXaW5kb3ddJywnY2FsbCcsJ3JlZHVjZScsJ3RvcCcsJzQ1MDAxMDA4UlJMdUdJJywnaUJGWGM0QVNvb2xva1FPN1lPWnBSa3VsVlFGLzZiaTNHVER1UlZaK0tiWT0nLCdBcnJheScsJ2RpdicsJ2lzQXJyYXknLCdPYmplY3QnLCdjb25zdHJ1Y3RvcicsJ1dpbmRvdycsJzI0bnBrdE5ZJywnODM4ZWE4YThiYzFjOTk1YmVkODNkODkzZjAwNzczOWJmYjcyYjEyMmNmNDZmOWQ1YTg3N2NmYmRmZDZhYzRlZHB4anpyJywnam9pbicsJzMzNUFnZkZYVCcsJzgyMTU4V0R5bGhKJywnQktlcmdyN0ZVS2ZhZ3lpN1Ewc1IzQ01qNUxPUXkvUEdzTDRGc3UrSnQrQT0nLCdsZW5ndGgnLCduYW1lJywncXVlcnlTZWxlY3RvckFsbCcsJ3RvU3RyaW5nJywnZmlsdGVyJywnY2hpbGRyZW4nLCdjcmVhdGVFbGVtZW50JywnZnJvbUNoYXJDb2RlJywnc29tZScsJ3N0cmluZ2lmeScsJ2NoYXJDb2RlQXQnLCdnZXQnLCdlbmRzV2l0aCcsJ1N5bWJvbCcsJ3dlYmRyaXZlcicsJ2JvZHknLCdrZXlzJywncGFyc2VJbnQnLCdwdXNoJywncHJvdG90eXBlJywnYXBwZW5kQ2hpbGQnLCdzZWxmJywnZnVuY3Rpb24nXTtfMHg0NGExPWZ1bmN0aW9uKCl7cmV0dXJuIF8weDVjNDU4Yjt9O3JldHVybiBfMHg0NGExKCk7fXJldHVybnsnc2VydmVyX2hhc2hlcyc6W18weDFlYzBlNygweDFiNSksXzB4MWVjMGU3KDB4MWU1KSxfMHgxZWMwZTcoMHgxZGYpXSwnY2xpZW50X2hhc2hlcyc6XzB4MWZkM2UyLCdzaWduYWxzJzp7fSwnbWV0YSc6eyd2JzonNCcsJ2NoYWxsZW5nZV9pZCc6XzB4MWVjMGU3KDB4MWVkKSwndGltZXN0YW1wJzpfMHgxZWMwZTcoMHgxZGQpLCdkZWJ1Zyc6XzB4MjY0MWM4fX07fSkoKQ==", + "browserProbes": ["3598", "8186"], + "browserReduceVectors": [ + { + "seed": 3586, + "booleans": [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1] + }, + { + "seed": 8186, + "booleans": [0, 0, 0] + } + ] + }, + "variant-3.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7ZnVuY3Rpb24gXzB4M2EwMChfMHgxNjQzYjYsXzB4MjZkOTJjKXtjb25zdCBfMHgxNGRlYTM9XzB4MTRkZSgpO3JldHVybiBfMHgzYTAwPWZ1bmN0aW9uKF8weDNhMDBhOCxfMHg1ZGEwNTUpe18weDNhMDBhOD1fMHgzYTAwYTgtMHg2YTtsZXQgXzB4MWI1YjBlPV8weDE0ZGVhM1tfMHgzYTAwYThdO3JldHVybiBfMHgxYjViMGU7fSxfMHgzYTAwKF8weDE2NDNiNixfMHgyNmQ5MmMpO31mdW5jdGlvbiBfMHgxNGRlKCl7Y29uc3QgXzB4NDNhZjQzPVsnUHJveHknLCd1S2E0cFZFYWxqelJiNkZGY3dOM1dlWDMwSkQ3b3JsUEZSWVIyc2wrSlk0PScsJzQ4OTUzN0F5aUpIWicsJ2g4amJ0Jywnc29tZScsJ2FwcGVuZENoaWxkJywna2V5cycsJzcyNzVKb29XdFAnLCdTeW1ib2wnLCdXaW5kb3cnLCdpZnJhbWUnLCdnZXQnLCdjb250ZW50V2luZG93JywnSlNPTicsJzI0NjRYR2N5bGQnLCdhbGwnLCdjcmVhdGVFbGVtZW50Jywnc3RyaW5naWZ5JywnZnJvbScsJ2xlbmd0aCcsJzU4Mk5SV1VBQicsJ3JlbW92ZUNoaWxkJywnMzE5MDVnYXFoWkUnLCcxMzQ4ODg3akxPcXR6Jywnc3JjZG9jJywnZnJvbUNoYXJDb2RlJywnYm9keScsJ09iamVjdCcsJ3RvcCcsJ08ybUJpdU91bm5FSm1LVkFrUllmTENCMmJCTnhsNCtxeWRiSUE4TGxyclU9Jywnam9pbicsJ2NoYXJDb2RlQXQnLCdwdXNoJywnc2VsZicsJ2lubmVySFRNTCcsJ3JlZHVjZScsJ3F1ZXJ5U2VsZWN0b3JBbGwnLCdmZGE1YThjZTMyODgyZDkwJywneko3aHp2M3dHZWlxUzlGcDVDT1lVbE1QUFQvZ3JHRXJ1Z0lTNk9GNmFJcz0nLCc0bEFrb3ZPJywnMjIyODgyOTBXd3RoQU8nLCdmaWx0ZXInLCdEdWNrRHVja0dvXHgyMEZyYXVkXHgyMCZceDIwQWJ1c2UnLCcyMDUxMzUwekZnZ3J6JywnZGl2JywnaXNBcnJheScsJ0FycmF5JywnZW5kc1dpdGgnLCcxNzg2MDkzNTk5MTQ3JywndG9TdHJpbmcnLCcyMTk3MjQ0ckl1a1hLJ107XzB4MTRkZT1mdW5jdGlvbigpe3JldHVybiBfMHg0M2FmNDM7fTtyZXR1cm4gXzB4MTRkZSgpO31jb25zdCBfMHgzOGViYzc9XzB4M2EwMDsoZnVuY3Rpb24oXzB4MzBhMGFhLF8weDIzZGE4Myl7Y29uc3QgXzB4NDI5YzcxPV8weDNhMDAsXzB4NGZkMjdiPV8weDMwYTBhYSgpO3doaWxlKCEhW10pe3RyeXtjb25zdCBfMHgzYmVhMzc9cGFyc2VJbnQoXzB4NDI5YzcxKDB4OTcpKS8weDErLXBhcnNlSW50KF8weDQyOWM3MSgweDhkKSkvMHgyKy1wYXJzZUludChfMHg0MjljNzEoMHg3OSkpLzB4MyooLXBhcnNlSW50KF8weDQyOWM3MSgweDg5KSkvMHg0KStwYXJzZUludChfMHg0MjljNzEoMHg5YykpLzB4NSoocGFyc2VJbnQoXzB4NDI5YzcxKDB4NzYpKS8weDYpKy1wYXJzZUludChfMHg0MjljNzEoMHg5NCkpLzB4NytwYXJzZUludChfMHg0MjljNzEoMHg3MCkpLzB4OCooLXBhcnNlSW50KF8weDQyOWM3MSgweDc4KSkvMHg5KStwYXJzZUludChfMHg0MjljNzEoMHg4YSkpLzB4YTtpZihfMHgzYmVhMzc9PT1fMHgyM2RhODMpYnJlYWs7ZWxzZSBfMHg0ZmQyN2JbJ3B1c2gnXShfMHg0ZmQyN2JbJ3NoaWZ0J10oKSk7fWNhdGNoKF8weDM5NDhiMCl7XzB4NGZkMjdiWydwdXNoJ10oXzB4NGZkMjdiWydzaGlmdCddKCkpO319fShfMHgxNGRlLDB4ZDY0ODcpKTtjb25zdCBfMHgxNzE0ZTU9W1sndWEnLCFbXV0sW18weDM4ZWJjNygweDk4KSwhW11dLFsnaTNqcDAnLCFbXV1dLF8weDNlYTlmNj1hd2FpdCBQcm9taXNlW18weDM4ZWJjNygweDcxKV0oW25hdmlnYXRvclsndXNlckFnZW50J10sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4NWYyZDA1PV8weDM4ZWJjNyxfMHgxYjBmNjU9ZG9jdW1lbnRbXzB4NWYyZDA1KDB4NzIpXShfMHg1ZjJkMDUoMHg4ZSkpO3JldHVybiBfMHgxYjBmNjVbXzB4NWYyZDA1KDB4ODQpXT0nPGxpPjxkaXY+PC9saT48bGk+PC9kaXYnLFN0cmluZygweDVjOCtfMHgxYjBmNjVbXzB4NWYyZDA1KDB4ODQpXVtfMHg1ZjJkMDUoMHg3NSldKl8weDFiMGY2NVtfMHg1ZjJkMDUoMHg4NildKCcqJylbXzB4NWYyZDA1KDB4NzUpXSk7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHg0ZGRmYmU9XzB4MzhlYmM3O3JldHVybiBTdHJpbmcoW25hdmlnYXRvclsnd2ViZHJpdmVyJ109PT0hIVtdLChmdW5jdGlvbigpe2NvbnN0IF8weGIxMmVhYT1fMHgzYTAwLF8weDU0MDVjYT1kb2N1bWVudFtfMHhiMTJlYWEoMHg3MildKF8weGIxMmVhYSgweDZjKSk7XzB4NTQwNWNhW18weGIxMmVhYSgweDdhKV09XzB4YjEyZWFhKDB4OGMpLGRvY3VtZW50Wydib2R5J11bXzB4YjEyZWFhKDB4OWEpXShfMHg1NDA1Y2EpO2xldCBfMHhiY2ZjZjc7cmV0dXJuIF8weDU0MDVjYVtfMHhiMTJlYWEoMHg2ZSldJiZfMHg1NDA1Y2FbXzB4YjEyZWFhKDB4NmUpXVtfMHhiMTJlYWEoMHg4MyldJiZfMHg1NDA1Y2FbXzB4YjEyZWFhKDB4NmUpXVtfMHhiMTJlYWEoMHg4MyldW18weGIxMmVhYSgweDZkKV0/XzB4YmNmY2Y3PV8weDU0MDVjYVsnY29udGVudFdpbmRvdyddW18weGIxMmVhYSgweDgzKV1bXzB4YjEyZWFhKDB4NmQpXVtfMHhiMTJlYWEoMHg5MyldKCk6XzB4YmNmY2Y3PXVuZGVmaW5lZCxkb2N1bWVudFtfMHhiMTJlYWEoMHg3YyldW18weGIxMmVhYSgweDc3KV0oXzB4NTQwNWNhKSwhIV8weGJjZmNmNzt9KCkpLChmdW5jdGlvbigpe2NvbnN0IF8weDM5Y2M3Zj1fMHgzYTAwLF8weDNlZmRmYz1bXzB4MzljYzdmKDB4OTApLF8weDM5Y2M3ZigweDdkKSwnUHJvbWlzZScsXzB4MzljYzdmKDB4OTUpLF8weDM5Y2M3ZigweDZhKSxfMHgzOWNjN2YoMHg2ZiksXzB4MzljYzdmKDB4NmIpXSxfMHg1ZGQzZDE9T2JqZWN0W18weDM5Y2M3ZigweDliKV0od2luZG93W18weDM5Y2M3ZigweDdlKV0pW18weDM5Y2M3ZigweDhiKV0oXzB4NGZhYzM5PT5fMHgzZWZkZmNbXzB4MzljYzdmKDB4OTkpXShfMHg0MTQxY2M9Pl8weDRmYWMzOSE9PV8weDQxNDFjYyYmXzB4NGZhYzM5W18weDM5Y2M3ZigweDkxKV0oJ18nK18weDQxNDFjYykmJndpbmRvd1sndG9wJ11bXzB4NGZhYzM5XT09PXdpbmRvd1tfMHgzOWNjN2YoMHg3ZSldW18weDQxNDFjY10pKTtyZXR1cm4gXzB4NWRkM2QxW18weDM5Y2M3ZigweDc1KV0+MHgwO30oKSldWydtYXAnXShOdW1iZXIpW18weDRkZGZiZSgweDg1KV0oKF8weDIwMmY5NSxfMHgxZDY4MzIpPT5fMHgyMDJmOTUrXzB4MWQ2ODMyLDB4Njg1KSk7fSgpKV0pLF8weDE3NzhmNT1bXSxfMHgzY2NjMTY9e30sXzB4MzEyODVjPV8weDM4ZWJjNygweDg3KTtmb3IobGV0IF8weDE2ZGY4OT0weDA7XzB4MTZkZjg5PF8weDNlYTlmNltfMHgzOGViYzcoMHg3NSldO18weDE2ZGY4OSsrKXtjb25zdCBfMHgzMThkMjM9XzB4M2VhOWY2W18weDE2ZGY4OV07QXJyYXlbXzB4MzhlYmM3KDB4OGYpXShfMHgzMThkMjMpPyhfMHgxNzc4ZjVbJ3B1c2gnXShfMHgzMThkMjNbMHgwXSksXzB4MzE4ZDIzW18weDM4ZWJjNygweDc1KV0+MHgxJiZfMHgxNzE0ZTVbXzB4MTZkZjg5XVsweDFdJiYoXzB4M2NjYzE2W18weDE3MTRlNVtfMHgxNmRmODldWzB4MF1dPV8weDMxOGQyM1sweDFdKSk6XzB4MTc3OGY1W18weDM4ZWJjNygweDgyKV0oXzB4MzE4ZDIzKTt9Y29uc3QgXzB4NDM2ZWJjPUFycmF5W18weDM4ZWJjNygweDc0KV0oSlNPTltfMHgzOGViYzcoMHg3MyldKF8weDNjY2MxNikpWydtYXAnXSgoXzB4MzRmNGFhLF8weDJjMzUwYyk9PlN0cmluZ1tfMHgzOGViYzcoMHg3YildKF8weDM0ZjRhYVsnY2hhckNvZGVBdCddKDB4MCleXzB4MzEyODVjW18weDM4ZWJjNygweDgxKV0oXzB4MmMzNTBjJV8weDMxMjg1Y1tfMHgzOGViYzcoMHg3NSldKSkpW18weDM4ZWJjNygweDgwKV0oJycpO3JldHVybnsnc2VydmVyX2hhc2hlcyc6W18weDM4ZWJjNygweDg4KSxfMHgzOGViYzcoMHg5NiksXzB4MzhlYmM3KDB4N2YpXSwnY2xpZW50X2hhc2hlcyc6XzB4MTc3OGY1LCdzaWduYWxzJzp7fSwnbWV0YSc6eyd2JzonNCcsJ2NoYWxsZW5nZV9pZCc6J2ZiMmIwY2ExYzFmZWQwNTMwZGU2OGMwNzdkZThhYTA1NWFhMWQ1YTZiYzA4NjU3MjdlMDY5NzJhY2Q1ZDQ1ZjJoOGpidCcsJ3RpbWVzdGFtcCc6XzB4MzhlYmM3KDB4OTIpLCdkZWJ1Zyc6XzB4NDM2ZWJjfX07fSkoKQ==", + "browserProbes": ["1567", "1669"], + "browserReduceVectors": [ + { + "seed": 1669, + "booleans": [0, 0, 0] + } + ] + }, + "variant-4.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MzNkM2U1PV8weDJhMjM7KGZ1bmN0aW9uKF8weDMyZDI1NixfMHgzMzQ5MDEpe2NvbnN0IF8weDI4N2EyYj1fMHgyYTIzLF8weDU5MWZmOD1fMHgzMmQyNTYoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4MTFiODUxPS1wYXJzZUludChfMHgyODdhMmIoMHgxZTApKS8weDErLXBhcnNlSW50KF8weDI4N2EyYigweDFlYSkpLzB4MistcGFyc2VJbnQoXzB4Mjg3YTJiKDB4MWYxKSkvMHgzKy1wYXJzZUludChfMHgyODdhMmIoMHgyMDYpKS8weDQqKC1wYXJzZUludChfMHgyODdhMmIoMHgxZmQpKS8weDUpK3BhcnNlSW50KF8weDI4N2EyYigweDIwMSkpLzB4NitwYXJzZUludChfMHgyODdhMmIoMHgxZDYpKS8weDcrcGFyc2VJbnQoXzB4Mjg3YTJiKDB4MWU1KSkvMHg4KigtcGFyc2VJbnQoXzB4Mjg3YTJiKDB4MWUyKSkvMHg5KTtpZihfMHgxMWI4NTE9PT1fMHgzMzQ5MDEpYnJlYWs7ZWxzZSBfMHg1OTFmZjhbJ3B1c2gnXShfMHg1OTFmZjhbJ3NoaWZ0J10oKSk7fWNhdGNoKF8weDM0Njc0ZSl7XzB4NTkxZmY4WydwdXNoJ10oXzB4NTkxZmY4WydzaGlmdCddKCkpO319fShfMHg1Mzg2LDB4YzM0ZWMpKTtjb25zdCBfMHg1MDFjN2M9W1sndWEnLCFbXV0sW18weDMzZDNlNSgweDIwMiksIVtdXSxbXzB4MzNkM2U1KDB4MWQxKSwhW11dXSxfMHgzNWIzNzE9YXdhaXQgUHJvbWlzZVtfMHgzM2QzZTUoMHgyMDcpXShbbmF2aWdhdG9yW18weDMzZDNlNSgweDIwMyldLChmdW5jdGlvbigpe2NvbnN0IF8weDQzNTc5OT1fMHgzM2QzZTUsXzB4MThhNGEyPXdpbmRvd1sndG9wJ10sXzB4Mjk3ODg5PV8weDE4YTRhMltfMHg0MzU3OTkoMHgxZjcpXVtfMHg0MzU3OTkoMHgyMDApXShfMHg0MzU3OTkoMHgxZTYpKTtpZighXzB4Mjk3ODg5KXJldHVybiBTdHJpbmcoMHgxYjM1KTtjb25zdCBfMHgzMDdkNmU9XzB4Mjk3ODg5W18weDQzNTc5OSgweDFkYildfHxfMHgyOTc4ODlbXzB4NDM1Nzk5KDB4MWVjKV0mJl8weDI5Nzg4OVtfMHg0MzU3OTkoMHgxZWMpXVtfMHg0MzU3OTkoMHgxZjcpXTtpZighXzB4MzA3ZDZlKXJldHVybiBTdHJpbmcoMHgxYjM1KTtjb25zdCBfMHgzNzk5YjA9XzB4MzA3ZDZlW18weDQzNTc5OSgweDIwMCldKF8weDQzNTc5OSgweDFkNSkpO2lmKCFfMHgzNzk5YjApcmV0dXJuIFN0cmluZygweDFiMzUpO2NvbnN0IF8weDE3NTZmZT1fMHgzNzk5YjBbXzB4NDM1Nzk5KDB4MWQwKV0oXzB4NDM1Nzk5KDB4MWQ4KSksXzB4NDdlYWYxPV8weDI5Nzg4OVsnZ2V0QXR0cmlidXRlJ10oXzB4NDM1Nzk5KDB4MWZmKSk7cmV0dXJuIFN0cmluZyhbXzB4MTc1NmZlPT09J2RlZmF1bHQtc3JjXHgyMFx4Mjdub25lXHgyNztceDIwc2NyaXB0LXNyY1x4MjBceDI3dW5zYWZlLWlubGluZVx4Mjc7JyxfMHg0N2VhZjE9PT1fMHg0MzU3OTkoMHgxZDIpLF8weDE4YTRhMltfMHg0MzU3OTkoMHgxZDkpXShfMHg0MzU3OTkoMHgxZmMpKSxfMHgxOGE0YTJbXzB4NDM1Nzk5KDB4MWQ5KV0oXzB4NDM1Nzk5KDB4MWZlKSldW18weDQzNTc5OSgweDFlOCldKE51bWJlcilbXzB4NDM1Nzk5KDB4MWUzKV0oKF8weDFkM2Q1ZCxfMHgzNjU4YzMpPT5fMHgxZDNkNWQrXzB4MzY1OGMzLDB4MWIzNSkpO30oKSksKGZ1bmN0aW9uKCl7Y29uc3QgXzB4NWFlM2FiPV8weDMzZDNlNTtyZXR1cm4gU3RyaW5nKFtuYXZpZ2F0b3JbXzB4NWFlM2FiKDB4MWRjKV09PT0hIVtdLChmdW5jdGlvbigpe2NvbnN0IF8weDRiODEyYz1fMHg1YWUzYWIsXzB4MjY4YjU2PWRvY3VtZW50W18weDRiODEyYygweDFmMyldKCdpZnJhbWUnKTtfMHgyNjhiNTZbXzB4NGI4MTJjKDB4MWY0KV09XzB4NGI4MTJjKDB4MWRlKSxkb2N1bWVudFtfMHg0YjgxMmMoMHgxZjApXVtfMHg0YjgxMmMoMHgxZmEpXShfMHgyNjhiNTYpO2xldCBfMHgyMTlmOWM7cmV0dXJuIF8weDI2OGI1NltfMHg0YjgxMmMoMHgxZWMpXSYmXzB4MjY4YjU2Wydjb250ZW50V2luZG93J11bXzB4NGI4MTJjKDB4MWRkKV0mJl8weDI2OGI1NltfMHg0YjgxMmMoMHgxZWMpXVtfMHg0YjgxMmMoMHgxZGQpXVtfMHg0YjgxMmMoMHgxZTkpXT9fMHgyMTlmOWM9XzB4MjY4YjU2W18weDRiODEyYygweDFlYyldW18weDRiODEyYygweDFkZCldW18weDRiODEyYygweDFlOSldW18weDRiODEyYygweDFlNCldKCk6XzB4MjE5ZjljPXVuZGVmaW5lZCxkb2N1bWVudFtfMHg0YjgxMmMoMHgxZjApXVtfMHg0YjgxMmMoMHgxZWUpXShfMHgyNjhiNTYpLCEhXzB4MjE5ZjljO30oKSksKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MWNiMTY2PV8weDVhZTNhYixfMHg0NjM2ZTE9W18weDFjYjE2NigweDFmNiksXzB4MWNiMTY2KDB4MWUxKSxfMHgxY2IxNjYoMHgyMDQpLCdQcm94eScsXzB4MWNiMTY2KDB4MWZiKSxfMHgxY2IxNjYoMHgxZGYpLCdXaW5kb3cnXSxfMHgzMGNiYzk9T2JqZWN0WydrZXlzJ10od2luZG93W18weDFjYjE2NigweDFmOSldKVtfMHgxY2IxNjYoMHgxZWYpXShfMHhjYmVhMWM9Pl8weDQ2MzZlMVsnc29tZSddKF8weDM1Njk2Zj0+XzB4Y2JlYTFjIT09XzB4MzU2OTZmJiZfMHhjYmVhMWNbXzB4MWNiMTY2KDB4MWQzKV0oJ18nK18weDM1Njk2ZikmJndpbmRvd1tfMHgxY2IxNjYoMHgxZjkpXVtfMHhjYmVhMWNdPT09d2luZG93W18weDFjYjE2NigweDFmOSldW18weDM1Njk2Zl0pKTtyZXR1cm4gXzB4MzBjYmM5W18weDFjYjE2NigweDFkNyldPjB4MDt9KCkpXVtfMHg1YWUzYWIoMHgxZTgpXShOdW1iZXIpW18weDVhZTNhYigweDFlMyldKChfMHgyOWZiY2EsXzB4MzExNzY1KT0+XzB4MjlmYmNhK18weDMxMTc2NSwweDFiMmMpKTt9KCkpXSksXzB4NTIyNmQ5PVtdLF8weDJkMDU0MD17fSxfMHhjZGU4Yzk9JzExZThjMjJlODBhNjk5Y2EnO2ZvcihsZXQgXzB4MzExMmE1PTB4MDtfMHgzMTEyYTU8XzB4MzViMzcxW18weDMzZDNlNSgweDFkNyldO18weDMxMTJhNSsrKXtjb25zdCBfMHgzZTVjODk9XzB4MzViMzcxW18weDMxMTJhNV07QXJyYXlbXzB4MzNkM2U1KDB4MWU3KV0oXzB4M2U1Yzg5KT8oXzB4NTIyNmQ5W18weDMzZDNlNSgweDIwNSldKF8weDNlNWM4OVsweDBdKSxfMHgzZTVjODlbXzB4MzNkM2U1KDB4MWQ3KV0+MHgxJiZfMHg1MDFjN2NbXzB4MzExMmE1XVsweDFdJiYoXzB4MmQwNTQwW18weDUwMWM3Y1tfMHgzMTEyYTVdWzB4MF1dPV8weDNlNWM4OVsweDFdKSk6XzB4NTIyNmQ5W18weDMzZDNlNSgweDIwNSldKF8weDNlNWM4OSk7fWNvbnN0IF8weDUzZDRkZj1BcnJheVtfMHgzM2QzZTUoMHgxZDQpXShKU09OWydzdHJpbmdpZnknXShfMHgyZDA1NDApKVsnbWFwJ10oKF8weDVkZDkwZCxfMHg1NzRiMDMpPT5TdHJpbmdbJ2Zyb21DaGFyQ29kZSddKF8weDVkZDkwZFsnY2hhckNvZGVBdCddKDB4MCleXzB4Y2RlOGM5W18weDMzZDNlNSgweDFmOCldKF8weDU3NGIwMyVfMHhjZGU4YzlbXzB4MzNkM2U1KDB4MWQ3KV0pKSlbXzB4MzNkM2U1KDB4MWViKV0oJycpO2Z1bmN0aW9uIF8weDJhMjMoXzB4Mzk2YThkLF8weDRmYTNiMyl7Y29uc3QgXzB4NTM4NjRiPV8weDUzODYoKTtyZXR1cm4gXzB4MmEyMz1mdW5jdGlvbihfMHgyYTIzMTMsXzB4YzljZjliKXtfMHgyYTIzMTM9XzB4MmEyMzEzLTB4MWQwO2xldCBfMHg0N2Q2YmY9XzB4NTM4NjRiW18weDJhMjMxM107cmV0dXJuIF8weDQ3ZDZiZjt9LF8weDJhMjMoXzB4Mzk2YThkLF8weDRmYTNiMyk7fWZ1bmN0aW9uIF8weDUzODYoKXtjb25zdCBfMHgxOWI2NmQ9Wyd0b1N0cmluZycsJzI5MDkzNm1kd29OdCcsJyNqc2EnLCdpc0FycmF5JywnbWFwJywnZ2V0JywnMTE0MTIzOGFNc0VaWCcsJ2pvaW4nLCdjb250ZW50V2luZG93JywnMTRLNmFGakFBSkhXMWhkUzZFOHlRV0JUemdnR2lxSGR0OUNyay9rU1hVMD0nLCdyZW1vdmVDaGlsZCcsJ2ZpbHRlcicsJ2JvZHknLCcxMDE4NUZkR01pYScsJ0ZtcGJUWnh0WXNSc3JvNkkzd2xwUks2dXFobkVVL0NHNEx5a2U2MWhuZUU9JywnY3JlYXRlRWxlbWVudCcsJ3NyY2RvYycsJ25oOW0xdUE5RDNvVUFVdU9HODRSZEV4aE1iZnh4emZHVmVObDVYNk5NNWM9JywnQXJyYXknLCdkb2N1bWVudCcsJ2NoYXJDb2RlQXQnLCd0b3AnLCdhcHBlbmRDaGlsZCcsJ1N5bWJvbCcsJ19fRERHX0JFX1ZFUlNJT05fXycsJzQzNzBHYnNCa2snLCdfX0RER19GRV9DSEFUX0hBU0hfXycsJ3NhbmRib3gnLCdxdWVyeVNlbGVjdG9yJywnMzU5MDU0NGV1eUVaUCcsJ3Z6OTVuJywndXNlckFnZW50JywnUHJvbWlzZScsJ3B1c2gnLCcyOTQ0WWxoT0l1JywnYWxsJywnZ2V0QXR0cmlidXRlJywnaTNqcDAnLCdhbGxvdy1zY3JpcHRzXHgyMGFsbG93LXNhbWUtb3JpZ2luJywnZW5kc1dpdGgnLCdmcm9tJywnbWV0YVtodHRwLWVxdWl2PVx4MjJDb250ZW50LVNlY3VyaXR5LVBvbGljeVx4MjJdJywnMjcyOTYwMXZBdGdJcycsJ2xlbmd0aCcsJ2NvbnRlbnQnLCdoYXNPd25Qcm9wZXJ0eScsJ2UwNDI5OGQxZGIxMjc4MTgwMDk5ODRmNzY0Y2IwMTIxZDFiZmY0Y2RkNTk0MjAyYTU3MWY2ODUwODc5YmRjYzh2ejk1bicsJ2NvbnRlbnREb2N1bWVudCcsJ3dlYmRyaXZlcicsJ3NlbGYnLCdEdWNrRHVja0dvXHgyMEZyYXVkXHgyMCZceDIwQWJ1c2UnLCdKU09OJywnMjIxMjcwWE1IUExTJywnT2JqZWN0JywnOXRlVFRUbicsJ3JlZHVjZSddO18weDUzODY9ZnVuY3Rpb24oKXtyZXR1cm4gXzB4MTliNjZkO307cmV0dXJuIF8weDUzODYoKTt9cmV0dXJueydzZXJ2ZXJfaGFzaGVzJzpbXzB4MzNkM2U1KDB4MWVkKSxfMHgzM2QzZTUoMHgxZjUpLF8weDMzZDNlNSgweDFmMildLCdjbGllbnRfaGFzaGVzJzpfMHg1MjI2ZDksJ3NpZ25hbHMnOnt9LCdtZXRhJzp7J3YnOic0JywnY2hhbGxlbmdlX2lkJzpfMHgzM2QzZTUoMHgxZGEpLCd0aW1lc3RhbXAnOicxNzg2MDkzNjA2Mzk0JywnZGVidWcnOl8weDUzZDRkZn19O30pKCk=", + "browserProbes": ["6969", "6956"], + "browserReduceVectors": [ + { + "seed": 6965, + "booleans": [1, 1, 1, 1] + }, + { + "seed": 6956, + "booleans": [0, 0, 0] + } + ] + }, + "variant-5.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4OTk2NjNiPV8weDNkMTg7ZnVuY3Rpb24gXzB4NTgzMSgpe2NvbnN0IF8weDNjNTAyZj1bJ2Zyb21DaGFyQ29kZScsJ3Njcm9sbEhlaWdodCcsJ0FycmF5Jywnam9pbicsJ29mZnNldEhlaWdodCcsJ1Byb3h5JywnZTY4MjM2NTEwNGJjMjNmOCcsJ3dlYmRyaXZlcicsJ2Zyb20nLCdjb250ZW50V2luZG93JywnZGlzcGxheTppbmxpbmUtYmxvY2s7cGFkZGluZzo4cHg7cG9zaXRpb246YWJzb2x1dGU7dmlzaWJpbGl0eTpoaWRkZW47JywnaTNqcDAnLCdTeW1ib2wnLCdyZWR1Y2UnLCdsZW5ndGgnLCcyc1Z0a1RFWFE5YTdWdTRzSzI4RTloRDU2U0lTYkxCcWxyUVZOWUpsQW9vPScsJ2dldCcsJzc4YTZmMWY5MGU5MzE2NjQ3ODEyNjg2MjcxYWU2ZDMxODYxZDI1NWQyYjY0N2VhZTYxY2Y4MTUwZTk4NTlhNzBxN2tsbScsJ09iamVjdCcsJzg3ODk0cERyd2NDJywncmVtb3ZlQ2hpbGQnLCc5dHVZQnpzJywnaXNBcnJheScsJzhnVXBKem4nLCc2ODE1NjFkQXNQV1UnLCcyODgzNjN1Y0lRUEMnLCd0b3AnLCdjcmVhdGVFbGVtZW50JywnOTg1MzUxMGp3VFdkcCcsJ21hcCcsJ3NyY2RvYycsJ29mZnNldFdpZHRoJywnMTgzMDgxNm9XeWNTUycsJ2Nzc1RleHQnLCc1dGtHRldDJywnV2luZG93JywnYXBwZW5kQ2hpbGQnLCdzdHlsZScsJ2JvZHknLCdlbmRzV2l0aCcsJ2FsbCcsJ3VzZXJBZ2VudCcsJ3dpZHRoJywnc29tZScsJ2RpdicsJzE4MzA5NjlJV0pLb0QnLCcxMGt5ZmFyZicsJ2NoYXJDb2RlQXQnLCdzdHJpbmdpZnknLCdKU09OJywnc2VsZicsJ3B1c2gnLCdxN2tsbScsJzk4MTU3MThzR2NheFgnLCdpZnJhbWUnXTtfMHg1ODMxPWZ1bmN0aW9uKCl7cmV0dXJuIF8weDNjNTAyZjt9O3JldHVybiBfMHg1ODMxKCk7fShmdW5jdGlvbihfMHgzYzI5M2UsXzB4MzA3NWVmKXtjb25zdCBfMHg1MjgyYjY9XzB4M2QxOCxfMHg0YjVmMTM9XzB4M2MyOTNlKCk7d2hpbGUoISFbXSl7dHJ5e2NvbnN0IF8weDJkMzAwYz1wYXJzZUludChfMHg1MjgyYjYoMHhhYykpLzB4MSstcGFyc2VJbnQoXzB4NTI4MmI2KDB4YzIpKS8weDIqKHBhcnNlSW50KF8weDUyODJiNigweGFkKSkvMHgzKStwYXJzZUludChfMHg1MjgyYjYoMHhiNCkpLzB4NCstcGFyc2VJbnQoXzB4NTI4MmI2KDB4YjYpKS8weDUqKC1wYXJzZUludChfMHg1MjgyYjYoMHhhNykpLzB4NikrLXBhcnNlSW50KF8weDUyODJiNigweGMxKSkvMHg3KigtcGFyc2VJbnQoXzB4NTI4MmI2KDB4YWIpKS8weDgpK3BhcnNlSW50KF8weDUyODJiNigweGE5KSkvMHg5KigtcGFyc2VJbnQoXzB4NTI4MmI2KDB4YjApKS8weGEpK3BhcnNlSW50KF8weDUyODJiNigweDkyKSkvMHhiO2lmKF8weDJkMzAwYz09PV8weDMwNzVlZilicmVhaztlbHNlIF8weDRiNWYxM1sncHVzaCddKF8weDRiNWYxM1snc2hpZnQnXSgpKTt9Y2F0Y2goXzB4NGM3NzllKXtfMHg0YjVmMTNbJ3B1c2gnXShfMHg0YjVmMTNbJ3NoaWZ0J10oKSk7fX19KF8weDU4MzEsMHhjZDg4NykpO2NvbnN0IF8weDVjYjlmZj1bWyd1YScsIVtdXSxbXzB4OTk2NjNiKDB4OTEpLCFbXV0sW18weDk5NjYzYigweDlmKSwhW11dXSxfMHg1OWM3ODU9YXdhaXQgUHJvbWlzZVtfMHg5OTY2M2IoMHhiYyldKFtuYXZpZ2F0b3JbXzB4OTk2NjNiKDB4YmQpXSwoZnVuY3Rpb24oKXtjb25zdCBfMHgzNjQ4ZjY9XzB4OTk2NjNiLF8weDRmMzYzMD1bXSxfMHgzZmY3MWE9ZG9jdW1lbnRbXzB4MzY0OGY2KDB4YWYpXShfMHgzNjQ4ZjYoMHhjMCkpO18weDNmZjcxYVtfMHgzNjQ4ZjYoMHhiOSldW18weDM2NDhmNigweGI1KV09XzB4MzY0OGY2KDB4OWUpLF8weDNmZjcxYVsndGV4dENvbnRlbnQnXT0neCcsZG9jdW1lbnRbXzB4MzY0OGY2KDB4YmEpXVtfMHgzNjQ4ZjYoMHhiOCldKF8weDNmZjcxYSksXzB4NGYzNjMwW18weDM2NDhmNigweDkwKV0oXzB4M2ZmNzFhW18weDM2NDhmNigweGIzKV0+MHgwKSxfMHg0ZjM2MzBbXzB4MzY0OGY2KDB4OTApXShfMHgzZmY3MWFbXzB4MzY0OGY2KDB4OTgpXT4weDApO2NvbnN0IF8weDI1NTA5Yj1fMHgzZmY3MWFbJ2dldEJvdW5kaW5nQ2xpZW50UmVjdCddKCk7XzB4NGYzNjMwW18weDM2NDhmNigweDkwKV0oXzB4MjU1MDliW18weDM2NDhmNigweGJlKV0+MHgwJiZfMHgyNTUwOWJbJ2hlaWdodCddPjB4MCk7Y29uc3QgXzB4NTdlMDY5PWdldENvbXB1dGVkU3R5bGUoXzB4M2ZmNzFhKTtyZXR1cm4gXzB4NGYzNjMwW18weDM2NDhmNigweDkwKV0oXzB4NTdlMDY5WydnZXRQcm9wZXJ0eVZhbHVlJ10oJ2Rpc3BsYXknKVsnbGVuZ3RoJ10+MHgwKSxfMHg0ZjM2MzBbXzB4MzY0OGY2KDB4OTApXShfMHgzZmY3MWFbXzB4MzY0OGY2KDB4OTUpXT4weDApLGRvY3VtZW50W18weDM2NDhmNigweGJhKV1bXzB4MzY0OGY2KDB4YTgpXShfMHgzZmY3MWEpLFN0cmluZyhfMHg0ZjM2MzBbJ21hcCddKE51bWJlcilbXzB4MzY0OGY2KDB4YTEpXSgoXzB4YTkzM2E0LF8weDJmZTVjZik9Pl8weGE5MzNhNCtfMHgyZmU1Y2YsMHg1OTMpKTt9KCkpLChmdW5jdGlvbigpe2NvbnN0IF8weDQ2YmJmMD1fMHg5OTY2M2I7cmV0dXJuIFN0cmluZyhbbmF2aWdhdG9yW18weDQ2YmJmMCgweDliKV09PT0hIVtdLChmdW5jdGlvbigpe2NvbnN0IF8weDE2N2JhOT1fMHg0NmJiZjAsXzB4NDg4ZTJhPWRvY3VtZW50W18weDE2N2JhOSgweGFmKV0oXzB4MTY3YmE5KDB4OTMpKTtfMHg0ODhlMmFbXzB4MTY3YmE5KDB4YjIpXT0nRHVja0R1Y2tHb1x4MjBGcmF1ZFx4MjAmXHgyMEFidXNlJyxkb2N1bWVudFsnYm9keSddW18weDE2N2JhOSgweGI4KV0oXzB4NDg4ZTJhKTtsZXQgXzB4NDJkNmNiO3JldHVybiBfMHg0ODhlMmFbXzB4MTY3YmE5KDB4OWQpXSYmXzB4NDg4ZTJhW18weDE2N2JhOSgweDlkKV1bJ3NlbGYnXSYmXzB4NDg4ZTJhW18weDE2N2JhOSgweDlkKV1bXzB4MTY3YmE5KDB4OGYpXVtfMHgxNjdiYTkoMHhhNCldP18weDQyZDZjYj1fMHg0ODhlMmFbXzB4MTY3YmE5KDB4OWQpXVtfMHgxNjdiYTkoMHg4ZildWydnZXQnXVsndG9TdHJpbmcnXSgpOl8weDQyZDZjYj11bmRlZmluZWQsZG9jdW1lbnRbXzB4MTY3YmE5KDB4YmEpXVsncmVtb3ZlQ2hpbGQnXShfMHg0ODhlMmEpLCEhXzB4NDJkNmNiO30oKSksKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MWNhMGQ4PV8weDQ2YmJmMCxfMHg0ODRlNDU9W18weDFjYTBkOCgweDk2KSxfMHgxY2EwZDgoMHhhNiksJ1Byb21pc2UnLF8weDFjYTBkOCgweDk5KSxfMHgxY2EwZDgoMHhhMCksXzB4MWNhMGQ4KDB4YzUpLF8weDFjYTBkOCgweGI3KV0sXzB4MjQ0OTJlPU9iamVjdFsna2V5cyddKHdpbmRvd1tfMHgxY2EwZDgoMHhhZSldKVsnZmlsdGVyJ10oXzB4NDc4MWU5PT5fMHg0ODRlNDVbXzB4MWNhMGQ4KDB4YmYpXShfMHg0NjJjNTg9Pl8weDQ3ODFlOSE9PV8weDQ2MmM1OCYmXzB4NDc4MWU5W18weDFjYTBkOCgweGJiKV0oJ18nK18weDQ2MmM1OCkmJndpbmRvd1tfMHgxY2EwZDgoMHhhZSldW18weDQ3ODFlOV09PT13aW5kb3dbXzB4MWNhMGQ4KDB4YWUpXVtfMHg0NjJjNThdKSk7cmV0dXJuIF8weDI0NDkyZVtfMHgxY2EwZDgoMHhhMildPjB4MDt9KCkpXVsnbWFwJ10oTnVtYmVyKVtfMHg0NmJiZjAoMHhhMSldKChfMHgzMmVmNWIsXzB4M2IyMGQ4KT0+XzB4MzJlZjViK18weDNiMjBkOCwweDFmOGEpKTt9KCkpXSksXzB4NDRiMzY4PVtdLF8weDNiYTM3OD17fSxfMHgxYzQyMDk9XzB4OTk2NjNiKDB4OWEpO2Z1bmN0aW9uIF8weDNkMTgoXzB4NDM4M2Q3LF8weDM2MGVkYSl7Y29uc3QgXzB4NTgzMWU4PV8weDU4MzEoKTtyZXR1cm4gXzB4M2QxOD1mdW5jdGlvbihfMHgzZDE4M2QsXzB4MjU0NGQ3KXtfMHgzZDE4M2Q9XzB4M2QxODNkLTB4OGY7bGV0IF8weDNlZjJlZj1fMHg1ODMxZThbXzB4M2QxODNkXTtyZXR1cm4gXzB4M2VmMmVmO30sXzB4M2QxOChfMHg0MzgzZDcsXzB4MzYwZWRhKTt9Zm9yKGxldCBfMHgxNzc3MDE9MHgwO18weDE3NzcwMTxfMHg1OWM3ODVbJ2xlbmd0aCddO18weDE3NzcwMSsrKXtjb25zdCBfMHgyN2I1NGY9XzB4NTljNzg1W18weDE3NzcwMV07QXJyYXlbXzB4OTk2NjNiKDB4YWEpXShfMHgyN2I1NGYpPyhfMHg0NGIzNjhbJ3B1c2gnXShfMHgyN2I1NGZbMHgwXSksXzB4MjdiNTRmW18weDk5NjYzYigweGEyKV0+MHgxJiZfMHg1Y2I5ZmZbXzB4MTc3NzAxXVsweDFdJiYoXzB4M2JhMzc4W18weDVjYjlmZltfMHgxNzc3MDFdWzB4MF1dPV8weDI3YjU0ZlsweDFdKSk6XzB4NDRiMzY4W18weDk5NjYzYigweDkwKV0oXzB4MjdiNTRmKTt9Y29uc3QgXzB4MjExNzg5PUFycmF5W18weDk5NjYzYigweDljKV0oSlNPTltfMHg5OTY2M2IoMHhjNCldKF8weDNiYTM3OCkpW18weDk5NjYzYigweGIxKV0oKF8weDllYTliMyxfMHg1YjJmZDQpPT5TdHJpbmdbXzB4OTk2NjNiKDB4OTQpXShfMHg5ZWE5YjNbXzB4OTk2NjNiKDB4YzMpXSgweDApXl8weDFjNDIwOVsnY2hhckNvZGVBdCddKF8weDViMmZkNCVfMHgxYzQyMDlbXzB4OTk2NjNiKDB4YTIpXSkpKVtfMHg5OTY2M2IoMHg5NyldKCcnKTtyZXR1cm57J3NlcnZlcl9oYXNoZXMnOlsnNk1Hemw3blpUT3oxYjQwK1FydmNNVzRJUzNqVUhJaGlvTlRlNDFoejI3Zz0nLCdqVHZYU09pNWNmRDBIM2pyc0lYSGNudVB4UDBxZkRtMlI0c3ZITS9ycWZvPScsXzB4OTk2NjNiKDB4YTMpXSwnY2xpZW50X2hhc2hlcyc6XzB4NDRiMzY4LCdzaWduYWxzJzp7fSwnbWV0YSc6eyd2JzonNCcsJ2NoYWxsZW5nZV9pZCc6XzB4OTk2NjNiKDB4YTUpLCd0aW1lc3RhbXAnOicxNzg2MDkzNjE0NDAwJywnZGVidWcnOl8weDIxMTc4OX19O30pKCk=", + "browserProbes": ["1432", "8074"], + "browserReduceVectors": [ + { + "seed": 1427, + "booleans": [1, 1, 1, 1, 1] + }, + { + "seed": 8074, + "booleans": [0, 0, 0] + } + ] + }, + "variant-6.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MmIwODY0PV8weDRjZjU7KGZ1bmN0aW9uKF8weDgyMmFjMSxfMHg0NDFmMzApe2NvbnN0IF8weDQxZjQ3Yz1fMHg0Y2Y1LF8weDE4MDE4Yz1fMHg4MjJhYzEoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4MzUxODAwPS1wYXJzZUludChfMHg0MWY0N2MoMHgxN2IpKS8weDErLXBhcnNlSW50KF8weDQxZjQ3YygweDE2MikpLzB4MistcGFyc2VJbnQoXzB4NDFmNDdjKDB4MTYwKSkvMHgzK3BhcnNlSW50KF8weDQxZjQ3YygweDE2YykpLzB4NCooLXBhcnNlSW50KF8weDQxZjQ3YygweDE3OSkpLzB4NSkrcGFyc2VJbnQoXzB4NDFmNDdjKDB4MTU0KSkvMHg2K3BhcnNlSW50KF8weDQxZjQ3YygweDE2OCkpLzB4NytwYXJzZUludChfMHg0MWY0N2MoMHgxNmEpKS8weDgqKHBhcnNlSW50KF8weDQxZjQ3YygweDE2MSkpLzB4OSk7aWYoXzB4MzUxODAwPT09XzB4NDQxZjMwKWJyZWFrO2Vsc2UgXzB4MTgwMThjWydwdXNoJ10oXzB4MTgwMThjWydzaGlmdCddKCkpO31jYXRjaChfMHg0YTRlYjMpe18weDE4MDE4Y1sncHVzaCddKF8weDE4MDE4Y1snc2hpZnQnXSgpKTt9fX0oXzB4YzE5OCwweGNlYzNjKSk7Y29uc3QgXzB4MjYwMjI5PVtbJ3VhJywhW11dLFsndno5NW4nLCFbXV0sWydpM2pwMCcsIVtdXV0sXzB4MjY0NWZmPWF3YWl0IFByb21pc2VbJ2FsbCddKFtuYXZpZ2F0b3JbXzB4MmIwODY0KDB4MTc0KV0sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4MzIxY2FlPV8weDJiMDg2NCxfMHg0MTA1OTA9d2luZG93W18weDMyMWNhZSgweDE2MyldLF8weGI2YWM1MD1fMHg0MTA1OTBbXzB4MzIxY2FlKDB4MTgwKV1bJ3F1ZXJ5U2VsZWN0b3InXShfMHgzMjFjYWUoMHgxNTgpKTtpZighXzB4YjZhYzUwKXJldHVybiBTdHJpbmcoMHgxMzYxKTtjb25zdCBfMHg0MjM2NmE9XzB4YjZhYzUwW18weDMyMWNhZSgweDE1OSldfHxfMHhiNmFjNTBbXzB4MzIxY2FlKDB4MTc4KV0mJl8weGI2YWM1MFtfMHgzMjFjYWUoMHgxNzgpXVtfMHgzMjFjYWUoMHgxODApXTtpZighXzB4NDIzNjZhKXJldHVybiBTdHJpbmcoMHgxMzYxKTtjb25zdCBfMHg1MzZiZjc9XzB4NDIzNjZhW18weDMyMWNhZSgweDE1ZSldKCdtZXRhW2h0dHAtZXF1aXY9XHgyMkNvbnRlbnQtU2VjdXJpdHktUG9saWN5XHgyMl0nKTtpZighXzB4NTM2YmY3KXJldHVybiBTdHJpbmcoMHgxMzYxKTtjb25zdCBfMHgxMjQwOWY9XzB4NTM2YmY3W18weDMyMWNhZSgweDE1ZCldKF8weDMyMWNhZSgweDE3YSkpLF8weDM2NTZhYj1fMHhiNmFjNTBbXzB4MzIxY2FlKDB4MTVkKV0oXzB4MzIxY2FlKDB4MTZkKSk7cmV0dXJuIFN0cmluZyhbXzB4MTI0MDlmPT09XzB4MzIxY2FlKDB4MTdmKSxfMHgzNjU2YWI9PT0nYWxsb3ctc2NyaXB0c1x4MjBhbGxvdy1zYW1lLW9yaWdpbicsXzB4NDEwNTkwW18weDMyMWNhZSgweDE3YyldKF8weDMyMWNhZSgweDE3MCkpLF8weDQxMDU5MFtfMHgzMjFjYWUoMHgxN2MpXShfMHgzMjFjYWUoMHgxNmIpKV1bXzB4MzIxY2FlKDB4MTc3KV0oTnVtYmVyKVsncmVkdWNlJ10oKF8weDVkMThhOSxfMHgxODIxODApPT5fMHg1ZDE4YTkrXzB4MTgyMTgwLDB4MTM2MSkpO30oKSksKGZ1bmN0aW9uKCl7Y29uc3QgXzB4NWMyZmI3PV8weDJiMDg2NDtyZXR1cm4gU3RyaW5nKFtuYXZpZ2F0b3JbXzB4NWMyZmI3KDB4MTVjKV09PT0hIVtdLChmdW5jdGlvbigpe2NvbnN0IF8weDFjMmNkND1fMHg1YzJmYjcsXzB4MTBlZTI0PWRvY3VtZW50W18weDFjMmNkNCgweDE1NyldKF8weDFjMmNkNCgweDE3ZSkpO18weDEwZWUyNFtfMHgxYzJjZDQoMHgxNjQpXT1fMHgxYzJjZDQoMHgxNjYpLGRvY3VtZW50W18weDFjMmNkNCgweDE4MildWydhcHBlbmRDaGlsZCddKF8weDEwZWUyNCk7bGV0IF8weDJmNjIyNztyZXR1cm4gXzB4MTBlZTI0W18weDFjMmNkNCgweDE3OCldJiZfMHgxMGVlMjRbJ2NvbnRlbnRXaW5kb3cnXVtfMHgxYzJjZDQoMHgxNTIpXSYmXzB4MTBlZTI0Wydjb250ZW50V2luZG93J11bXzB4MWMyY2Q0KDB4MTUyKV1bJ2dldCddP18weDJmNjIyNz1fMHgxMGVlMjRbXzB4MWMyY2Q0KDB4MTc4KV1bXzB4MWMyY2Q0KDB4MTUyKV1bJ2dldCddW18weDFjMmNkNCgweDE1NildKCk6XzB4MmY2MjI3PXVuZGVmaW5lZCxkb2N1bWVudFsnYm9keSddWydyZW1vdmVDaGlsZCddKF8weDEwZWUyNCksISFfMHgyZjYyMjc7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHg1MGZjYWI9XzB4NWMyZmI3LF8weDMyNzg1OD1bJ0FycmF5JyxfMHg1MGZjYWIoMHgxNzMpLCdQcm9taXNlJyxfMHg1MGZjYWIoMHgxNjcpLF8weDUwZmNhYigweDE1YSksJ0pTT04nLF8weDUwZmNhYigweDE1MyldLF8weDI1OWIxMj1PYmplY3RbJ2tleXMnXSh3aW5kb3dbXzB4NTBmY2FiKDB4MTYzKV0pW18weDUwZmNhYigweDE2ZildKF8weDlkOTE0NT0+XzB4MzI3ODU4W18weDUwZmNhYigweDE3MildKF8weDUwZjI4YT0+XzB4OWQ5MTQ1IT09XzB4NTBmMjhhJiZfMHg5ZDkxNDVbXzB4NTBmY2FiKDB4MTVmKV0oJ18nK18weDUwZjI4YSkmJndpbmRvd1tfMHg1MGZjYWIoMHgxNjMpXVtfMHg5ZDkxNDVdPT09d2luZG93W18weDUwZmNhYigweDE2MyldW18weDUwZjI4YV0pKTtyZXR1cm4gXzB4MjU5YjEyWydsZW5ndGgnXT4weDA7fSgpKV1bXzB4NWMyZmI3KDB4MTc3KV0oTnVtYmVyKVsncmVkdWNlJ10oKF8weDM4NTEzMSxfMHg1NWM0OTQpPT5fMHgzODUxMzErXzB4NTVjNDk0LDB4MjcwYykpO30oKSldKSxfMHg4YTk2MTE9W10sXzB4NGZmZmE4PXt9LF8weDEwMDdhMj1fMHgyYjA4NjQoMHgxODMpO2Z1bmN0aW9uIF8weDRjZjUoXzB4NGQ4MTg2LF8weDUzODA2ZSl7Y29uc3QgXzB4YzE5OGQ1PV8weGMxOTgoKTtyZXR1cm4gXzB4NGNmNT1mdW5jdGlvbihfMHg0Y2Y1ZjUsXzB4ZWYwZjczKXtfMHg0Y2Y1ZjU9XzB4NGNmNWY1LTB4MTUyO2xldCBfMHg1Njk0Nzk9XzB4YzE5OGQ1W18weDRjZjVmNV07cmV0dXJuIF8weDU2OTQ3OTt9LF8weDRjZjUoXzB4NGQ4MTg2LF8weDUzODA2ZSk7fWZ1bmN0aW9uIF8weGMxOTgoKXtjb25zdCBfMHg0YTQ5NWI9WydjcmVhdGVFbGVtZW50JywnI2pzYScsJ2NvbnRlbnREb2N1bWVudCcsJ1N5bWJvbCcsJ2xlbmd0aCcsJ3dlYmRyaXZlcicsJ2dldEF0dHJpYnV0ZScsJ3F1ZXJ5U2VsZWN0b3InLCdlbmRzV2l0aCcsJzk5OTcyM3pGZWl4ZCcsJzM2dmVuZ1hVJywnMTAxMzgwNkNmcHB4dycsJ3RvcCcsJ3NyY2RvYycsJ3JSMW93aUNwN3d3cmFONmhzWUxIK0VjT3F0NEVVK0hjSFl3VDk4Z041dG89JywnRHVja0R1Y2tHb1x4MjBGcmF1ZFx4MjAmXHgyMEFidXNlJywnUHJveHknLCc0MTgwNDI4THNydm5zJywnam9pbicsJzU3MTI4MjRJdnpuUksnLCdfX0RER19GRV9DSEFUX0hBU0hfXycsJzg1NDc3NmxOWWppSicsJ3NhbmRib3gnLCcxNzg2MDkzNjIxNjExJywnZmlsdGVyJywnX19EREdfQkVfVkVSU0lPTl9fJywnY2hhckNvZGVBdCcsJ3NvbWUnLCdPYmplY3QnLCd1c2VyQWdlbnQnLCdmcm9tJywneGlBK21YY3BCaXFJOGtleHhTdEJzT2JRbEF2OS9hVWw3OC9HY01GSzdzUT0nLCdtYXAnLCdjb250ZW50V2luZG93JywnMTBQQ0JNUW8nLCdjb250ZW50JywnMTQwMTA3OFJBWUVDSScsJ2hhc093blByb3BlcnR5JywnaXNBcnJheScsJ2lmcmFtZScsJ2RlZmF1bHQtc3JjXHgyMFx4Mjdub25lXHgyNztceDIwc2NyaXB0LXNyY1x4MjBceDI3dW5zYWZlLWlubGluZVx4Mjc7JywnZG9jdW1lbnQnLCdzdHJpbmdpZnknLCdib2R5JywnYWZmY2ZhZWEzZjI1MGY5ZCcsJ3NlbGYnLCdXaW5kb3cnLCczNzE0MTJyWGxucW8nLCdmcm9tQ2hhckNvZGUnLCd0b1N0cmluZyddO18weGMxOTg9ZnVuY3Rpb24oKXtyZXR1cm4gXzB4NGE0OTViO307cmV0dXJuIF8weGMxOTgoKTt9Zm9yKGxldCBfMHg1ZDhmZjQ9MHgwO18weDVkOGZmNDxfMHgyNjQ1ZmZbXzB4MmIwODY0KDB4MTViKV07XzB4NWQ4ZmY0Kyspe2NvbnN0IF8weDIxYjMwMz1fMHgyNjQ1ZmZbXzB4NWQ4ZmY0XTtBcnJheVtfMHgyYjA4NjQoMHgxN2QpXShfMHgyMWIzMDMpPyhfMHg4YTk2MTFbJ3B1c2gnXShfMHgyMWIzMDNbMHgwXSksXzB4MjFiMzAzW18weDJiMDg2NCgweDE1YildPjB4MSYmXzB4MjYwMjI5W18weDVkOGZmNF1bMHgxXSYmKF8weDRmZmZhOFtfMHgyNjAyMjlbXzB4NWQ4ZmY0XVsweDBdXT1fMHgyMWIzMDNbMHgxXSkpOl8weDhhOTYxMVsncHVzaCddKF8weDIxYjMwMyk7fWNvbnN0IF8weDRmMWU3MT1BcnJheVtfMHgyYjA4NjQoMHgxNzUpXShKU09OW18weDJiMDg2NCgweDE4MSldKF8weDRmZmZhOCkpWydtYXAnXSgoXzB4NDg4ZTEyLF8weDJlNjE3NCk9PlN0cmluZ1tfMHgyYjA4NjQoMHgxNTUpXShfMHg0ODhlMTJbXzB4MmIwODY0KDB4MTcxKV0oMHgwKV5fMHgxMDA3YTJbXzB4MmIwODY0KDB4MTcxKV0oXzB4MmU2MTc0JV8weDEwMDdhMltfMHgyYjA4NjQoMHgxNWIpXSkpKVtfMHgyYjA4NjQoMHgxNjkpXSgnJyk7cmV0dXJueydzZXJ2ZXJfaGFzaGVzJzpbXzB4MmIwODY0KDB4MTY1KSwnWVpxUkRtc1RGU1ZQdFY1QmlhYVRhY0syQmczVXZHZHNNRjlYNTlGSUQwZz0nLF8weDJiMDg2NCgweDE3NildLCdjbGllbnRfaGFzaGVzJzpfMHg4YTk2MTEsJ3NpZ25hbHMnOnt9LCdtZXRhJzp7J3YnOic0JywnY2hhbGxlbmdlX2lkJzonOGQ2MzhjODExNDcxODllMzRjOTRlMzdjOWI4NDRhZjI1NmFhZTQxNzcyMzY1ZDc2MGFmNWEwYzQ3ZGRmMTFmMXZ6OTVuJywndGltZXN0YW1wJzpfMHgyYjA4NjQoMHgxNmUpLCdkZWJ1Zyc6XzB4NGYxZTcxfX07fSkoKQ==", + "browserProbes": ["4965", "9996"], + "browserReduceVectors": [ + { + "seed": 4961, + "booleans": [1, 1, 1, 1] + }, + { + "seed": 9996, + "booleans": [0, 0, 0] + } + ] + }, + "variant-7.js": { + "challengeBase64": "KGFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgXzB4MzNmOTMyPV8weDM1OWQ7ZnVuY3Rpb24gXzB4MzU5ZChfMHhiMmYxMGMsXzB4NWRjNDQ1KXtjb25zdCBfMHg4MDYxYzY9XzB4ODA2MSgpO3JldHVybiBfMHgzNTlkPWZ1bmN0aW9uKF8weDM1OWRkMCxfMHgxNWU5YTIpe18weDM1OWRkMD1fMHgzNTlkZDAtMHgxYTA7bGV0IF8weDFhZjM0ZT1fMHg4MDYxYzZbXzB4MzU5ZGQwXTtyZXR1cm4gXzB4MWFmMzRlO30sXzB4MzU5ZChfMHhiMmYxMGMsXzB4NWRjNDQ1KTt9KGZ1bmN0aW9uKF8weDMyZWU3YSxfMHg1NjUyMWYpe2NvbnN0IF8weDE1OTYxZj1fMHgzNTlkLF8weDJkZTgzOD1fMHgzMmVlN2EoKTt3aGlsZSghIVtdKXt0cnl7Y29uc3QgXzB4NTUyOGUxPS1wYXJzZUludChfMHgxNTk2MWYoMHgxYmYpKS8weDEqKHBhcnNlSW50KF8weDE1OTYxZigweDFiNSkpLzB4MikrcGFyc2VJbnQoXzB4MTU5NjFmKDB4MWI0KSkvMHgzKigtcGFyc2VJbnQoXzB4MTU5NjFmKDB4MWJiKSkvMHg0KStwYXJzZUludChfMHgxNTk2MWYoMHgxYjApKS8weDUrLXBhcnNlSW50KF8weDE1OTYxZigweDFkYikpLzB4NiooLXBhcnNlSW50KF8weDE1OTYxZigweDFkYykpLzB4NykrcGFyc2VJbnQoXzB4MTU5NjFmKDB4MWNhKSkvMHg4K3BhcnNlSW50KF8weDE1OTYxZigweDFkNSkpLzB4OStwYXJzZUludChfMHgxNTk2MWYoMHgxZDcpKS8weGE7aWYoXzB4NTUyOGUxPT09XzB4NTY1MjFmKWJyZWFrO2Vsc2UgXzB4MmRlODM4WydwdXNoJ10oXzB4MmRlODM4WydzaGlmdCddKCkpO31jYXRjaChfMHgyM2Y1YWQpe18weDJkZTgzOFsncHVzaCddKF8weDJkZTgzOFsnc2hpZnQnXSgpKTt9fX0oXzB4ODA2MSwweGExMzhlKSk7Y29uc3QgXzB4ODE3MTdkPVtbJ3VhJywhW11dLFtfMHgzM2Y5MzIoMHgxY2UpLCFbXV0sW18weDMzZjkzMigweDFjOSksIVtdXV0sXzB4NWE4MDIwPWF3YWl0IFByb21pc2VbXzB4MzNmOTMyKDB4MWM2KV0oW25hdmlnYXRvcltfMHgzM2Y5MzIoMHgxYTcpXSwoZnVuY3Rpb24oKXtjb25zdCBfMHgzM2ExODA9XzB4MzNmOTMyLF8weGRhMWQ1NT1bXSxfMHgzNGQ3OWY9d2luZG93W18weDMzYTE4MCgweDFiOSldO18weGRhMWQ1NVtfMHgzM2ExODAoMHgxY2IpXShfMHgzNGQ3OWZbXzB4MzNhMTgwKDB4MWQyKV0oKVtfMHgzM2ExODAoMHgxZGQpXShfMHgzM2ExODAoMHgxYWIpKSk7Y2xhc3MgXzB4NTQzY2U0IGV4dGVuZHMgQXJyYXl7fWNvbnN0IF8weDRlNTc2MT1uZXcgXzB4NTQzY2U0KDB4MSwweDIsMHgzKSxfMHhjZmY5ZjQ9XzB4NGU1NzYxW18weDMzYTE4MCgweDFhYyldKF8weDlkMDZjNT0+XzB4OWQwNmM1KjB4Mik7XzB4ZGExZDU1WydwdXNoJ10oXzB4Y2ZmOWY0IGluc3RhbmNlb2YgXzB4NTQzY2U0KSxfMHhkYTFkNTVbXzB4MzNhMTgwKDB4MWNiKV0oT2JqZWN0Wydwcm90b3R5cGUnXVtfMHgzM2ExODAoMHgxZDIpXVtfMHgzM2ExODAoMHgxZGEpXSh3aW5kb3cpPT09XzB4MzNhMTgwKDB4MWFhKSk7Y29uc3QgXzB4MWI2MDhlPUVycm9yO18weGRhMWQ1NVsncHVzaCddKG5ldyBfMHgxYjYwOGUoKWluc3RhbmNlb2YgRXJyb3IpLF8weGRhMWQ1NVtfMHgzM2ExODAoMHgxY2IpXShfMHgxYjYwOGVbXzB4MzNhMTgwKDB4MWMxKV09PT11bmRlZmluZWR8fHR5cGVvZiBfMHgxYjYwOGVbXzB4MzNhMTgwKDB4MWMxKV09PT1fMHgzM2ExODAoMHgxYzMpKSxfMHhkYTFkNTVbXzB4MzNhMTgwKDB4MWNiKV0oT2JqZWN0W18weDMzYTE4MCgweDFiOCldKE1hdGgpKSxfMHhkYTFkNTVbJ3B1c2gnXSgoZnVuY3Rpb24oKXtyZXR1cm4gdGhpczt9KCkpPT09d2luZG93KTtjb25zdCBfMHgyYmI1NTY9ZG9jdW1lbnRbXzB4MzNhMTgwKDB4MWIzKV1bXzB4MzNhMTgwKDB4MWM3KV0sXzB4NTE4NzQzPV8weDJiYjU1NltfMHgzM2ExODAoMHgxYTgpXSxfMHgyZjNjNzc9ZG9jdW1lbnRbJ2NyZWF0ZUVsZW1lbnQnXSgnZGl2Jyk7ZG9jdW1lbnRbXzB4MzNhMTgwKDB4MWIzKV1bXzB4MzNhMTgwKDB4MWNmKV0oXzB4MmYzYzc3KSxfMHhkYTFkNTVbJ3B1c2gnXShfMHgyYmI1NTZbXzB4MzNhMTgwKDB4MWE4KV09PT1fMHg1MTg3NDMrMHgxKSxkb2N1bWVudFtfMHgzM2ExODAoMHgxYjMpXVtfMHgzM2ExODAoMHgxYmMpXShfMHgyZjNjNzcpO2NvbnN0IF8weDU5NjMwZT1kb2N1bWVudFsncXVlcnlTZWxlY3RvckFsbCddKCcqJyk7XzB4ZGExZDU1WydwdXNoJ10oIUFycmF5W18weDMzYTE4MCgweDFhMyldKF8weDU5NjMwZSkpLF8weGRhMWQ1NVtfMHgzM2ExODAoMHgxY2IpXShfMHg1OTYzMGVbXzB4MzNhMTgwKDB4MWEyKV1bXzB4MzNhMTgwKDB4MWM1KV09PT1fMHgzM2ExODAoMHgxZDgpKTtjb25zdCBfMHhiMzJkMjE9ZG9jdW1lbnRbXzB4MzNhMTgwKDB4MWE2KV0oXzB4MzNhMTgwKDB4MWEwKSk7cmV0dXJuIF8weGRhMWQ1NVsncHVzaCddKF8weGIzMmQyMSBpbnN0YW5jZW9mIEhUTUxEaXZFbGVtZW50KSxfMHhkYTFkNTVbJ3B1c2gnXShIVE1MRGl2RWxlbWVudFsncHJvdG90eXBlJ11pbnN0YW5jZW9mIEhUTUxFbGVtZW50KSxfMHhkYTFkNTVbXzB4MzNhMTgwKDB4MWNiKV0oSFRNTEVsZW1lbnRbXzB4MzNhMTgwKDB4MWJhKV1pbnN0YW5jZW9mIEVsZW1lbnQpLFN0cmluZyhfMHhkYTFkNTVbXzB4MzNhMTgwKDB4MWFjKV0oTnVtYmVyKVtfMHgzM2ExODAoMHgxY2MpXSgoXzB4M2FhMGY1LF8weDQ4Mjk1Myk9Pl8weDNhYTBmNStfMHg0ODI5NTMsMHgxN2VlKSk7fSgpKSwoZnVuY3Rpb24oKXtjb25zdCBfMHg0Mjc4YTk9XzB4MzNmOTMyO3JldHVybiBTdHJpbmcoW25hdmlnYXRvcltfMHg0Mjc4YTkoMHgxZDApXT09PSEhW10sKGZ1bmN0aW9uKCl7Y29uc3QgXzB4NWIxOTE3PV8weDQyNzhhOSxfMHgxN2UzNDE9ZG9jdW1lbnRbJ2NyZWF0ZUVsZW1lbnQnXShfMHg1YjE5MTcoMHgxYzIpKTtfMHgxN2UzNDFbXzB4NWIxOTE3KDB4MWRlKV09XzB4NWIxOTE3KDB4MWUwKSxkb2N1bWVudFtfMHg1YjE5MTcoMHgxYjMpXVtfMHg1YjE5MTcoMHgxY2YpXShfMHgxN2UzNDEpO2xldCBfMHgyNGIzYjM7cmV0dXJuIF8weDE3ZTM0MVtfMHg1YjE5MTcoMHgxZGYpXSYmXzB4MTdlMzQxW18weDViMTkxNygweDFkZildW18weDViMTkxNygweDFjOCldJiZfMHgxN2UzNDFbJ2NvbnRlbnRXaW5kb3cnXVtfMHg1YjE5MTcoMHgxYzgpXVsnZ2V0J10/XzB4MjRiM2IzPV8weDE3ZTM0MVsnY29udGVudFdpbmRvdyddW18weDViMTkxNygweDFjOCldW18weDViMTkxNygweDFkMyldW18weDViMTkxNygweDFkMildKCk6XzB4MjRiM2IzPXVuZGVmaW5lZCxkb2N1bWVudFtfMHg1YjE5MTcoMHgxYjMpXVtfMHg1YjE5MTcoMHgxYmMpXShfMHgxN2UzNDEpLCEhXzB4MjRiM2IzO30oKSksKGZ1bmN0aW9uKCl7Y29uc3QgXzB4Mjc4ZDc3PV8weDQyNzhhOSxfMHg1Yzk3MTE9WydBcnJheScsXzB4Mjc4ZDc3KDB4MWQ2KSxfMHgyNzhkNzcoMHgxZDEpLF8weDI3OGQ3NygweDFjNCksXzB4Mjc4ZDc3KDB4MWE1KSxfMHgyNzhkNzcoMHgxYTQpLF8weDI3OGQ3NygweDFjMCldLF8weDI3MTllNj1PYmplY3RbXzB4Mjc4ZDc3KDB4MWE5KV0od2luZG93W18weDI3OGQ3NygweDFhMSldKVtfMHgyNzhkNzcoMHgxZDkpXShfMHgzMzg5YWY9Pl8weDVjOTcxMVtfMHgyNzhkNzcoMHgxYWQpXShfMHgxMjdmMDQ9Pl8weDMzODlhZiE9PV8weDEyN2YwNCYmXzB4MzM4OWFmW18weDI3OGQ3NygweDFiNildKCdfJytfMHgxMjdmMDQpJiZ3aW5kb3dbXzB4Mjc4ZDc3KDB4MWExKV1bXzB4MzM4OWFmXT09PXdpbmRvd1tfMHgyNzhkNzcoMHgxYTEpXVtfMHgxMjdmMDRdKSk7cmV0dXJuIF8weDI3MTllNltfMHgyNzhkNzcoMHgxYTgpXT4weDA7fSgpKV1bXzB4NDI3OGE5KDB4MWFjKV0oTnVtYmVyKVsncmVkdWNlJ10oKF8weDUzNzZhZCxfMHgxMDNiYWYpPT5fMHg1Mzc2YWQrXzB4MTAzYmFmLDB4MjBmNCkpO30oKSldKSxfMHg0NzJkOTE9W10sXzB4M2U5YjYzPXt9LF8weDUxMzBkYT1fMHgzM2Y5MzIoMHgxYWYpO2ZvcihsZXQgXzB4NDEwMjA2PTB4MDtfMHg0MTAyMDY8XzB4NWE4MDIwW18weDMzZjkzMigweDFhOCldO18weDQxMDIwNisrKXtjb25zdCBfMHgyYzYyZWU9XzB4NWE4MDIwW18weDQxMDIwNl07QXJyYXlbXzB4MzNmOTMyKDB4MWEzKV0oXzB4MmM2MmVlKT8oXzB4NDcyZDkxW18weDMzZjkzMigweDFjYildKF8weDJjNjJlZVsweDBdKSxfMHgyYzYyZWVbJ2xlbmd0aCddPjB4MSYmXzB4ODE3MTdkW18weDQxMDIwNl1bMHgxXSYmKF8weDNlOWI2M1tfMHg4MTcxN2RbXzB4NDEwMjA2XVsweDBdXT1fMHgyYzYyZWVbMHgxXSkpOl8weDQ3MmQ5MVsncHVzaCddKF8weDJjNjJlZSk7fWZ1bmN0aW9uIF8weDgwNjEoKXtjb25zdCBfMHhlZWVjMmU9WydEdWNrRHVja0dvXHgyMEZyYXVkXHgyMCZceDIwQWJ1c2UnLCdkaXYnLCd0b3AnLCdjb25zdHJ1Y3RvcicsJ2lzQXJyYXknLCdKU09OJywnU3ltYm9sJywnY3JlYXRlRWxlbWVudCcsJ3VzZXJBZ2VudCcsJ2xlbmd0aCcsJ2tleXMnLCdbb2JqZWN0XHgyMFdpbmRvd10nLCdbbmF0aXZlXHgyMGNvZGVdJywnbWFwJywnc29tZScsJzkxY2Y0ODE0MThjN2QxY2UwMDc3MGRhMTYwM2YyYjU1NDgyODM2OTg2ZmRiMWFlNzE4MjQxNjBhOTRjZWNlYWVweGp6cicsJ2IxZWI2NzZlNmE0MjEwMGEnLCcxMDc0MjUwdkRxeWFvJywnY2hhckNvZGVBdCcsJ2pvaW4nLCdib2R5JywnOTE1Q0hCcmdkJywnMTgxODc0NmRieGJnUicsJ2VuZHNXaXRoJywndGlkUUFTNGZlRlduTnlEN0NEZVRFazh4STBqb2tYbnBwelZrNnB2L1hvQT0nLCdpc1NlYWxlZCcsJ3BhcnNlSW50JywncHJvdG90eXBlJywnMTQ3ODBWTHNFYkUnLCdyZW1vdmVDaGlsZCcsJ3N0cmluZ2lmeScsJzE3ODYwOTM2Mjk0NTInLCcxTUptak1iJywnV2luZG93JywnY2FwdHVyZVN0YWNrVHJhY2UnLCdpZnJhbWUnLCdmdW5jdGlvbicsJ1Byb3h5JywnbmFtZScsJ2FsbCcsJ2NoaWxkcmVuJywnc2VsZicsJ2kzanAwJywnMjA4NDcwNFVkRHd5eCcsJ3B1c2gnLCdyZWR1Y2UnLCdGUWE1MUlLcVRMME9mc2htVm84YXFOSmI1d1o3Y0doL2lRNjlwdHRoVVUwPScsJ3B4anpyJywnYXBwZW5kQ2hpbGQnLCd3ZWJkcml2ZXInLCdQcm9taXNlJywndG9TdHJpbmcnLCdnZXQnLCdmcm9tJywnMTE1NjQ2OTRCUU1nRmInLCdPYmplY3QnLCc5MjU4NzkwRm5nY1hEJywnTm9kZUxpc3QnLCdmaWx0ZXInLCdjYWxsJywnMzY2T0xBaEdvJywnMTE5N2pIc2JXQicsJ2luY2x1ZGVzJywnc3JjZG9jJywnY29udGVudFdpbmRvdyddO18weDgwNjE9ZnVuY3Rpb24oKXtyZXR1cm4gXzB4ZWVlYzJlO307cmV0dXJuIF8weDgwNjEoKTt9Y29uc3QgXzB4NTE5ODc4PUFycmF5W18weDMzZjkzMigweDFkNCldKEpTT05bXzB4MzNmOTMyKDB4MWJkKV0oXzB4M2U5YjYzKSlbXzB4MzNmOTMyKDB4MWFjKV0oKF8weDMyZGZjNCxfMHgyNTA4MDgpPT5TdHJpbmdbJ2Zyb21DaGFyQ29kZSddKF8weDMyZGZjNFtfMHgzM2Y5MzIoMHgxYjEpXSgweDApXl8weDUxMzBkYVtfMHgzM2Y5MzIoMHgxYjEpXShfMHgyNTA4MDglXzB4NTEzMGRhW18weDMzZjkzMigweDFhOCldKSkpW18weDMzZjkzMigweDFiMildKCcnKTtyZXR1cm57J3NlcnZlcl9oYXNoZXMnOltfMHgzM2Y5MzIoMHgxYjcpLF8weDMzZjkzMigweDFjZCksJ1FEWDNHaW94Tm0rU0dlYWF1TnF3SU1RVm9BdklHUjVTWVlaUTRRTXd1ajg9J10sJ2NsaWVudF9oYXNoZXMnOl8weDQ3MmQ5MSwnc2lnbmFscyc6e30sJ21ldGEnOnsndic6JzQnLCdjaGFsbGVuZ2VfaWQnOl8weDMzZjkzMigweDFhZSksJ3RpbWVzdGFtcCc6XzB4MzNmOTMyKDB4MWJlKSwnZGVidWcnOl8weDUxOTg3OH19O30pKCk=", + "browserProbes": ["6138", "8436"], + "browserReduceVectors": [ + { + "seed": 6126, + "booleans": [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1] + }, + { + "seed": 8436, + "booleans": [0, 0, 0] + } + ] + } +} diff --git a/tests/unit/duckduckgo-challenge-solver-regression.test.ts b/tests/unit/duckduckgo-challenge-solver-regression.test.ts new file mode 100644 index 0000000000..a63cdb7583 --- /dev/null +++ b/tests/unit/duckduckgo-challenge-solver-regression.test.ts @@ -0,0 +1,258 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import vm from "node:vm"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +import { + CHALLENGE_STUBS, + buildHtmlLookup, + countHtmlElements, + sha256Base64, + solveDuckDuckGoChallenge, + DUCKDUCKGO_CHALLENGE_ORIGIN, +} from "../../open-sse/executors/duckduckgo-web/challenge.ts"; + +/** + * Regression suite for the DuckDuckGo AI Chat anti-abuse challenge solver. + * + * Background: every duckduckgo-web chat request was failing with HTTP 418 + * ERR_CHALLENGE while duck.ai worked normally in a browser from the same IP. + * Root-causing it turned up several independent defects, each of which is + * pinned below. The fixtures in `tests/fixtures/duckduckgo/challenge-variants.json` + * are REAL challenge programs captured from duckduckgo.com, together with the + * probe vectors a real (headful) Chromium produced for those exact programs. + * Matching Chromium bit-for-bit is the actual correctness criterion, so these + * tests assert against recorded browser behaviour rather than our own output. + */ + +const HERE = dirname(fileURLToPath(import.meta.url)); +const FIXTURES = join(HERE, "../fixtures/duckduckgo/challenge-variants.json"); + +type Variant = { + challengeBase64: string; + browserProbes: string[]; + browserReduceVectors: Array<{ seed: number; booleans: number[] }>; +}; +const VARIANTS = JSON.parse(readFileSync(FIXTURES, "utf8")) as Record; +const UA = + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"; + +function makeContext(challengeJs: string): vm.Context { + const stubs = CHALLENGE_STUBS.replace("__DDG_REAL_UA__", JSON.stringify(UA)).replace( + "__DDG_HTML_LOOKUP__", + JSON.stringify(buildHtmlLookup(challengeJs)) + ); + const context = vm.createContext({}); + vm.runInContext(stubs, context, { timeout: 5000 }); + return context; +} + +// --------------------------------------------------------------------------- +// Bug 1 — module syntax inside the sandbox source. +// `vm.runInContext` compiles in SCRIPT mode. A refactor mass-added `export` to +// the `function` declarations inside CHALLENGE_STUBS (they look like ordinary +// top-level TS functions), making every solve throw SyntaxError. The executor +// swallows solve failures and posts the raw unsolved challenge, so the upstream +// answered 418 for every request. +// --------------------------------------------------------------------------- +test("CHALLENGE_STUBS uses no module syntax and compiles in script mode", () => { + assert.doesNotMatch( + CHALLENGE_STUBS, + /(^|[\s;{}])(export|import)[\s{*]/, + "vm.runInContext compiles in script mode — export/import is a hard SyntaxError" + ); + const source = CHALLENGE_STUBS.replace("__DDG_REAL_UA__", '"ua"').replace( + "__DDG_HTML_LOOKUP__", + "{}" + ); + assert.doesNotThrow(() => new vm.Script(source)); +}); + +// --------------------------------------------------------------------------- +// Bug 2 — regex escaping inside a String.raw template. +// CHALLENGE_STUBS is a String.raw literal, so `\\s` reaches the sandbox as a +// literal backslash-backslash-s and the display regex never matched. One +// challenge variant asserts getComputedStyle(el).getPropertyValue('display') +// is non-empty, so this silently flipped a probe to false. +// --------------------------------------------------------------------------- +test("computed-style display probe resolves a real value", () => { + const context = makeContext(""); + const display = vm.runInContext( + `(function(){ + var d = document.createElement('div'); + d.style.cssText = 'display:inline-block;padding:8px;position:absolute;visibility:hidden;'; + return getComputedStyle(d).getPropertyValue('display'); + })()`, + context + ); + assert.equal(display, "inline-block"); +}); + +test("CHALLENGE_STUBS contains no double-escaped regex metacharacters", () => { + // String.raw means `\\s` in the source IS `\\s` in the sandbox — always a bug. + assert.doesNotMatch(CHALLENGE_STUBS, /\\\\[sdwbSDWB]/); +}); + +// --------------------------------------------------------------------------- +// Bug 3 — buildHtmlLookup descendant count was off by one. +// `count` backs `el.querySelectorAll('*').length` for an element whose +// innerHTML is the given markup. querySelectorAll('*') returns DESCENDANTS, and +// countHtmlElements already skips the #document-fragment root, so subtracting 1 +// undercounted. A variant multiplies innerHTML.length by that count, so the +// error propagated straight into the hash. +// --------------------------------------------------------------------------- +test("buildHtmlLookup reports the browser's descendant count", () => { + // Chromium: for innerHTML = '
  • "); + assert.equal(entry.html.length, 29); + assert.equal(entry.count, 3); + assert.equal(countHtmlElements({ nodeName: undefined, childNodes: [] }), 0); +}); + +// --------------------------------------------------------------------------- +// Bug 4 — the browser-fidelity probes. +// Newer challenge variants interrogate JS/DOM invariants that a naive stub +// object does not satisfy (prototype chains, NodeList identity, live +// HTMLCollection, native-code toString, sloppy-mode `this`). Nine of thirteen +// failed. Each is pinned individually so a future stub regression names itself. +// --------------------------------------------------------------------------- +const FIDELITY_PROBES: Array<[string, string, boolean]> = [ + [ + "built-ins stringify as native code", + `window.parseInt.toString().includes("[native code]")`, + true, + ], + [ + "Array subclass survives map", + `(function(){ class S extends Array {}; return new S(1,2,3).map(function(x){return x*2;}) instanceof S; })()`, + true, + ], + [ + "window brands as [object Window]", + `Object.prototype.toString.call(window) === "[object Window]"`, + true, + ], + ["Error instances are real", `new Error() instanceof Error`, true], + [ + "captureStackTrace is absent or a function", + `Error.captureStackTrace === undefined || typeof Error.captureStackTrace === "function"`, + true, + ], + // Chromium reports false here; sealing Math made our vector differ by one. + ["Math is NOT sealed (matches Chromium)", `Object.isSealed(Math)`, false], + ["sloppy-mode this is window", `(function(){ return this; })() === window`, true], + [ + "document.body.children is live", + `(function(){ + var c = document.body.children, n = c.length, d = document.createElement('div'); + document.body.appendChild(d); + var grew = c.length === n + 1; + document.body.removeChild(d); + return grew && c.length === n; + })()`, + true, + ], + ["querySelectorAll is not an Array", `!Array.isArray(document.querySelectorAll("*"))`, true], + [ + "querySelectorAll is a NodeList", + `document.querySelectorAll("*").constructor.name === "NodeList"`, + true, + ], + [ + "createElement('div') is an HTMLDivElement", + `document.createElement("div") instanceof HTMLDivElement`, + true, + ], + [ + "HTMLDivElement derives from HTMLElement", + `HTMLDivElement.prototype instanceof HTMLElement`, + true, + ], + ["HTMLElement derives from Element", `HTMLElement.prototype instanceof Element`, true], + ["navigator.webdriver is falsy", `navigator.webdriver === true`, false], + ["navigator survives the global aliasing", `navigator.userAgent === ${JSON.stringify(UA)}`, true], + ["window.document is the document", `window.document === document`, true], +]; + +for (const [name, expression, expected] of FIDELITY_PROBES) { + test(`browser-fidelity probe: ${name}`, () => { + const context = makeContext(""); + assert.equal(vm.runInContext(expression, context), expected); + }); +} + +// --------------------------------------------------------------------------- +// The real acceptance criterion: for every captured challenge variant our +// sandbox must produce exactly the probe values a real Chromium produced. +// --------------------------------------------------------------------------- +for (const [file, variant] of Object.entries(VARIANTS)) { + test(`challenge variant ${file} matches real-browser probe values`, async () => { + const js = Buffer.from(variant.challengeBase64, "base64").toString("utf8"); + const context = makeContext(js); + const result = (await vm.runInContext(js, context, { timeout: 5000 })) as { + client_hashes: unknown[]; + }; + // `result` crosses the vm realm boundary, so its arrays carry the sandbox's + // Array.prototype. Copy into this realm or deepStrictEqual fails on the + // prototype even when every element matches. + const ours = Array.from(result.client_hashes).slice(1).map(String); + assert.deepEqual( + ours, + variant.browserProbes, + `probe values must match Chromium exactly for ${file}` + ); + }); +} + +// --------------------------------------------------------------------------- +// Bug 5 — the solved payload dropped meta.origin / meta.stack / meta.duration. +// The duck.ai frontend always sends all three; captured browser requests +// confirm it. Without them the upstream returns 418 even when every +// client_hash is correct. +// --------------------------------------------------------------------------- +test("solveDuckDuckGoChallenge stamps meta.origin/stack/duration", async () => { + const [variant] = Object.values(VARIANTS); + const solved = await solveDuckDuckGoChallenge(variant.challengeBase64, UA); + const decoded = JSON.parse(Buffer.from(solved, "base64").toString("utf8")); + + assert.equal(decoded.meta.origin, DUCKDUCKGO_CHALLENGE_ORIGIN); + assert.match(decoded.meta.stack, /^Error\n\s*at l \(https:\/\/duck\.ai\/.*\.js:\d+:\d+\)/); + assert.match(String(decoded.meta.duration), /^\d+$/); + // The challenge's own meta must survive alongside the added fields. + assert.equal(decoded.meta.v, "4"); + assert.ok(decoded.meta.challenge_id); +}); + +test("solveDuckDuckGoChallenge honours an explicit origin/bundle", async () => { + const [variant] = Object.values(VARIANTS); + const solved = await solveDuckDuckGoChallenge(variant.challengeBase64, UA, { + origin: "https://duckduckgo.com", + bundlePath: "/dist/x.js", + }); + const decoded = JSON.parse(Buffer.from(solved, "base64").toString("utf8")); + assert.equal(decoded.meta.origin, "https://duckduckgo.com"); + assert.ok(decoded.meta.stack.includes("https://duckduckgo.com/dist/x.js")); +}); + +test("solveDuckDuckGoChallenge hashes client_hashes with the real UA in slot 0", async () => { + const [file, variant] = Object.entries(VARIANTS)[0]; + const solved = await solveDuckDuckGoChallenge(variant.challengeBase64, UA); + const decoded = JSON.parse(Buffer.from(solved, "base64").toString("utf8")); + + const expected = [sha256Base64(UA), ...variant.browserProbes.map((p) => sha256Base64(p))]; + assert.deepEqual(decoded.client_hashes, expected, `client_hashes mismatch for ${file}`); + // server_hashes are echoed back untouched. + assert.ok(Array.isArray(decoded.server_hashes)); +}); + +test("solveDuckDuckGoChallenge rejects a challenge with no client_hashes", async () => { + const bad = Buffer.from(`(async function(){ return { client_hashes: [] }; })()`, "utf8").toString( + "base64" + ); + await assert.rejects(() => solveDuckDuckGoChallenge(bad, UA), /empty client_hashes/); +}); diff --git a/tests/unit/duckduckgo-challenge-split.test.ts b/tests/unit/duckduckgo-challenge-split.test.ts index 0ea9a98070..774503e6ef 100644 --- a/tests/unit/duckduckgo-challenge-split.test.ts +++ b/tests/unit/duckduckgo-challenge-split.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; +import vm from "node:vm"; // Split-guard for the duckduckgo-web challenge-solver extraction. // The anti-abuse challenge solver + FE signals live in duckduckgo-web/challenge.ts @@ -35,3 +36,81 @@ test("makeDuckDuckGoFeSignals returns a base64 string", async () => { assert.equal(typeof out, "string"); assert.ok(out.length > 0); }); + +// Regression guard: CHALLENGE_STUBS is browser-emulation source executed by +// `vm.runInContext`, which compiles in SCRIPT mode — module syntax is a hard +// SyntaxError there. A refactor once mass-added `export` to the `function` +// declarations inside this template literal (they look like ordinary top-level +// TS functions), which made every solve throw. The executor swallows solve +// failures and sends the raw unsolved challenge, so DuckDuckGo answered every +// chat request with HTTP 418 ERR_CHALLENGE while the site worked fine in a +// browser from the same IP. The three tests below fail on that class of bug. +test("CHALLENGE_STUBS contains no module syntax (vm runs it in script mode)", async () => { + const { CHALLENGE_STUBS } = await import("../../open-sse/executors/duckduckgo-web/challenge.ts"); + assert.doesNotMatch( + CHALLENGE_STUBS, + /(^|[\s;{}])(export|import)[\s{*]/, + "CHALLENGE_STUBS must not use export/import — vm.runInContext compiles in script mode" + ); +}); + +test("CHALLENGE_STUBS compiles as a script", async () => { + const { CHALLENGE_STUBS } = await import("../../open-sse/executors/duckduckgo-web/challenge.ts"); + // Placeholders are substituted before execution; do the same here so the + // source is syntactically complete. + const source = CHALLENGE_STUBS.replace("__DDG_REAL_UA__", '"test-ua"').replace( + "__DDG_HTML_LOOKUP__", + "{}" + ); + assert.doesNotThrow(() => new vm.Script(source), "CHALLENGE_STUBS must parse in script mode"); +}); + +test("CHALLENGE_STUBS evaluates and defines the browser stubs the challenge probes", async () => { + const { CHALLENGE_STUBS } = await import("../../open-sse/executors/duckduckgo-web/challenge.ts"); + const source = CHALLENGE_STUBS.replace("__DDG_REAL_UA__", '"test-ua"').replace( + "__DDG_HTML_LOOKUP__", + "{}" + ); + const context = vm.createContext({}); + vm.runInContext(source, context, { timeout: 5000 }); + + // A real DDG challenge reads these; if the stubs silently failed to evaluate + // they would all be undefined and the solver would produce garbage. + assert.equal( + vm.runInContext("navigator.userAgent", context), + "test-ua", + "navigator.userAgent must carry the injected UA" + ); + assert.equal(vm.runInContext("typeof document.querySelector", context), "function"); + assert.equal(vm.runInContext("document.getElementById('jsa').tagName", context), "IFRAME"); + assert.equal(vm.runInContext("typeof getComputedStyle", context), "function"); + assert.equal(vm.runInContext("window.top === window", context), true); +}); + +// End-to-end guard on the solver itself, using a stand-in challenge that mimics +// the real one's contract: an async IIFE returning { server_hashes, client_hashes, +// signals, meta }. This exercises the full stubs -> vm -> hash -> base64 path +// without hitting the network. +test("solveDuckDuckGoChallenge solves a representative challenge payload", async () => { + const { solveDuckDuckGoChallenge, sha256Base64 } = + await import("../../open-sse/executors/duckduckgo-web/challenge.ts"); + const fakeChallenge = `(async function(){ + return { + server_hashes: ["s1", "s2"], + client_hashes: [navigator.userAgent, document.getElementById('jsa').tagName], + signals: {}, + meta: { v: "4", challenge_id: "test" } + }; + })()`; + const ua = "Mozilla/5.0 (X11; Linux x86_64) TestAgent/1.0"; + const solved = await solveDuckDuckGoChallenge( + Buffer.from(fakeChallenge, "utf8").toString("base64"), + ua + ); + const decoded = JSON.parse(Buffer.from(solved, "base64").toString("utf8")); + + assert.deepEqual(decoded.server_hashes, ["s1", "s2"], "server_hashes pass through untouched"); + // Slot 0 is overwritten with the real UA before hashing, then every slot is sha256+base64. + assert.deepEqual(decoded.client_hashes, [sha256Base64(ua), sha256Base64("IFRAME")]); + assert.equal(decoded.meta.challenge_id, "test"); +}); diff --git a/tests/unit/duckduckgo-reasoning-effort-required.test.ts b/tests/unit/duckduckgo-reasoning-effort-required.test.ts new file mode 100644 index 0000000000..3dd3d8fc9c --- /dev/null +++ b/tests/unit/duckduckgo-reasoning-effort-required.test.ts @@ -0,0 +1,134 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { DuckDuckGoWebExecutor } from "../../open-sse/executors/duckduckgo-web.ts"; + +/** + * Regression: duckchat/v1/chat now REQUIRES a `reasoningEffort` field. + * + * The executor previously omitted it for most models on the assumption that the + * upstream would apply its own default. It does not: an otherwise byte-identical + * payload returns 200 with the field and 400 ERR_BAD_REQUEST without it + * (A/B verified live against duck.ai, repeated). The live duck.ai bundle always + * sends one, so every outgoing payload must carry it. + * + * These tests capture the executor's real outgoing request body by stubbing + * fetch, so they assert on the wire format rather than on internal helpers. + */ + +type Captured = { url: string; body: Record }; + +async function captureChatPayload(model: string): Promise { + const realFetch = globalThis.fetch; + const captured: Captured[] = []; + + globalThis.fetch = (async (input: unknown, init: RequestInit = {}) => { + const url = typeof input === "string" ? input : String((input as { url?: string })?.url ?? ""); + + if (url.includes("/duckchat/v1/status")) { + // Hand back a trivially solvable challenge so the executor proceeds to the + // chat POST without touching the network. + const challenge = Buffer.from( + `(async function(){ return { server_hashes: [], client_hashes: ["ua"], signals: {}, meta: {} }; })()`, + "utf8" + ).toString("base64"); + return new Response("{}", { status: 200, headers: { "x-vqd-hash-1": challenge } }); + } + + if (url.includes("/duckchat/v1/chat")) { + captured.push({ url, body: JSON.parse(String(init.body)) }); + return new Response(`data: {"action":"success","message":"OK"}\n\ndata: [DONE]\n\n`, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); + } + + // Warm-up fetches (homepage, country.json, auth/token). + return new Response("", { status: 200 }); + }) as typeof globalThis.fetch; + + try { + const executor = new DuckDuckGoWebExecutor(); + await executor.execute({ + model, + body: { messages: [{ role: "user", content: "Say OK" }] }, + stream: false, + } as never); + } finally { + globalThis.fetch = realFetch; + } + + assert.ok(captured.length > 0, "executor never issued a chat request"); + return captured[captured.length - 1]; +} + +test("chat payload always carries reasoningEffort", async () => { + const { body } = await captureChatPayload("duckduckgo-web/gpt-4o-mini"); + assert.ok( + Object.prototype.hasOwnProperty.call(body, "reasoningEffort"), + "omitting reasoningEffort yields 400 ERR_BAD_REQUEST upstream" + ); + assert.equal(typeof body.reasoningEffort, "string"); + assert.notEqual(body.reasoningEffort, ""); +}); + +test("default models send reasoningEffort 'none'", async () => { + const { body } = await captureChatPayload("duckduckgo-web/gpt-5.4-mini"); + assert.equal(body.model, "gpt-5.4-mini"); + assert.equal(body.reasoningEffort, "none"); +}); + +test("reasoning models keep their 'low' effort", async () => { + const haiku = await captureChatPayload("duckduckgo-web/claude-haiku-4-5"); + assert.equal(haiku.body.model, "claude-haiku-4-5"); + assert.equal(haiku.body.reasoningEffort, "low"); + + const oss = await captureChatPayload("duckduckgo-web/gpt-oss-120b"); + assert.equal(oss.body.model, "tinfoil/gpt-oss-120b"); + assert.equal(oss.body.reasoningEffort, "low"); +}); + +test("retired model ids are still aliased to live wire ids", async () => { + const { body } = await captureChatPayload("duckduckgo-web/gpt-4o-mini"); + assert.equal(body.model, "gpt-5.4-mini"); +}); + +test("executor issues exactly one chat request per call", async () => { + // A throwaway "seed" chat POST used to run before the real one, doubling the + // request volume against an IP-rate-limited endpoint and causing spurious 429s. + const realFetch = globalThis.fetch; + let chatCalls = 0; + + globalThis.fetch = (async (input: unknown, init: RequestInit = {}) => { + const url = typeof input === "string" ? input : String((input as { url?: string })?.url ?? ""); + if (url.includes("/duckchat/v1/status")) { + const challenge = Buffer.from( + `(async function(){ return { server_hashes: [], client_hashes: ["ua"], signals: {}, meta: {} }; })()`, + "utf8" + ).toString("base64"); + return new Response("{}", { status: 200, headers: { "x-vqd-hash-1": challenge } }); + } + if (url.includes("/duckchat/v1/chat")) { + chatCalls++; + void init; + return new Response(`data: {"action":"success","message":"OK"}\n\ndata: [DONE]\n\n`, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); + } + return new Response("", { status: 200 }); + }) as typeof globalThis.fetch; + + try { + const executor = new DuckDuckGoWebExecutor(); + await executor.execute({ + model: "duckduckgo-web/gpt-5.4-mini", + body: { messages: [{ role: "user", content: "Say OK" }] }, + stream: false, + } as never); + } finally { + globalThis.fetch = realFetch; + } + + assert.equal(chatCalls, 1, "expected exactly one POST /duckchat/v1/chat per user request"); +});