@xbrowser/cli 1.18.2 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-MTNVT2T7.js";
24
- import "./chunk-LL37GYPP.js";
23
+ } from "./chunk-76EKW7YJ.js";
24
+ import "./chunk-FDU5BCPV.js";
25
25
  import "./chunk-3KZ34AUC.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-A6LPGFAL.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-NA7IP6PO.js";
24
- import "./chunk-DVKIL7H6.js";
23
+ } from "./chunk-75TLPVVZ.js";
24
+ import "./chunk-UYEJ66VX.js";
25
25
  import "./chunk-3FWLW7FS.js";
26
26
  import "./chunk-3KZ34AUC.js";
27
27
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-7SDM4EA4.js";
23
+ } from "./chunk-T2KUVO4H.js";
24
24
  import "./chunk-3KZ34AUC.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-DVKIL7H6.js";
17
+ } from "./chunk-UYEJ66VX.js";
18
18
  import "./chunk-3FWLW7FS.js";
19
19
  import {
20
20
  connectToCDP,
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-LL37GYPP.js";
17
+ } from "./chunk-FDU5BCPV.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -133,8 +133,17 @@ function buildStealthInitScript() {
133
133
  " if(!e)return f.call(this,e);",
134
134
  " return f.call(this,new Proxy(e,{get:function(k,p){",
135
135
  ' if(p==="sourceCapabilities")return fc;',
136
- ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
137
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
136
+ // S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
137
+ // 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
138
+ // 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
139
+ // + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
140
+ ' if(p==="isTrusted"){',
141
+ ' var orig=Reflect.get(k,"isTrusted");',
142
+ " if(orig===true)return true;",
143
+ ' if(k.type==="paste")return true;',
144
+ " return orig;",
145
+ " }",
146
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
138
147
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
139
148
  " return k[p]+_f;",
140
149
  " }",
@@ -144,6 +153,30 @@ function buildStealthInitScript() {
144
153
  " return o.call(this,t,w,op);",
145
154
  " };",
146
155
  " EventTarget.prototype.addEventListener=_ael;",
156
+ // window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
157
+ // (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
158
+ " try{",
159
+ ' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
160
+ " if(_weDesc&&_weDesc.get){",
161
+ " var _origWeGet=_weDesc.get;",
162
+ ' Object.defineProperty(Window.prototype,"event",{',
163
+ " configurable:true,",
164
+ " get:function(){",
165
+ " var ev=_origWeGet.call(this);",
166
+ " if(!ev)return ev;",
167
+ " return new Proxy(ev,{get:function(k,p){",
168
+ ' if(p==="isTrusted"){',
169
+ ' var orig=Reflect.get(k,"isTrusted");',
170
+ " if(orig===true)return true;",
171
+ ' if(k.type==="paste")return true;',
172
+ " return orig;",
173
+ " }",
174
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
175
+ " }});",
176
+ " }",
177
+ " });",
178
+ " }",
179
+ " }catch(e){}",
147
180
  // 2. Screen override (prototype-level, not instance-level)
148
181
  " var _gw=function(){return 1728};",
149
182
  " var _gh=function(){return 1117};",
@@ -152,9 +185,42 @@ function buildStealthInitScript() {
152
185
  ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
153
186
  ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
154
187
  ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
188
+ // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
189
+ // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
190
+ // 原型+实例双层覆写(S172 模式)。
191
+ " try{",
192
+ ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
193
+ ' var fixBrands=function(list){return (list||[]).map(function(b){return (b.brand&&b.brand.indexOf("Headless")!==-1)?Object.assign({},b,{brand:b.brand.replace("HeadlessChrome","Chrome")}):b;});};',
194
+ ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
195
+ " var wrapUAD=function(uad){",
196
+ " if(!uad||uad.__xbUA)return uad;",
197
+ " try{",
198
+ ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
199
+ " var _ghev=uad.getHighEntropyValues.bind(uad);",
200
+ " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
201
+ ' var o={};for(var k in v){o[k]=(typeof v[k]==="string")?fixUA(v[k]):(Array.isArray(v[k])?fixBrands(v[k]):v[k]);}',
202
+ " return o;});};",
203
+ " uad.__xbUA=true;",
204
+ " }catch(e){}",
205
+ " return uad;",
206
+ " };",
207
+ ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
208
+ " if(_nua&&_nua.get){",
209
+ " var _nuag=function(){return fixUA(_nua.get.call(this));};",
210
+ ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
211
+ ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
212
+ " }",
213
+ ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
214
+ " if(_nuad&&_nuad.get){",
215
+ " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
216
+ ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
217
+ ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
218
+ " }",
219
+ " }catch(e){}",
155
220
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
156
221
  // —— 真实浏览器失焦/后台时 hasFocus=false
157
- ' document.hasFocus=function(){return document.visibilityState==="visible";};',
222
+ // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
223
+ ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
158
224
  // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
159
225
  // Headless software raster differs subtly from Chrome GPU raster —
160
226
  // toDataURL hashes fingerprint the rasterizer. Inject a stable
@@ -217,26 +283,25 @@ function buildStealthInitScript() {
217
283
  " }catch(e){}",
218
284
  // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
219
285
  " try{",
220
- " var _mt=CanvasRenderingContext2D.prototype.measureText;",
221
286
  ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
222
- " CanvasRenderingContext2D.prototype.measureText=function(t){",
223
- " var m=_mt.call(this,t);",
224
- ' try{Object.defineProperty(m,"width",{get:function(){return _tmw.get.call(m)+_dx*0.01;},configurable:true});}catch(e){}',
225
- " return m;",
226
- " };",
287
+ " if(_tmw&&_tmw.get){",
288
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
289
+ ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
290
+ " }",
227
291
  " }catch(e){}",
228
292
  " try{",
229
293
  ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
230
- " var _sv=speechSynthesis.getVoices.bind(speechSynthesis);",
231
- " speechSynthesis.getVoices=function(){",
232
- " var real=_sv();",
294
+ // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
295
+ " var _sv=SpeechSynthesis.prototype.getVoices;",
296
+ " SpeechSynthesis.prototype.getVoices=function(){",
297
+ " var real=_sv.call(this);",
233
298
  " if(real&&real.length>50)return real;",
234
299
  // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
235
300
  // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
236
301
  ' return _fakeVoices.map(function(n,i){return {name:n,lang:i<2?"en-US":i<6?"en-GB":"zh-TW",localService:true,default:i===0,voiceURI:n};});',
237
302
  " };",
238
303
  // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
239
- // 轮询同源 iframe,对其 contentWindow.speechSynthesis 同样 patch
304
+ // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
240
305
  " try{",
241
306
  " var _piv=function(){",
242
307
  ' var frs=document.querySelectorAll("iframe");',
@@ -244,8 +309,8 @@ function buildStealthInitScript() {
244
309
  " var cw=frs[i].contentWindow;",
245
310
  " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
246
311
  " cw.speechSynthesis.__xbP=true;",
247
- " var _sv2=cw.speechSynthesis.getVoices.bind(cw.speechSynthesis);",
248
- " cw.speechSynthesis.getVoices=function(){var r=_sv2();if(r&&r.length>50)return r;",
312
+ " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
313
+ " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
249
314
  ' return _fakeVoices.map(function(n,j){return{name:n,lang:j<2?"en-US":j<6?"en-GB":"zh-TW",localService:true,default:j===0,voiceURI:n};});};',
250
315
  " }catch(e2){}}",
251
316
  " };",
@@ -280,10 +345,21 @@ function buildStealthInitScript() {
280
345
  // 3d-2. performance.now precision clamp (d34): full-precision timestamps
281
346
  // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
282
347
  " try{",
283
- " var _pn=performance.now.bind(performance);",
284
- " performance.now=function(){return Math.round(_pn()*10)/10;};",
348
+ " var _pn=Performance.prototype.now;",
349
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
350
+ " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
285
351
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
286
- ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
352
+ " if(_pto&&_pto.get){",
353
+ " // S173: timeOrigin \u771F\u4F2A\u88C5\u2014\u2014\u5360\u4F4D\u5C42\uFF08\u8FD4\u56DE\u771F\u503C\uFF09\u5347\u7EA7\u4E3A\u4F1A\u8BDD\u7EA7\u968F\u673A\u504F\u79FB\u3002",
354
+ " // \u9632 cross-page \u5BFC\u822A\u65F6\u523B\u5173\u8054\uFF1A\u4E0D\u540C\u7AD9\u70B9\u7528 timeOrigin \u7CBE\u786E\u5BF9\u9F50\u7528\u6237\u884C\u4E3A\u6863\u6848\u3002",
355
+ " // \u504F\u79FB\u7531\u672C\u9875 _seed \u6D3E\u751F\uFF08\u9875\u5185\u81EA\u6D3D\uFF0Cnow()+\u504F\u79FB\u4E00\u81F4\uFF09\uFF0C\u8DE8\u9875\u968F\u673A\uFF08\u4E0D\u53EF\u5173\u8054\uFF09\u3002",
356
+ " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
357
+ " var _tog=function(){return _pto.get.call(performance)+_toff;};",
358
+ ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
359
+ " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
360
+ ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
361
+ ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
362
+ " }",
287
363
  " }catch(e){}",
288
364
  // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
289
365
  // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
@@ -397,11 +473,19 @@ function buildStealthInitScript() {
397
473
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
398
474
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
399
475
  ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
476
+ ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
477
+ ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
478
+ ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
400
479
  " return _ts.call(this);",
401
480
  " };",
402
481
  // 4. onclick prototype hijack (dual-stream consistency)
403
482
  " var _ba=function(k,p){",
404
- ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
483
+ ' if(p==="isTrusted"){',
484
+ ' var orig=Reflect.get(k,"isTrusted");',
485
+ " if(orig===true)return true;",
486
+ ' if(k.type==="paste")return true;',
487
+ " return orig;",
488
+ " }",
405
489
  ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
406
490
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
407
491
  " }",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-DVKIL7H6.js";
4
+ } from "./chunk-UYEJ66VX.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
@@ -495,7 +495,9 @@ async function resolveCDPEndpoint(raw) {
495
495
  }
496
496
  if (raw.startsWith("http://") || raw.startsWith("https://")) {
497
497
  try {
498
- const httpResp = await fetchNoProxy(`${raw}/json/version`);
498
+ const u = new URL(raw);
499
+ u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/version";
500
+ const httpResp = await fetchNoProxy(u.toString());
499
501
  const data = await httpResp.json();
500
502
  if (!data.webSocketDebuggerUrl) {
501
503
  throw new Error(`Could not discover CDP endpoint from ${raw}`);
@@ -633,9 +635,17 @@ function discoverChromiumPath() {
633
635
  }
634
636
  return void 0;
635
637
  }
638
+ var _browserPool = /* @__PURE__ */ new Map();
636
639
  async function createBrowser(options) {
637
640
  if (options?.cdpEndpoint) {
638
641
  const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
642
+ if (!options.intercept) {
643
+ const pooled = _browserPool.get(realEndpoint);
644
+ if (pooled && !pooled.disconnected) {
645
+ return pooled;
646
+ }
647
+ if (pooled) _browserPool.delete(realEndpoint);
648
+ }
639
649
  if (options.intercept) {
640
650
  const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
641
651
  _sharedCdpProxy = new CDPInterceptorProxy(config);
@@ -648,6 +658,7 @@ async function createBrowser(options) {
648
658
  await browser2.discoverContexts().catch((err) => {
649
659
  console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
650
660
  });
661
+ if (!options.intercept) _browserPool.set(realEndpoint, browser2);
651
662
  return browser2;
652
663
  }
653
664
  const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-LL37GYPP.js";
3
+ } from "./chunk-FDU5BCPV.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -68,7 +68,9 @@ async function resolveCDPEndpoint(raw) {
68
68
  }
69
69
  if (raw.startsWith("http://") || raw.startsWith("https://")) {
70
70
  try {
71
- const httpResp = await fetchNoProxy(`${raw}/json/version`);
71
+ const u = new URL(raw);
72
+ u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/version";
73
+ const httpResp = await fetchNoProxy(u.toString());
72
74
  const data = await httpResp.json();
73
75
  if (!data.webSocketDebuggerUrl) {
74
76
  throw new Error(`Could not discover CDP endpoint from ${raw}`);
@@ -206,9 +208,17 @@ function discoverChromiumPath() {
206
208
  }
207
209
  return void 0;
208
210
  }
211
+ var _browserPool = /* @__PURE__ */ new Map();
209
212
  async function createBrowser(options) {
210
213
  if (options?.cdpEndpoint) {
211
214
  const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
215
+ if (!options.intercept) {
216
+ const pooled = _browserPool.get(realEndpoint);
217
+ if (pooled && !pooled.disconnected) {
218
+ return pooled;
219
+ }
220
+ if (pooled) _browserPool.delete(realEndpoint);
221
+ }
212
222
  if (options.intercept) {
213
223
  const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
214
224
  _sharedCdpProxy = new CDPInterceptorProxy(config);
@@ -221,6 +231,7 @@ async function createBrowser(options) {
221
231
  await browser2.discoverContexts().catch((err) => {
222
232
  console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
223
233
  });
234
+ if (!options.intercept) _browserPool.set(realEndpoint, browser2);
224
235
  return browser2;
225
236
  }
226
237
  const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
@@ -143,8 +143,17 @@ function buildStealthInitScript() {
143
143
  " if(!e)return f.call(this,e);",
144
144
  " return f.call(this,new Proxy(e,{get:function(k,p){",
145
145
  ' if(p==="sourceCapabilities")return fc;',
146
- ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
147
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
146
+ // S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
147
+ // 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
148
+ // 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
149
+ // + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
150
+ ' if(p==="isTrusted"){',
151
+ ' var orig=Reflect.get(k,"isTrusted");',
152
+ " if(orig===true)return true;",
153
+ ' if(k.type==="paste")return true;',
154
+ " return orig;",
155
+ " }",
156
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
148
157
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
149
158
  " return k[p]+_f;",
150
159
  " }",
@@ -154,6 +163,30 @@ function buildStealthInitScript() {
154
163
  " return o.call(this,t,w,op);",
155
164
  " };",
156
165
  " EventTarget.prototype.addEventListener=_ael;",
166
+ // window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
167
+ // (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
168
+ " try{",
169
+ ' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
170
+ " if(_weDesc&&_weDesc.get){",
171
+ " var _origWeGet=_weDesc.get;",
172
+ ' Object.defineProperty(Window.prototype,"event",{',
173
+ " configurable:true,",
174
+ " get:function(){",
175
+ " var ev=_origWeGet.call(this);",
176
+ " if(!ev)return ev;",
177
+ " return new Proxy(ev,{get:function(k,p){",
178
+ ' if(p==="isTrusted"){',
179
+ ' var orig=Reflect.get(k,"isTrusted");',
180
+ " if(orig===true)return true;",
181
+ ' if(k.type==="paste")return true;',
182
+ " return orig;",
183
+ " }",
184
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
185
+ " }});",
186
+ " }",
187
+ " });",
188
+ " }",
189
+ " }catch(e){}",
157
190
  // 2. Screen override (prototype-level, not instance-level)
158
191
  " var _gw=function(){return 1728};",
159
192
  " var _gh=function(){return 1117};",
@@ -162,9 +195,42 @@ function buildStealthInitScript() {
162
195
  ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
163
196
  ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
164
197
  ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
198
+ // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
199
+ // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
200
+ // 原型+实例双层覆写(S172 模式)。
201
+ " try{",
202
+ ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
203
+ ' var fixBrands=function(list){return (list||[]).map(function(b){return (b.brand&&b.brand.indexOf("Headless")!==-1)?Object.assign({},b,{brand:b.brand.replace("HeadlessChrome","Chrome")}):b;});};',
204
+ ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
205
+ " var wrapUAD=function(uad){",
206
+ " if(!uad||uad.__xbUA)return uad;",
207
+ " try{",
208
+ ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
209
+ " var _ghev=uad.getHighEntropyValues.bind(uad);",
210
+ " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
211
+ ' var o={};for(var k in v){o[k]=(typeof v[k]==="string")?fixUA(v[k]):(Array.isArray(v[k])?fixBrands(v[k]):v[k]);}',
212
+ " return o;});};",
213
+ " uad.__xbUA=true;",
214
+ " }catch(e){}",
215
+ " return uad;",
216
+ " };",
217
+ ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
218
+ " if(_nua&&_nua.get){",
219
+ " var _nuag=function(){return fixUA(_nua.get.call(this));};",
220
+ ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
221
+ ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
222
+ " }",
223
+ ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
224
+ " if(_nuad&&_nuad.get){",
225
+ " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
226
+ ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
227
+ ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
228
+ " }",
229
+ " }catch(e){}",
165
230
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
166
231
  // —— 真实浏览器失焦/后台时 hasFocus=false
167
- ' document.hasFocus=function(){return document.visibilityState==="visible";};',
232
+ // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
233
+ ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
168
234
  // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
169
235
  // Headless software raster differs subtly from Chrome GPU raster —
170
236
  // toDataURL hashes fingerprint the rasterizer. Inject a stable
@@ -227,26 +293,25 @@ function buildStealthInitScript() {
227
293
  " }catch(e){}",
228
294
  // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
229
295
  " try{",
230
- " var _mt=CanvasRenderingContext2D.prototype.measureText;",
231
296
  ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
232
- " CanvasRenderingContext2D.prototype.measureText=function(t){",
233
- " var m=_mt.call(this,t);",
234
- ' try{Object.defineProperty(m,"width",{get:function(){return _tmw.get.call(m)+_dx*0.01;},configurable:true});}catch(e){}',
235
- " return m;",
236
- " };",
297
+ " if(_tmw&&_tmw.get){",
298
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
299
+ ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
300
+ " }",
237
301
  " }catch(e){}",
238
302
  " try{",
239
303
  ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
240
- " var _sv=speechSynthesis.getVoices.bind(speechSynthesis);",
241
- " speechSynthesis.getVoices=function(){",
242
- " var real=_sv();",
304
+ // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
305
+ " var _sv=SpeechSynthesis.prototype.getVoices;",
306
+ " SpeechSynthesis.prototype.getVoices=function(){",
307
+ " var real=_sv.call(this);",
243
308
  " if(real&&real.length>50)return real;",
244
309
  // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
245
310
  // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
246
311
  ' return _fakeVoices.map(function(n,i){return {name:n,lang:i<2?"en-US":i<6?"en-GB":"zh-TW",localService:true,default:i===0,voiceURI:n};});',
247
312
  " };",
248
313
  // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
249
- // 轮询同源 iframe,对其 contentWindow.speechSynthesis 同样 patch
314
+ // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
250
315
  " try{",
251
316
  " var _piv=function(){",
252
317
  ' var frs=document.querySelectorAll("iframe");',
@@ -254,8 +319,8 @@ function buildStealthInitScript() {
254
319
  " var cw=frs[i].contentWindow;",
255
320
  " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
256
321
  " cw.speechSynthesis.__xbP=true;",
257
- " var _sv2=cw.speechSynthesis.getVoices.bind(cw.speechSynthesis);",
258
- " cw.speechSynthesis.getVoices=function(){var r=_sv2();if(r&&r.length>50)return r;",
322
+ " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
323
+ " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
259
324
  ' return _fakeVoices.map(function(n,j){return{name:n,lang:j<2?"en-US":j<6?"en-GB":"zh-TW",localService:true,default:j===0,voiceURI:n};});};',
260
325
  " }catch(e2){}}",
261
326
  " };",
@@ -290,10 +355,21 @@ function buildStealthInitScript() {
290
355
  // 3d-2. performance.now precision clamp (d34): full-precision timestamps
291
356
  // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
292
357
  " try{",
293
- " var _pn=performance.now.bind(performance);",
294
- " performance.now=function(){return Math.round(_pn()*10)/10;};",
358
+ " var _pn=Performance.prototype.now;",
359
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
360
+ " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
295
361
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
296
- ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
362
+ " if(_pto&&_pto.get){",
363
+ " // S173: timeOrigin \u771F\u4F2A\u88C5\u2014\u2014\u5360\u4F4D\u5C42\uFF08\u8FD4\u56DE\u771F\u503C\uFF09\u5347\u7EA7\u4E3A\u4F1A\u8BDD\u7EA7\u968F\u673A\u504F\u79FB\u3002",
364
+ " // \u9632 cross-page \u5BFC\u822A\u65F6\u523B\u5173\u8054\uFF1A\u4E0D\u540C\u7AD9\u70B9\u7528 timeOrigin \u7CBE\u786E\u5BF9\u9F50\u7528\u6237\u884C\u4E3A\u6863\u6848\u3002",
365
+ " // \u504F\u79FB\u7531\u672C\u9875 _seed \u6D3E\u751F\uFF08\u9875\u5185\u81EA\u6D3D\uFF0Cnow()+\u504F\u79FB\u4E00\u81F4\uFF09\uFF0C\u8DE8\u9875\u968F\u673A\uFF08\u4E0D\u53EF\u5173\u8054\uFF09\u3002",
366
+ " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
367
+ " var _tog=function(){return _pto.get.call(performance)+_toff;};",
368
+ ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
369
+ " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
370
+ ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
371
+ ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
372
+ " }",
297
373
  " }catch(e){}",
298
374
  // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
299
375
  // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
@@ -407,11 +483,19 @@ function buildStealthInitScript() {
407
483
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
408
484
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
409
485
  ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
486
+ ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
487
+ ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
488
+ ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
410
489
  " return _ts.call(this);",
411
490
  " };",
412
491
  // 4. onclick prototype hijack (dual-stream consistency)
413
492
  " var _ba=function(k,p){",
414
- ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
493
+ ' if(p==="isTrusted"){',
494
+ ' var orig=Reflect.get(k,"isTrusted");',
495
+ " if(orig===true)return true;",
496
+ ' if(k.type==="paste")return true;',
497
+ " return orig;",
498
+ " }",
415
499
  ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
416
500
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
417
501
  " }",