@xbrowser/cli 1.20.0 → 1.21.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-75TLPVVZ.js";
24
- import "./chunk-UYEJ66VX.js";
23
+ } from "./chunk-AVSTNODR.js";
24
+ import "./chunk-CLYZPSDR.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-T2KUVO4H.js";
23
+ } from "./chunk-ESJYANQO.js";
24
24
  import "./chunk-3KZ34AUC.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,12 +20,12 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-76EKW7YJ.js";
24
- import "./chunk-FDU5BCPV.js";
23
+ } from "./chunk-Z5RTK4GW.js";
24
+ import "./chunk-HWWG2ISJ.js";
25
25
  import "./chunk-3KZ34AUC.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
- import "./chunk-A6LPGFAL.js";
28
- import "./chunk-H2A5JUK5.js";
27
+ import "./chunk-WYETQ5C7.js";
28
+ import "./chunk-75DNUI6M.js";
29
29
  import "./chunk-KFQGP6VL.js";
30
30
  export {
31
31
  closeAllSessions,
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-FDU5BCPV.js";
17
+ } from "./chunk-HWWG2ISJ.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -23,7 +23,7 @@ import {
23
23
  launchChrome
24
24
  } from "./chunk-3KZ34AUC.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
- import "./chunk-H2A5JUK5.js";
26
+ import "./chunk-75DNUI6M.js";
27
27
  import "./chunk-KFQGP6VL.js";
28
28
  export {
29
29
  CDPConnection,
@@ -21,6 +21,25 @@ import { EventEmitter } from "events";
21
21
 
22
22
  // src/cdp-driver/stealth.ts
23
23
  var DEFAULT_STEALTH_CONFIG = {
24
+ // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
25
+ uaChProfile: {
26
+ brands: [
27
+ { brand: "Not=A?Brand", version: "99" },
28
+ { brand: "Google Chrome", version: "151" },
29
+ { brand: "Chromium", version: "151" }
30
+ ],
31
+ platform: "macOS",
32
+ platformVersion: "26.2.0",
33
+ architecture: "arm",
34
+ bitness: "64",
35
+ model: "",
36
+ uaFullVersion: "151.0.7922.175",
37
+ fullVersionList: [
38
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
39
+ { brand: "Google Chrome", version: "151.0.7922.175" },
40
+ { brand: "Chromium", version: "151.0.7922.175" }
41
+ ]
42
+ },
24
43
  bezierCurvature: [0.35, 0.6],
25
44
  noiseAmplitude: 5.5,
26
45
  overshootRange: [6, 14],
@@ -119,10 +138,13 @@ Object.assign(KEY_MAP, {
119
138
  "@": { key: "@", code: "Digit2", vk: 50, shift: true },
120
139
  "_": { key: "_", code: "Minus", vk: 189, shift: true }
121
140
  });
122
- function buildStealthInitScript() {
141
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
142
+ const prof = config.uaChProfile;
143
+ const profJson = prof ? JSON.stringify(prof) : "null";
123
144
  return [
124
145
  "(function(){",
125
146
  ' window.__xbStealthVer="57";',
147
+ " window.__xbUaChProf=" + profJson + ";",
126
148
  // 1. AEL event proxy
127
149
  " var o=EventTarget.prototype.addEventListener;",
128
150
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -216,6 +238,29 @@ function buildStealthInitScript() {
216
238
  ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
217
239
  ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
218
240
  " }",
241
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
242
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
243
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
244
+ " var prof = window.__xbUaChProf;",
245
+ " var uad={",
246
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
247
+ " mobile:false,",
248
+ " platform:prof.platform,",
249
+ " getHighEntropyValues:function(hints){",
250
+ " return Promise.resolve().then(function(){",
251
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
252
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
253
+ " uaFullVersion:prof.uaFullVersion,",
254
+ " fullVersionList:prof.fullVersionList};",
255
+ " var out={};",
256
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
257
+ " return out;",
258
+ " });",
259
+ " },",
260
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
261
+ " };",
262
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
263
+ " }",
219
264
  " }catch(e){}",
220
265
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
221
266
  // —— 真实浏览器失焦/后台时 hasFocus=false
@@ -2030,7 +2075,7 @@ var XBPageImpl = class _XBPageImpl {
2030
2075
  try {
2031
2076
  await this.conn.send(
2032
2077
  "Page.addScriptToEvaluateOnNewDocument",
2033
- { source: buildStealthInitScript() },
2078
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
2034
2079
  this.sessionId
2035
2080
  );
2036
2081
  } catch {
@@ -3287,6 +3332,10 @@ var XBContextImpl = class {
3287
3332
  this.options = opts;
3288
3333
  this.setupAutoAttach();
3289
3334
  }
3335
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3336
+ get stealthConfig() {
3337
+ return this.options.stealthConfig;
3338
+ }
3290
3339
  async newPage() {
3291
3340
  if (this.closed) throw new Error("Context is closed");
3292
3341
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3453,20 +3502,8 @@ var XBContextImpl = class {
3453
3502
 
3454
3503
  // src/cdp-driver/browser.ts
3455
3504
  var XBBrowserImpl = class {
3456
- conn;
3457
- _emitter = new EventEmitter3();
3458
- _contexts = /* @__PURE__ */ new Map();
3459
- _disconnected = false;
3460
- childProcess = null;
3461
- tmpDir;
3462
- _exitHandler = null;
3463
- /**
3464
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3465
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3466
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3467
- */
3468
- cdpEndpoint;
3469
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3505
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3506
+ this.launchOpts = launchOpts;
3470
3507
  this.conn = conn;
3471
3508
  this.childProcess = childProcess ?? null;
3472
3509
  this.tmpDir = tmpDir;
@@ -3494,6 +3531,20 @@ var XBBrowserImpl = class {
3494
3531
  process.on("exit", this._exitHandler);
3495
3532
  }
3496
3533
  }
3534
+ launchOpts;
3535
+ conn;
3536
+ _emitter = new EventEmitter3();
3537
+ _contexts = /* @__PURE__ */ new Map();
3538
+ _disconnected = false;
3539
+ childProcess = null;
3540
+ tmpDir;
3541
+ _exitHandler = null;
3542
+ /**
3543
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3544
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3545
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3546
+ */
3547
+ cdpEndpoint;
3497
3548
  get disconnected() {
3498
3549
  return this._disconnected;
3499
3550
  }
@@ -3521,6 +3572,9 @@ var XBBrowserImpl = class {
3521
3572
  this._emitter.emit("disconnected");
3522
3573
  }
3523
3574
  async newContext(opts = {}) {
3575
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3576
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3577
+ }
3524
3578
  if (this._disconnected) {
3525
3579
  throw new Error("Browser is disconnected");
3526
3580
  }
@@ -3747,6 +3801,9 @@ function extractAllStrings(rawArgs) {
3747
3801
  }
3748
3802
  return strings.length > 0 ? strings.join("\n") : null;
3749
3803
  }
3804
+ function isProbeMarked(code) {
3805
+ return code.includes("/* @xb-probe */");
3806
+ }
3750
3807
 
3751
3808
  // src/cdp-interceptor/rules/dom-mutation.ts
3752
3809
  var DOM_PATTERNS = [
@@ -4309,6 +4366,7 @@ var eventSimulationRule = {
4309
4366
  evaluate(ctx) {
4310
4367
  const userCode = extractUserCode(ctx);
4311
4368
  if (!userCode) return null;
4369
+ if (isProbeMarked(userCode)) return null;
4312
4370
  for (const p of EVENT_PATTERNS) {
4313
4371
  if (p.pattern.test(userCode)) {
4314
4372
  return {
@@ -4636,6 +4694,11 @@ function createRuleEngine(customRules) {
4636
4694
  ...ctx,
4637
4695
  sessionState: getSessionState(ctx.sessionId)
4638
4696
  };
4697
+ try {
4698
+ const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
4699
+ if (isProbeMarked(code)) return null;
4700
+ } catch {
4701
+ }
4639
4702
  for (const rule of rules) {
4640
4703
  if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
4641
4704
  const decision = rule.evaluate(fullCtx);
@@ -5017,7 +5080,13 @@ async function launch(options = {}) {
5017
5080
  const conn = new CDPConnection(wsEndpoint);
5018
5081
  await conn.ready();
5019
5082
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
5020
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
5083
+ const browser = new XBBrowserImpl(
5084
+ conn,
5085
+ childProcess,
5086
+ tmpDir,
5087
+ httpEndpoint,
5088
+ options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
5089
+ );
5021
5090
  return { browser, wsEndpoint };
5022
5091
  }
5023
5092
  export {
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-UYEJ66VX.js";
17
+ } from "./chunk-CLYZPSDR.js";
18
18
  import "./chunk-3FWLW7FS.js";
19
19
  import {
20
20
  connectToCDP,
@@ -39,6 +39,9 @@ function extractAllStrings(rawArgs) {
39
39
  }
40
40
  return strings.length > 0 ? strings.join("\n") : null;
41
41
  }
42
+ function isProbeMarked(code) {
43
+ return code.includes("/* @xb-probe */");
44
+ }
42
45
 
43
46
  // src/cdp-interceptor/rules/dom-mutation.ts
44
47
  var DOM_PATTERNS = [
@@ -601,6 +604,7 @@ var eventSimulationRule = {
601
604
  evaluate(ctx) {
602
605
  const userCode = extractUserCode(ctx);
603
606
  if (!userCode) return null;
607
+ if (isProbeMarked(userCode)) return null;
604
608
  for (const p of EVENT_PATTERNS) {
605
609
  if (p.pattern.test(userCode)) {
606
610
  return {
@@ -928,6 +932,11 @@ function createRuleEngine(customRules) {
928
932
  ...ctx,
929
933
  sessionState: getSessionState(ctx.sessionId)
930
934
  };
935
+ try {
936
+ const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
937
+ if (isProbeMarked(code)) return null;
938
+ } catch {
939
+ }
931
940
  for (const rule of rules) {
932
941
  if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
933
942
  const decision = rule.evaluate(fullCtx);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-UYEJ66VX.js";
4
+ } from "./chunk-CLYZPSDR.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
@@ -24,6 +24,25 @@ import { EventEmitter } from "events";
24
24
 
25
25
  // src/cdp-driver/stealth.ts
26
26
  var DEFAULT_STEALTH_CONFIG = {
27
+ // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
28
+ uaChProfile: {
29
+ brands: [
30
+ { brand: "Not=A?Brand", version: "99" },
31
+ { brand: "Google Chrome", version: "151" },
32
+ { brand: "Chromium", version: "151" }
33
+ ],
34
+ platform: "macOS",
35
+ platformVersion: "26.2.0",
36
+ architecture: "arm",
37
+ bitness: "64",
38
+ model: "",
39
+ uaFullVersion: "151.0.7922.175",
40
+ fullVersionList: [
41
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
42
+ { brand: "Google Chrome", version: "151.0.7922.175" },
43
+ { brand: "Chromium", version: "151.0.7922.175" }
44
+ ]
45
+ },
27
46
  bezierCurvature: [0.35, 0.6],
28
47
  noiseAmplitude: 5.5,
29
48
  overshootRange: [6, 14],
@@ -130,10 +149,13 @@ Object.assign(KEY_MAP, {
130
149
  "@": { key: "@", code: "Digit2", vk: 50, shift: true },
131
150
  "_": { key: "_", code: "Minus", vk: 189, shift: true }
132
151
  });
133
- function buildStealthInitScript() {
152
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
153
+ const prof = config.uaChProfile;
154
+ const profJson = prof ? JSON.stringify(prof) : "null";
134
155
  return [
135
156
  "(function(){",
136
157
  ' window.__xbStealthVer="57";',
158
+ " window.__xbUaChProf=" + profJson + ";",
137
159
  // 1. AEL event proxy
138
160
  " var o=EventTarget.prototype.addEventListener;",
139
161
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -227,6 +249,29 @@ function buildStealthInitScript() {
227
249
  ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
228
250
  ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
229
251
  " }",
252
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
253
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
254
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
255
+ " var prof = window.__xbUaChProf;",
256
+ " var uad={",
257
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
258
+ " mobile:false,",
259
+ " platform:prof.platform,",
260
+ " getHighEntropyValues:function(hints){",
261
+ " return Promise.resolve().then(function(){",
262
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
263
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
264
+ " uaFullVersion:prof.uaFullVersion,",
265
+ " fullVersionList:prof.fullVersionList};",
266
+ " var out={};",
267
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
268
+ " return out;",
269
+ " });",
270
+ " },",
271
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
272
+ " };",
273
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
274
+ " }",
230
275
  " }catch(e){}",
231
276
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
232
277
  // —— 真实浏览器失焦/后台时 hasFocus=false
@@ -1939,7 +1984,7 @@ var XBPageImpl = class _XBPageImpl {
1939
1984
  try {
1940
1985
  await this.conn.send(
1941
1986
  "Page.addScriptToEvaluateOnNewDocument",
1942
- { source: buildStealthInitScript() },
1987
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
1943
1988
  this.sessionId
1944
1989
  );
1945
1990
  } catch {
@@ -3196,6 +3241,10 @@ var XBContextImpl = class {
3196
3241
  this.options = opts;
3197
3242
  this.setupAutoAttach();
3198
3243
  }
3244
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3245
+ get stealthConfig() {
3246
+ return this.options.stealthConfig;
3247
+ }
3199
3248
  async newPage() {
3200
3249
  if (this.closed) throw new Error("Context is closed");
3201
3250
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3362,20 +3411,8 @@ var XBContextImpl = class {
3362
3411
 
3363
3412
  // src/cdp-driver/browser.ts
3364
3413
  var XBBrowserImpl = class {
3365
- conn;
3366
- _emitter = new EventEmitter3();
3367
- _contexts = /* @__PURE__ */ new Map();
3368
- _disconnected = false;
3369
- childProcess = null;
3370
- tmpDir;
3371
- _exitHandler = null;
3372
- /**
3373
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3374
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3375
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3376
- */
3377
- cdpEndpoint;
3378
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3414
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3415
+ this.launchOpts = launchOpts;
3379
3416
  this.conn = conn;
3380
3417
  this.childProcess = childProcess ?? null;
3381
3418
  this.tmpDir = tmpDir;
@@ -3403,6 +3440,20 @@ var XBBrowserImpl = class {
3403
3440
  process.on("exit", this._exitHandler);
3404
3441
  }
3405
3442
  }
3443
+ launchOpts;
3444
+ conn;
3445
+ _emitter = new EventEmitter3();
3446
+ _contexts = /* @__PURE__ */ new Map();
3447
+ _disconnected = false;
3448
+ childProcess = null;
3449
+ tmpDir;
3450
+ _exitHandler = null;
3451
+ /**
3452
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3453
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3454
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3455
+ */
3456
+ cdpEndpoint;
3406
3457
  get disconnected() {
3407
3458
  return this._disconnected;
3408
3459
  }
@@ -3430,6 +3481,9 @@ var XBBrowserImpl = class {
3430
3481
  this._emitter.emit("disconnected");
3431
3482
  }
3432
3483
  async newContext(opts = {}) {
3484
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3485
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3486
+ }
3433
3487
  if (this._disconnected) {
3434
3488
  throw new Error("Browser is disconnected");
3435
3489
  }
@@ -3656,6 +3710,9 @@ function extractAllStrings(rawArgs) {
3656
3710
  }
3657
3711
  return strings.length > 0 ? strings.join("\n") : null;
3658
3712
  }
3713
+ function isProbeMarked(code) {
3714
+ return code.includes("/* @xb-probe */");
3715
+ }
3659
3716
 
3660
3717
  // src/cdp-interceptor/rules/dom-mutation.ts
3661
3718
  var DOM_PATTERNS = [
@@ -4218,6 +4275,7 @@ var eventSimulationRule = {
4218
4275
  evaluate(ctx) {
4219
4276
  const userCode = extractUserCode(ctx);
4220
4277
  if (!userCode) return null;
4278
+ if (isProbeMarked(userCode)) return null;
4221
4279
  for (const p of EVENT_PATTERNS) {
4222
4280
  if (p.pattern.test(userCode)) {
4223
4281
  return {
@@ -4545,6 +4603,11 @@ function createRuleEngine(customRules) {
4545
4603
  ...ctx,
4546
4604
  sessionState: getSessionState(ctx.sessionId)
4547
4605
  };
4606
+ try {
4607
+ const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
4608
+ if (isProbeMarked(code)) return null;
4609
+ } catch {
4610
+ }
4548
4611
  for (const rule of rules) {
4549
4612
  if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
4550
4613
  const decision = rule.evaluate(fullCtx);
@@ -4926,7 +4989,13 @@ async function launch(options = {}) {
4926
4989
  const conn = new CDPConnection(wsEndpoint);
4927
4990
  await conn.ready();
4928
4991
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4929
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
4992
+ const browser = new XBBrowserImpl(
4993
+ conn,
4994
+ childProcess,
4995
+ tmpDir,
4996
+ httpEndpoint,
4997
+ options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
4998
+ );
4930
4999
  return { browser, wsEndpoint };
4931
5000
  }
4932
5001
 
@@ -26,6 +26,25 @@ import { EventEmitter } from "events";
26
26
 
27
27
  // src/cdp-driver/stealth.ts
28
28
  var DEFAULT_STEALTH_CONFIG = {
29
+ // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
30
+ uaChProfile: {
31
+ brands: [
32
+ { brand: "Not=A?Brand", version: "99" },
33
+ { brand: "Google Chrome", version: "151" },
34
+ { brand: "Chromium", version: "151" }
35
+ ],
36
+ platform: "macOS",
37
+ platformVersion: "26.2.0",
38
+ architecture: "arm",
39
+ bitness: "64",
40
+ model: "",
41
+ uaFullVersion: "151.0.7922.175",
42
+ fullVersionList: [
43
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
44
+ { brand: "Google Chrome", version: "151.0.7922.175" },
45
+ { brand: "Chromium", version: "151.0.7922.175" }
46
+ ]
47
+ },
29
48
  bezierCurvature: [0.35, 0.6],
30
49
  noiseAmplitude: 5.5,
31
50
  overshootRange: [6, 14],
@@ -132,10 +151,13 @@ Object.assign(KEY_MAP, {
132
151
  "@": { key: "@", code: "Digit2", vk: 50, shift: true },
133
152
  "_": { key: "_", code: "Minus", vk: 189, shift: true }
134
153
  });
135
- function buildStealthInitScript() {
154
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
155
+ const prof = config.uaChProfile;
156
+ const profJson = prof ? JSON.stringify(prof) : "null";
136
157
  return [
137
158
  "(function(){",
138
159
  ' window.__xbStealthVer="57";',
160
+ " window.__xbUaChProf=" + profJson + ";",
139
161
  // 1. AEL event proxy
140
162
  " var o=EventTarget.prototype.addEventListener;",
141
163
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -229,6 +251,29 @@ function buildStealthInitScript() {
229
251
  ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
230
252
  ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
231
253
  " }",
254
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
255
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
256
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
257
+ " var prof = window.__xbUaChProf;",
258
+ " var uad={",
259
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
260
+ " mobile:false,",
261
+ " platform:prof.platform,",
262
+ " getHighEntropyValues:function(hints){",
263
+ " return Promise.resolve().then(function(){",
264
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
265
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
266
+ " uaFullVersion:prof.uaFullVersion,",
267
+ " fullVersionList:prof.fullVersionList};",
268
+ " var out={};",
269
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
270
+ " return out;",
271
+ " });",
272
+ " },",
273
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
274
+ " };",
275
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
276
+ " }",
232
277
  " }catch(e){}",
233
278
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
234
279
  // —— 真实浏览器失焦/后台时 hasFocus=false
@@ -2043,7 +2088,7 @@ var XBPageImpl = class _XBPageImpl {
2043
2088
  try {
2044
2089
  await this.conn.send(
2045
2090
  "Page.addScriptToEvaluateOnNewDocument",
2046
- { source: buildStealthInitScript() },
2091
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
2047
2092
  this.sessionId
2048
2093
  );
2049
2094
  } catch {
@@ -3300,6 +3345,10 @@ var XBContextImpl = class {
3300
3345
  this.options = opts;
3301
3346
  this.setupAutoAttach();
3302
3347
  }
3348
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3349
+ get stealthConfig() {
3350
+ return this.options.stealthConfig;
3351
+ }
3303
3352
  async newPage() {
3304
3353
  if (this.closed) throw new Error("Context is closed");
3305
3354
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3466,20 +3515,8 @@ var XBContextImpl = class {
3466
3515
 
3467
3516
  // src/cdp-driver/browser.ts
3468
3517
  var XBBrowserImpl = class {
3469
- conn;
3470
- _emitter = new EventEmitter3();
3471
- _contexts = /* @__PURE__ */ new Map();
3472
- _disconnected = false;
3473
- childProcess = null;
3474
- tmpDir;
3475
- _exitHandler = null;
3476
- /**
3477
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3478
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3479
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3480
- */
3481
- cdpEndpoint;
3482
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3518
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3519
+ this.launchOpts = launchOpts;
3483
3520
  this.conn = conn;
3484
3521
  this.childProcess = childProcess ?? null;
3485
3522
  this.tmpDir = tmpDir;
@@ -3507,6 +3544,20 @@ var XBBrowserImpl = class {
3507
3544
  process.on("exit", this._exitHandler);
3508
3545
  }
3509
3546
  }
3547
+ launchOpts;
3548
+ conn;
3549
+ _emitter = new EventEmitter3();
3550
+ _contexts = /* @__PURE__ */ new Map();
3551
+ _disconnected = false;
3552
+ childProcess = null;
3553
+ tmpDir;
3554
+ _exitHandler = null;
3555
+ /**
3556
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3557
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3558
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3559
+ */
3560
+ cdpEndpoint;
3510
3561
  get disconnected() {
3511
3562
  return this._disconnected;
3512
3563
  }
@@ -3534,6 +3585,9 @@ var XBBrowserImpl = class {
3534
3585
  this._emitter.emit("disconnected");
3535
3586
  }
3536
3587
  async newContext(opts = {}) {
3588
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3589
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3590
+ }
3537
3591
  if (this._disconnected) {
3538
3592
  throw new Error("Browser is disconnected");
3539
3593
  }
@@ -3760,6 +3814,9 @@ function extractAllStrings(rawArgs) {
3760
3814
  }
3761
3815
  return strings.length > 0 ? strings.join("\n") : null;
3762
3816
  }
3817
+ function isProbeMarked(code) {
3818
+ return code.includes("/* @xb-probe */");
3819
+ }
3763
3820
 
3764
3821
  // src/cdp-interceptor/rules/dom-mutation.ts
3765
3822
  var DOM_PATTERNS = [
@@ -4322,6 +4379,7 @@ var eventSimulationRule = {
4322
4379
  evaluate(ctx) {
4323
4380
  const userCode = extractUserCode(ctx);
4324
4381
  if (!userCode) return null;
4382
+ if (isProbeMarked(userCode)) return null;
4325
4383
  for (const p of EVENT_PATTERNS) {
4326
4384
  if (p.pattern.test(userCode)) {
4327
4385
  return {
@@ -4649,6 +4707,11 @@ function createRuleEngine(customRules) {
4649
4707
  ...ctx,
4650
4708
  sessionState: getSessionState(ctx.sessionId)
4651
4709
  };
4710
+ try {
4711
+ const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
4712
+ if (isProbeMarked(code)) return null;
4713
+ } catch {
4714
+ }
4652
4715
  for (const rule of rules) {
4653
4716
  if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
4654
4717
  const decision = rule.evaluate(fullCtx);
@@ -4970,7 +5033,13 @@ async function launch(options = {}) {
4970
5033
  const conn = new CDPConnection(wsEndpoint);
4971
5034
  await conn.ready();
4972
5035
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4973
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
5036
+ const browser = new XBBrowserImpl(
5037
+ conn,
5038
+ childProcess,
5039
+ tmpDir,
5040
+ httpEndpoint,
5041
+ options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
5042
+ );
4974
5043
  return { browser, wsEndpoint };
4975
5044
  }
4976
5045
 
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-GDKLH7ZY.js";
8
8
  import {
9
9
  createRuleEngine
10
- } from "./chunk-H2A5JUK5.js";
10
+ } from "./chunk-75DNUI6M.js";
11
11
  import {
12
12
  __require
13
13
  } from "./chunk-KFQGP6VL.js";
@@ -23,6 +23,25 @@ import { EventEmitter } from "events";
23
23
 
24
24
  // src/cdp-driver/stealth.ts
25
25
  var DEFAULT_STEALTH_CONFIG = {
26
+ // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
27
+ uaChProfile: {
28
+ brands: [
29
+ { brand: "Not=A?Brand", version: "99" },
30
+ { brand: "Google Chrome", version: "151" },
31
+ { brand: "Chromium", version: "151" }
32
+ ],
33
+ platform: "macOS",
34
+ platformVersion: "26.2.0",
35
+ architecture: "arm",
36
+ bitness: "64",
37
+ model: "",
38
+ uaFullVersion: "151.0.7922.175",
39
+ fullVersionList: [
40
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
41
+ { brand: "Google Chrome", version: "151.0.7922.175" },
42
+ { brand: "Chromium", version: "151.0.7922.175" }
43
+ ]
44
+ },
26
45
  bezierCurvature: [0.35, 0.6],
27
46
  noiseAmplitude: 5.5,
28
47
  overshootRange: [6, 14],
@@ -129,10 +148,13 @@ Object.assign(KEY_MAP, {
129
148
  "@": { key: "@", code: "Digit2", vk: 50, shift: true },
130
149
  "_": { key: "_", code: "Minus", vk: 189, shift: true }
131
150
  });
132
- function buildStealthInitScript() {
151
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
152
+ const prof = config.uaChProfile;
153
+ const profJson = prof ? JSON.stringify(prof) : "null";
133
154
  return [
134
155
  "(function(){",
135
156
  ' window.__xbStealthVer="57";',
157
+ " window.__xbUaChProf=" + profJson + ";",
136
158
  // 1. AEL event proxy
137
159
  " var o=EventTarget.prototype.addEventListener;",
138
160
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -226,6 +248,29 @@ function buildStealthInitScript() {
226
248
  ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
227
249
  ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
228
250
  " }",
251
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
252
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
253
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
254
+ " var prof = window.__xbUaChProf;",
255
+ " var uad={",
256
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
257
+ " mobile:false,",
258
+ " platform:prof.platform,",
259
+ " getHighEntropyValues:function(hints){",
260
+ " return Promise.resolve().then(function(){",
261
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
262
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
263
+ " uaFullVersion:prof.uaFullVersion,",
264
+ " fullVersionList:prof.fullVersionList};",
265
+ " var out={};",
266
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
267
+ " return out;",
268
+ " });",
269
+ " },",
270
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
271
+ " };",
272
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
273
+ " }",
229
274
  " }catch(e){}",
230
275
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
231
276
  // —— 真实浏览器失焦/后台时 hasFocus=false
@@ -2040,7 +2085,7 @@ var XBPageImpl = class _XBPageImpl {
2040
2085
  try {
2041
2086
  await this.conn.send(
2042
2087
  "Page.addScriptToEvaluateOnNewDocument",
2043
- { source: buildStealthInitScript() },
2088
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
2044
2089
  this.sessionId
2045
2090
  );
2046
2091
  } catch {
@@ -3297,6 +3342,10 @@ var XBContextImpl = class {
3297
3342
  this.options = opts;
3298
3343
  this.setupAutoAttach();
3299
3344
  }
3345
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3346
+ get stealthConfig() {
3347
+ return this.options.stealthConfig;
3348
+ }
3300
3349
  async newPage() {
3301
3350
  if (this.closed) throw new Error("Context is closed");
3302
3351
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3463,20 +3512,8 @@ var XBContextImpl = class {
3463
3512
 
3464
3513
  // src/cdp-driver/browser.ts
3465
3514
  var XBBrowserImpl = class {
3466
- conn;
3467
- _emitter = new EventEmitter3();
3468
- _contexts = /* @__PURE__ */ new Map();
3469
- _disconnected = false;
3470
- childProcess = null;
3471
- tmpDir;
3472
- _exitHandler = null;
3473
- /**
3474
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3475
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3476
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3477
- */
3478
- cdpEndpoint;
3479
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3515
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3516
+ this.launchOpts = launchOpts;
3480
3517
  this.conn = conn;
3481
3518
  this.childProcess = childProcess ?? null;
3482
3519
  this.tmpDir = tmpDir;
@@ -3504,6 +3541,20 @@ var XBBrowserImpl = class {
3504
3541
  process.on("exit", this._exitHandler);
3505
3542
  }
3506
3543
  }
3544
+ launchOpts;
3545
+ conn;
3546
+ _emitter = new EventEmitter3();
3547
+ _contexts = /* @__PURE__ */ new Map();
3548
+ _disconnected = false;
3549
+ childProcess = null;
3550
+ tmpDir;
3551
+ _exitHandler = null;
3552
+ /**
3553
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3554
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3555
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3556
+ */
3557
+ cdpEndpoint;
3507
3558
  get disconnected() {
3508
3559
  return this._disconnected;
3509
3560
  }
@@ -3531,6 +3582,9 @@ var XBBrowserImpl = class {
3531
3582
  this._emitter.emit("disconnected");
3532
3583
  }
3533
3584
  async newContext(opts = {}) {
3585
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3586
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3587
+ }
3534
3588
  if (this._disconnected) {
3535
3589
  throw new Error("Browser is disconnected");
3536
3590
  }
@@ -4084,7 +4138,13 @@ async function launch(options = {}) {
4084
4138
  const conn = new CDPConnection(wsEndpoint);
4085
4139
  await conn.ready();
4086
4140
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4087
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
4141
+ const browser = new XBBrowserImpl(
4142
+ conn,
4143
+ childProcess,
4144
+ tmpDir,
4145
+ httpEndpoint,
4146
+ options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
4147
+ );
4088
4148
  return { browser, wsEndpoint };
4089
4149
  }
4090
4150
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuleEngine
3
- } from "./chunk-H2A5JUK5.js";
3
+ } from "./chunk-75DNUI6M.js";
4
4
 
5
5
  // src/cdp-interceptor/proxy.ts
6
6
  import { WebSocketServer, WebSocket } from "ws";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-FDU5BCPV.js";
3
+ } from "./chunk-HWWG2ISJ.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
7
7
  import {
8
8
  CDPInterceptorProxy
9
- } from "./chunk-A6LPGFAL.js";
9
+ } from "./chunk-WYETQ5C7.js";
10
10
 
11
11
  // src/browser.ts
12
12
  import { randomUUID } from "crypto";
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ import {
72
72
  setActivePage,
73
73
  sleep,
74
74
  wheelDelta
75
- } from "./chunk-T2KUVO4H.js";
75
+ } from "./chunk-ESJYANQO.js";
76
76
  import "./chunk-3KZ34AUC.js";
77
77
  import {
78
78
  errMsg
@@ -8248,7 +8248,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8248
8248
  }
8249
8249
  let targetPageOverride = null;
8250
8250
  if (_target && extraOpts?.cdpEndpoint) {
8251
- const { findTargetPage } = await import("./browser-Y6B3NS4Q.js");
8251
+ const { findTargetPage } = await import("./browser-CNBSH53B.js");
8252
8252
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8253
8253
  if (!targetPageOverride) {
8254
8254
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -14433,7 +14433,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14433
14433
  const targetPage = pages[cmdTabIndex];
14434
14434
  await targetPage.bringToFront().catch(() => {
14435
14435
  });
14436
- const { setActivePage: setActivePage2 } = await import("./browser-Y6B3NS4Q.js");
14436
+ const { setActivePage: setActivePage2 } = await import("./browser-CNBSH53B.js");
14437
14437
  setActivePage2(session, targetPage);
14438
14438
  }
14439
14439
  }
@@ -14709,7 +14709,7 @@ async function main() {
14709
14709
  const command = process.argv[2];
14710
14710
  const isLongRunning = command === "preview" || command === "serve";
14711
14711
  if (!isLongRunning) {
14712
- const { ensureProcessCanExit } = await import("./browser-Y6B3NS4Q.js");
14712
+ const { ensureProcessCanExit } = await import("./browser-CNBSH53B.js");
14713
14713
  await ensureProcessCanExit().catch(() => {
14714
14714
  });
14715
14715
  process.exit(process.exitCode || exitCode);
@@ -34,13 +34,13 @@ import {
34
34
  resolveLaunchOpts,
35
35
  saveSessionDiskMeta,
36
36
  setActivePage
37
- } from "./chunk-75TLPVVZ.js";
37
+ } from "./chunk-AVSTNODR.js";
38
38
  import {
39
39
  createRuleEngine,
40
40
  rand,
41
41
  sleep,
42
42
  wheelDelta
43
- } from "./chunk-UYEJ66VX.js";
43
+ } from "./chunk-CLYZPSDR.js";
44
44
  import {
45
45
  queryJS
46
46
  } from "./chunk-3FWLW7FS.js";
@@ -7766,7 +7766,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7766
7766
  }
7767
7767
  let targetPageOverride = null;
7768
7768
  if (_target && extraOpts?.cdpEndpoint) {
7769
- const { findTargetPage } = await import("./browser-PBBC7RAV.js");
7769
+ const { findTargetPage } = await import("./browser-C2VEOJC2.js");
7770
7770
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7771
7771
  if (!targetPageOverride) {
7772
7772
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9588,7 +9588,7 @@ function createRPCHandler() {
9588
9588
  if (isNewFormat) {
9589
9589
  try {
9590
9590
  const replayErrors = [];
9591
- const { SessionReplayer } = await import("./session-replayer-SBYJXQZZ.js");
9591
+ const { SessionReplayer } = await import("./session-replayer-EVD4HZBB.js");
9592
9592
  const replayer = new SessionReplayer({
9593
9593
  page: session.page,
9594
9594
  stepDelay: slowMo * 500,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,103 @@ import { ZodType, ZodTypeDef, z } from 'zod';
6
6
 
7
7
  declare const version: any;
8
8
 
9
+ /**
10
+ * stealth.ts — Behavioral stealth layer for CDP driver
11
+ *
12
+ * Enhances mouse/keyboard with human-like patterns:
13
+ * • Bezier trajectory with cosine easing + noise + overshoot correction
14
+ * • Landing offset from element center (no dead-center clicks)
15
+ * • Press/release drift (finger micro-movement)
16
+ * • Three-tier typing rhythm with random pauses
17
+ * • Typo + backspace correction
18
+ * • Wheel exponential decay (momentum)
19
+ *
20
+ * Also provides init script for page-level stealth hooks:
21
+ *
22
+ * ── 维护红线(S183 站点观测实证)────────────────────────────
23
+ * 1. 永不覆写 navigator.webdriver —— 原生 false 即最佳伪装。
24
+ * 掘金等站点以 ~5s/次轮询 Navigator.prototype.webdriver 的
25
+ * descriptor(getter native + 实例无覆写 = 正常),任何覆写
26
+ * 都会在 descriptor 检测下露馅。
27
+ * 2. 实例覆写必须原型层同步(S172)—— 实例 defineProperty 可被
28
+ * X.prototype.p.call(target) 一行逃逸,见 stealth-audit.test.ts。
29
+ * 3. 覆写函数必须进 toString native 伪装名单(S172b 审计规则 R2)。
30
+ * ────────────────────────────────────────────────────────
31
+ * Also provides init script for page-level stealth hooks:
32
+ * • AEL event proxy (sourceCapabilities / isTrusted / coordinate floatification)
33
+ * • onclick prototype hijack (dual-stream consistency)
34
+ * • Screen/hasFocus override + toString disguise
35
+ *
36
+ * 124 rounds of attack-defense validated (output/cdp-duel-s5/)
37
+ */
38
+
39
+ interface UaChProfile {
40
+ /** GREASE brands 形状,例:Not=A?Brand:99(从真实环境采样) */
41
+ brands: Array<{
42
+ brand: string;
43
+ version: string;
44
+ }>;
45
+ /** 平台(macOS / Windows / Linux) */
46
+ platform: string;
47
+ /** 平台版本(高熵) */
48
+ platformVersion: string;
49
+ /** 架构(arm / x86) */
50
+ architecture: string;
51
+ /** 位宽(64 / 32) */
52
+ bitness: string;
53
+ /** 设备型号(桌面端通常空) */
54
+ model: string;
55
+ /** 完整版本号(uaFullVersion,例:151.0.7922.175) */
56
+ uaFullVersion: string;
57
+ /** 完整版本列表(fullVersionList,与 brands 同形状+全版本) */
58
+ fullVersionList: Array<{
59
+ brand: string;
60
+ version: string;
61
+ }>;
62
+ }
63
+ interface StealthConfig {
64
+ /**
65
+ * S191: UA-CH 真实档案——垫片在 navigator.userAgentData 缺失时
66
+ * 以此生成 API 表面(值来自真实有头 Chrome 采样,形状一致)。
67
+ * 未提供时从 UA 字符串派生兜底值。
68
+ */
69
+ uaChProfile?: UaChProfile;
70
+ /** Bezier curvature range [min, max] as fraction of distance */
71
+ bezierCurvature: [number, number];
72
+ /** Trajectory noise amplitude ±px */
73
+ noiseAmplitude: number;
74
+ /** Overshoot distance range [min, max] px */
75
+ overshootRange: [number, number];
76
+ /** Aim pause before click [min, max] ms */
77
+ aimPause: [number, number];
78
+ /** Press duration [min, max] ms */
79
+ pressDuration: [number, number];
80
+ /** Release coordinate drift [min, max] px */
81
+ releaseDrift: [number, number];
82
+ /** Landing offset for small elements [min, max] px */
83
+ landingOffsetSmall: [number, number];
84
+ /** Landing offset for large elements [min, max] px */
85
+ landingOffsetLarge: [number, number];
86
+ /** Elements smaller than this (px) use small offset */
87
+ smallElementThreshold: number;
88
+ /** Typing rhythm probabilities */
89
+ typingRhythm: {
90
+ fastProb: number;
91
+ fastRange: [number, number];
92
+ normalRange: [number, number];
93
+ pauseProb: number;
94
+ pauseRange: [number, number];
95
+ };
96
+ /** Key press duration [min, max] ms */
97
+ keyPressDuration: [number, number];
98
+ /** Typo probability per field (0-1) */
99
+ typoProbability: number;
100
+ /** Wheel peak delta */
101
+ wheelPeak: number;
102
+ /** Wheel decay rate */
103
+ wheelDecayRate: number;
104
+ }
105
+
9
106
  /**
10
107
  * XBrowser CDP Driver — Abstract Interface Types
11
108
  *
@@ -38,6 +135,8 @@ interface XBContextOptions {
38
135
  userAgent?: string;
39
136
  extraHTTPHeaders?: Record<string, string>;
40
137
  ignoreHTTPSErrors?: boolean;
138
+ /** S194: stealth 伪装配置(UA-CH 档案等)——经 context 传至 page 的注入层 */
139
+ stealthConfig?: Partial<StealthConfig>;
41
140
  }
42
141
  interface XBContext {
43
142
  newPage(): Promise<XBPage>;
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  resolveLaunchOpts,
19
19
  saveSessionDiskMeta,
20
20
  setActivePage
21
- } from "./chunk-76EKW7YJ.js";
21
+ } from "./chunk-Z5RTK4GW.js";
22
22
  import {
23
23
  detectAntiBot,
24
24
  formatDetectionMessage
@@ -86,7 +86,7 @@ import {
86
86
  rand,
87
87
  sleep,
88
88
  wheelDelta
89
- } from "./chunk-FDU5BCPV.js";
89
+ } from "./chunk-HWWG2ISJ.js";
90
90
  import "./chunk-3KZ34AUC.js";
91
91
  import {
92
92
  errMsg
@@ -94,7 +94,7 @@ import {
94
94
  import {
95
95
  CDPInterceptorProxy,
96
96
  advise
97
- } from "./chunk-A6LPGFAL.js";
97
+ } from "./chunk-WYETQ5C7.js";
98
98
  import {
99
99
  automationSignalsRule,
100
100
  createRuleEngine,
@@ -106,7 +106,7 @@ import {
106
106
  mouseTrajectoryRule,
107
107
  networkAnomalyRule,
108
108
  pageLifecycleRule
109
- } from "./chunk-H2A5JUK5.js";
109
+ } from "./chunk-75DNUI6M.js";
110
110
  import {
111
111
  SessionRecorder
112
112
  } from "./chunk-NODRQGOK.js";
@@ -8599,7 +8599,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8599
8599
  }
8600
8600
  let targetPageOverride = null;
8601
8601
  if (_target && extraOpts?.cdpEndpoint) {
8602
- const { findTargetPage } = await import("./browser-NVC7MTML.js");
8602
+ const { findTargetPage } = await import("./browser-VZOOFLAH.js");
8603
8603
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8604
8604
  if (!targetPageOverride) {
8605
8605
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -14822,7 +14822,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14822
14822
  const targetPage = pages[cmdTabIndex];
14823
14823
  await targetPage.bringToFront().catch(() => {
14824
14824
  });
14825
- const { setActivePage: setActivePage2 } = await import("./browser-NVC7MTML.js");
14825
+ const { setActivePage: setActivePage2 } = await import("./browser-VZOOFLAH.js");
14826
14826
  setActivePage2(session, targetPage);
14827
14827
  }
14828
14828
  }
@@ -17995,7 +17995,7 @@ var DataCollector = class {
17995
17995
  return results;
17996
17996
  }
17997
17997
  async createBrowserContext() {
17998
- const { launch } = await import("./cdp-driver-QYFA4QTP.js");
17998
+ const { launch } = await import("./cdp-driver-26LGCHXV.js");
17999
17999
  const { browser } = await launch({
18000
18000
  headless: true,
18001
18001
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -18035,7 +18035,7 @@ var DataCollector = class {
18035
18035
 
18036
18036
  // src/cdp-interceptor/index.ts
18037
18037
  async function createCDPInterceptor(config) {
18038
- const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-LUR4U5YF.js");
18038
+ const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-NKCYE76M.js");
18039
18039
  const proxy = new CDPInterceptorProxy2(config);
18040
18040
  await proxy.start();
18041
18041
  return proxy;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CDPInterceptorProxy
3
- } from "./chunk-A6LPGFAL.js";
4
- import "./chunk-H2A5JUK5.js";
3
+ } from "./chunk-WYETQ5C7.js";
4
+ import "./chunk-75DNUI6M.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
7
7
  CDPInterceptorProxy
@@ -34,7 +34,7 @@ var SessionReplayer = class {
34
34
  if (this.opts.page) {
35
35
  this.page = this.opts.page;
36
36
  } else if (this.opts.cdpUrl) {
37
- const { launch } = await import("./cdp-driver-73F5CVH2.js");
37
+ const { launch } = await import("./cdp-driver-KFX4A6WR.js");
38
38
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
39
39
  let contexts = browser.contexts();
40
40
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -75,6 +75,7 @@
75
75
  "lint": "eslint src/ bin/ --ext .ts",
76
76
  "lint:plugin-contract": "node lint-scripts/check-plugin-contract.mjs && node lint-scripts/check-plugin-status.mjs",
77
77
  "lint:plugin-status": "node lint-scripts/check-plugin-status.mjs --update",
78
+ "stealth:check": "vitest run tests/cdp-driver/stealth-health.test.ts tests/cdp-driver/stealth-audit.test.ts tests/cdp-driver/stealth-probe.test.ts",
78
79
  "lint:docs": "node lint-scripts/check-docs-consistency.mjs",
79
80
  "typecheck": "tsc --noEmit",
80
81
  "start": "node dist/cli.js",