opensteer 0.8.0 → 0.8.2

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.
package/dist/cli/bin.cjs CHANGED
@@ -78,6 +78,38 @@ async function clearChromeSingletonEntries(userDataDir) {
78
78
  )
79
79
  );
80
80
  }
81
+ async function sanitizeChromeProfile(userDataDir) {
82
+ if (!fs.existsSync(userDataDir)) {
83
+ return;
84
+ }
85
+ const entries = await promises.readdir(userDataDir).catch(() => []);
86
+ const profileDirs = entries.filter(
87
+ (entry) => entry === "Default" || /^Profile \d+$/i.test(entry)
88
+ );
89
+ await Promise.all(profileDirs.map((dir) => sanitizeProfilePreferences(userDataDir, dir)));
90
+ }
91
+ async function sanitizeProfilePreferences(userDataDir, profileDir) {
92
+ const prefsPath = path6.join(userDataDir, profileDir, "Preferences");
93
+ if (!fs.existsSync(prefsPath)) {
94
+ return;
95
+ }
96
+ try {
97
+ const raw = await promises.readFile(prefsPath, "utf8");
98
+ const prefs = JSON.parse(raw);
99
+ const profile = prefs.profile ?? {};
100
+ if (profile.exit_type === "Normal" && profile.exited_cleanly === true) {
101
+ return;
102
+ }
103
+ profile.exit_type = "Normal";
104
+ profile.exited_cleanly = true;
105
+ prefs.profile = profile;
106
+ await promises.writeFile(prefsPath, JSON.stringify(prefs), "utf8");
107
+ await promises.rm(path6.join(userDataDir, profileDir, "Secure Preferences"), { force: true }).catch(
108
+ () => void 0
109
+ );
110
+ } catch {
111
+ }
112
+ }
81
113
  var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
82
114
  var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
83
115
  var WINDOWS_PROGRAM_FILES = process.env.PROGRAMFILES ?? "C:\\Program Files";
@@ -873,6 +905,7 @@ async function createBrowserProfileSnapshot(input) {
873
905
  ...profileDirectory === void 0 ? {} : { selectedProfileDirectory: profileDirectory }
874
906
  });
875
907
  await clearChromeSingletonEntries(targetUserDataDir);
908
+ await sanitizeChromeProfile(targetUserDataDir);
876
909
  }
877
910
  async function copyRootLevelEntries(input) {
878
911
  const entries = await promises.readdir(input.sourceUserDataDir).catch(() => []);
@@ -936,68 +969,88 @@ function generateStealthInitScript(profile) {
936
969
  const encodedProfile = JSON.stringify(profile);
937
970
  return `(() => {
938
971
  const profile = ${encodedProfile};
939
- const define = (target, key, value) => {
940
- Object.defineProperty(target, key, {
972
+
973
+ // --- navigator.webdriver safety net ---
974
+ // --disable-blink-features=AutomationControlled handles this at the flag level
975
+ // and CDP handles it at the protocol level, but some Chrome builds still leak
976
+ // webdriver=true when --remote-debugging-port is active.
977
+ if (navigator.webdriver === true) {
978
+ Object.defineProperty(Navigator.prototype, 'webdriver', {
941
979
  configurable: true,
942
- get: typeof value === "function" ? value : () => value,
980
+ get: function() { return false; },
943
981
  });
944
- };
945
- define(Navigator.prototype, 'webdriver', false);
946
- define(Navigator.prototype, 'platform', profile.platform === 'macos' ? 'MacIntel' : profile.platform === 'windows' ? 'Win32' : 'Linux x86_64');
947
- define(Navigator.prototype, 'userAgent', profile.userAgent);
948
- define(Navigator.prototype, 'language', profile.locale);
949
- define(Navigator.prototype, 'languages', [profile.locale, 'en']);
950
- define(Navigator.prototype, 'maxTouchPoints', profile.maxTouchPoints);
951
- define(window, 'devicePixelRatio', profile.devicePixelRatio);
952
- define(window.screen, 'width', profile.screenResolution.width);
953
- define(window.screen, 'height', profile.screenResolution.height);
954
- define(window.screen, 'availWidth', profile.screenResolution.width);
955
- define(window.screen, 'availHeight', profile.screenResolution.height - 40);
956
- if (document.fonts && typeof document.fonts.check === 'function') {
957
- const originalCheck = document.fonts.check.bind(document.fonts);
958
- document.fonts.check = function(font, text) {
959
- const family = String(font).match(/["']([^"']+)["']/)?.[1] || String(font).split(/s+/).at(-1);
960
- if (family && profile.fonts.includes(family)) {
961
- return true;
962
- }
963
- return originalCheck(font, text);
964
- };
965
982
  }
966
- const seedNoise = (seed, input) => {
967
- const value = Math.sin(seed + input * 12.9898) * 43758.5453;
983
+
984
+ // --- CDP Runtime.enable leak defense ---
985
+ var _wrap = function(name) {
986
+ var orig = console[name];
987
+ if (typeof orig !== 'function') return;
988
+ console[name] = new Proxy(orig, {
989
+ apply: function(target, thisArg, args) {
990
+ for (var i = 0; i < args.length; i++) {
991
+ if (args[i] instanceof Error) {
992
+ var d = Object.getOwnPropertyDescriptor(args[i], 'stack');
993
+ if (d && typeof d.get === 'function') return undefined;
994
+ }
995
+ }
996
+ return Reflect.apply(target, thisArg, args);
997
+ },
998
+ });
999
+ };
1000
+ ['debug', 'log', 'info', 'error', 'warn', 'trace', 'dir'].forEach(_wrap);
1001
+
1002
+ // --- Canvas fingerprint noise ---
1003
+ var seedNoise = function(seed, input) {
1004
+ var value = Math.sin(seed + input * 12.9898) * 43758.5453;
968
1005
  return value - Math.floor(value);
969
1006
  };
970
1007
  if (HTMLCanvasElement.prototype.toDataURL) {
971
- const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
972
- HTMLCanvasElement.prototype.toDataURL = function(...args) {
973
- const context = this.getContext('2d');
1008
+ var originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
1009
+ HTMLCanvasElement.prototype.toDataURL = function() {
1010
+ var context = this.getContext('2d');
974
1011
  if (context) {
975
- const x = Math.min(1, Math.max(0, this.width - 1));
976
- const y = Math.min(1, Math.max(0, this.height - 1));
977
- const imageData = context.getImageData(x, y, 1, 1);
1012
+ var x = Math.min(1, Math.max(0, this.width - 1));
1013
+ var y = Math.min(1, Math.max(0, this.height - 1));
1014
+ var imageData = context.getImageData(x, y, 1, 1);
978
1015
  imageData.data[0] = Math.max(0, Math.min(255, imageData.data[0] + Math.floor(seedNoise(profile.canvasNoiseSeed, 1) * 2)));
979
1016
  context.putImageData(imageData, x, y);
980
1017
  }
981
- return originalToDataURL.apply(this, args);
1018
+ return originalToDataURL.apply(this, arguments);
982
1019
  };
983
1020
  }
1021
+
1022
+ // --- WebGL vendor/renderer spoofing ---
984
1023
  if (typeof WebGLRenderingContext !== 'undefined') {
985
- const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
1024
+ var originalGetParameter = WebGLRenderingContext.prototype.getParameter;
986
1025
  WebGLRenderingContext.prototype.getParameter = function(parameter) {
987
1026
  if (parameter === 37445) return profile.webglVendor;
988
1027
  if (parameter === 37446) return profile.webglRenderer;
989
1028
  return originalGetParameter.call(this, parameter);
990
1029
  };
991
1030
  }
1031
+
1032
+ // --- AudioBuffer fingerprint noise ---
992
1033
  if (typeof AudioBuffer !== 'undefined' && typeof AnalyserNode !== 'undefined') {
993
- const originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData;
1034
+ var originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData;
994
1035
  AnalyserNode.prototype.getFloatFrequencyData = function(array) {
995
1036
  originalGetFloatFrequencyData.call(this, array);
996
- for (let index = 0; index < array.length; index += 16) {
1037
+ for (var index = 0; index < array.length; index += 16) {
997
1038
  array[index] += (seedNoise(profile.audioNoiseSeed, index) - 0.5) * 0.0001;
998
1039
  }
999
1040
  };
1000
1041
  }
1042
+
1043
+ // --- Font availability spoofing ---
1044
+ if (document.fonts && typeof document.fonts.check === 'function') {
1045
+ var originalCheck = document.fonts.check.bind(document.fonts);
1046
+ document.fonts.check = function(font, text) {
1047
+ var family = String(font).match(/["']([^"']+)["']/)?.[1] || String(font).split(/\\s+/).at(-1);
1048
+ if (family && profile.fonts.includes(family)) {
1049
+ return true;
1050
+ }
1051
+ return originalCheck(font, text);
1052
+ };
1053
+ }
1001
1054
  })();`;
1002
1055
  }
1003
1056
 
@@ -1043,25 +1096,95 @@ var STEALTH_INIT_SCRIPT = `(() => {
1043
1096
  ['debug', 'log', 'info', 'error', 'warn', 'trace', 'dir'].forEach(_wrap);
1044
1097
  })();`;
1045
1098
  async function injectBrowserStealthScripts(context, input = {}) {
1099
+ if (input.profile !== void 0 && input.page !== void 0) {
1100
+ await applyCdpStealthOverrides(context, input.page, input.profile);
1101
+ }
1046
1102
  if (typeof context.addInitScript === "function") {
1047
1103
  await context.addInitScript({
1048
1104
  content: input.profile === void 0 ? STEALTH_INIT_SCRIPT : generateStealthInitScript(input.profile)
1049
1105
  });
1050
1106
  }
1051
1107
  }
1108
+ function buildUserAgentMetadata(profile) {
1109
+ const majorVersion = profile.browserVersion.split(".")[0] ?? "136";
1110
+ const brands = [
1111
+ { brand: "Chromium", version: majorVersion },
1112
+ ...profile.browserBrand === "edge" ? [{ brand: "Microsoft Edge", version: majorVersion }] : [{ brand: "Google Chrome", version: majorVersion }],
1113
+ { brand: "Not-A.Brand", version: "99" }
1114
+ ];
1115
+ const fullVersionList = [
1116
+ { brand: "Chromium", version: profile.browserVersion },
1117
+ ...profile.browserBrand === "edge" ? [{ brand: "Microsoft Edge", version: profile.browserVersion }] : [{ brand: "Google Chrome", version: profile.browserVersion }],
1118
+ { brand: "Not-A.Brand", version: "99.0.0.0" }
1119
+ ];
1120
+ const platformMap = {
1121
+ macos: { platform: "macOS", platformVersion: "14.4.0", architecture: "arm" },
1122
+ windows: { platform: "Windows", platformVersion: "15.0.0", architecture: "x86" },
1123
+ linux: { platform: "Linux", platformVersion: "6.5.0", architecture: "x86" }
1124
+ };
1125
+ const platformInfo = platformMap[profile.platform] ?? platformMap.linux;
1126
+ return {
1127
+ brands,
1128
+ fullVersionList,
1129
+ platform: platformInfo.platform,
1130
+ platformVersion: platformInfo.platformVersion,
1131
+ architecture: platformInfo.architecture,
1132
+ model: "",
1133
+ mobile: false,
1134
+ bitness: "64",
1135
+ wow64: false
1136
+ };
1137
+ }
1138
+ async function applyCdpStealthOverrides(context, page, profile) {
1139
+ const contextWithCdp = context;
1140
+ if (typeof contextWithCdp.newCDPSession !== "function") {
1141
+ return;
1142
+ }
1143
+ let cdp;
1144
+ try {
1145
+ cdp = await contextWithCdp.newCDPSession(page);
1146
+ } catch {
1147
+ return;
1148
+ }
1149
+ try {
1150
+ const platformString = profile.platform === "macos" ? "MacIntel" : profile.platform === "windows" ? "Win32" : "Linux x86_64";
1151
+ await cdp.send("Network.setUserAgentOverride", {
1152
+ userAgent: profile.userAgent,
1153
+ acceptLanguage: `${profile.locale},en;q=0.9`,
1154
+ platform: platformString,
1155
+ userAgentMetadata: buildUserAgentMetadata(profile)
1156
+ });
1157
+ await cdp.send("Emulation.setDeviceMetricsOverride", {
1158
+ width: profile.viewport.width,
1159
+ height: profile.viewport.height,
1160
+ deviceScaleFactor: profile.devicePixelRatio,
1161
+ mobile: false,
1162
+ screenWidth: profile.screenResolution.width,
1163
+ screenHeight: profile.screenResolution.height
1164
+ });
1165
+ await cdp.send("Emulation.setLocaleOverride", {
1166
+ locale: profile.locale
1167
+ }).catch(() => void 0);
1168
+ await cdp.send("Emulation.setTimezoneOverride", {
1169
+ timezoneId: profile.timezoneId
1170
+ }).catch(() => void 0);
1171
+ await cdp.detach();
1172
+ } catch {
1173
+ }
1174
+ }
1052
1175
 
1053
1176
  // src/local-browser/stealth-profiles.ts
1054
1177
  var PROFILE_PRESETS = [
1055
1178
  {
1056
1179
  platform: "macos",
1057
1180
  browserBrand: "chrome",
1058
- browserVersion: "133.0.6943.99",
1059
- userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36",
1181
+ browserVersion: "136.0.7103.93",
1182
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
1060
1183
  viewport: { width: 1440, height: 900 },
1061
1184
  screenResolution: { width: 1512, height: 982 },
1062
1185
  devicePixelRatio: 2,
1063
1186
  maxTouchPoints: 0,
1064
- webglVendor: "Apple Inc.",
1187
+ webglVendor: "Apple",
1065
1188
  webglRenderer: "Apple M2",
1066
1189
  fonts: ["SF Pro Text", "Helvetica Neue", "Arial", "Menlo", "Apple Color Emoji"],
1067
1190
  locale: "en-US",
@@ -1070,8 +1193,8 @@ var PROFILE_PRESETS = [
1070
1193
  {
1071
1194
  platform: "windows",
1072
1195
  browserBrand: "chrome",
1073
- browserVersion: "133.0.6943.99",
1074
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36",
1196
+ browserVersion: "136.0.7103.93",
1197
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
1075
1198
  viewport: { width: 1536, height: 864 },
1076
1199
  screenResolution: { width: 1920, height: 1080 },
1077
1200
  devicePixelRatio: 1.25,
@@ -1085,8 +1208,8 @@ var PROFILE_PRESETS = [
1085
1208
  {
1086
1209
  platform: "windows",
1087
1210
  browserBrand: "edge",
1088
- browserVersion: "133.0.3065.82",
1089
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.3065.82",
1211
+ browserVersion: "136.0.3240.50",
1212
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.3240.50",
1090
1213
  viewport: { width: 1536, height: 864 },
1091
1214
  screenResolution: { width: 1920, height: 1080 },
1092
1215
  devicePixelRatio: 1.25,
@@ -1100,8 +1223,8 @@ var PROFILE_PRESETS = [
1100
1223
  {
1101
1224
  platform: "linux",
1102
1225
  browserBrand: "chrome",
1103
- browserVersion: "133.0.6943.99",
1104
- userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36",
1226
+ browserVersion: "136.0.7103.93",
1227
+ userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
1105
1228
  viewport: { width: 1366, height: 768 },
1106
1229
  screenResolution: { width: 1366, height: 768 },
1107
1230
  devicePixelRatio: 1,
@@ -9771,7 +9894,8 @@ var OpensteerBrowserManager = class {
9771
9894
  await clearChromeSingletonEntries(userDataDir);
9772
9895
  const launched = await launchOwnedBrowser({
9773
9896
  userDataDir,
9774
- ...this.launchOptions === void 0 ? {} : { launch: this.launchOptions }
9897
+ ...this.launchOptions === void 0 ? {} : { launch: this.launchOptions },
9898
+ ...this.contextOptions?.viewport === void 0 ? {} : { viewport: this.contextOptions.viewport }
9775
9899
  });
9776
9900
  try {
9777
9901
  return await this.createAttachedEngine({
@@ -9819,7 +9943,8 @@ var OpensteerBrowserManager = class {
9819
9943
  await this.ensurePersistentBrowserManifest(workspace);
9820
9944
  const launched = await launchOwnedBrowser({
9821
9945
  userDataDir: workspace.browserUserDataDir,
9822
- ...this.launchOptions === void 0 ? {} : { launch: this.launchOptions }
9946
+ ...this.launchOptions === void 0 ? {} : { launch: this.launchOptions },
9947
+ ...this.contextOptions?.viewport === void 0 ? {} : { viewport: this.contextOptions.viewport }
9823
9948
  });
9824
9949
  const liveRecord = {
9825
9950
  mode: "persistent",
@@ -9854,13 +9979,13 @@ var OpensteerBrowserManager = class {
9854
9979
  if (!context) {
9855
9980
  throw new Error("Connected browser did not expose a Chromium browser context.");
9856
9981
  }
9982
+ const page = input.freshTab || context.pages()[0] === void 0 ? await context.newPage() : context.pages()[0];
9983
+ await page.bringToFront?.();
9857
9984
  const stealthProfile = resolveStealthProfile(this.contextOptions?.stealthProfile);
9858
9985
  await injectBrowserStealthScripts(
9859
9986
  context,
9860
- stealthProfile === void 0 ? {} : { profile: stealthProfile }
9987
+ stealthProfile === void 0 ? {} : { profile: stealthProfile, page }
9861
9988
  );
9862
- const page = input.freshTab || context.pages()[0] === void 0 ? await context.newPage() : context.pages()[0];
9863
- await page.bringToFront?.();
9864
9989
  const engine = await enginePlaywright.createPlaywrightBrowserCoreEngine({
9865
9990
  browser,
9866
9991
  attachedContext: context,
@@ -10046,8 +10171,9 @@ async function resolveAttachEndpoint(browser) {
10046
10171
  async function launchOwnedBrowser(input) {
10047
10172
  await ensureDirectory(input.userDataDir);
10048
10173
  await clearChromeSingletonEntries(input.userDataDir);
10174
+ await sanitizeChromeProfile(input.userDataDir);
10049
10175
  const executablePath = resolveChromeExecutablePath(input.launch?.executablePath);
10050
- const args = buildChromeArgs(input.userDataDir, input.launch);
10176
+ const args = buildChromeArgs(input.userDataDir, input.launch, input.viewport);
10051
10177
  const child = child_process.spawn(executablePath, args, {
10052
10178
  stdio: ["ignore", "ignore", "pipe"],
10053
10179
  detached: process.platform !== "win32"
@@ -10074,7 +10200,8 @@ async function launchOwnedBrowser(input) {
10074
10200
  executablePath
10075
10201
  };
10076
10202
  }
10077
- function buildChromeArgs(userDataDir, launch) {
10203
+ function buildChromeArgs(userDataDir, launch, viewport) {
10204
+ const isHeadless = launch?.headless ?? true;
10078
10205
  const args = [
10079
10206
  "--remote-debugging-port=0",
10080
10207
  "--no-first-run",
@@ -10088,17 +10215,25 @@ function buildChromeArgs(userDataDir, launch) {
10088
10215
  "--disable-popup-blocking",
10089
10216
  "--disable-prompt-on-repost",
10090
10217
  "--disable-sync",
10218
+ "--disable-infobars",
10091
10219
  "--disable-features=Translate",
10092
10220
  "--enable-features=NetworkService,NetworkServiceInProcess",
10093
10221
  "--password-store=basic",
10094
10222
  "--use-mock-keychain",
10095
10223
  `--user-data-dir=${userDataDir}`
10096
10224
  ];
10097
- if (launch?.headless ?? true) {
10225
+ if (isHeadless) {
10098
10226
  args.push("--headless=new");
10099
- if (!(launch?.args ?? []).some((entry) => entry.startsWith("--window-size"))) {
10100
- args.push("--window-size=1280,800");
10101
- }
10227
+ }
10228
+ const hasUserWindowSize = (launch?.args ?? []).some(
10229
+ (entry) => entry.startsWith("--window-size")
10230
+ );
10231
+ if (!hasUserWindowSize) {
10232
+ const width = viewport?.width ?? 1440;
10233
+ const height = viewport?.height ?? 900;
10234
+ args.push(
10235
+ isHeadless ? `--window-size=${String(width)},${String(height)}` : `--window-size=${String(width)},${String(height + 150)}`
10236
+ );
10102
10237
  }
10103
10238
  args.push(...launch?.args ?? []);
10104
10239
  return args;
@@ -18194,8 +18329,8 @@ async function isExecutable(candidate) {
18194
18329
  }
18195
18330
  async function readDirSafe(directory) {
18196
18331
  try {
18197
- const { readdir: readdir3 } = await import('fs/promises');
18198
- return await readdir3(directory);
18332
+ const { readdir: readdir4 } = await import('fs/promises');
18333
+ return await readdir4(directory);
18199
18334
  } catch {
18200
18335
  return [];
18201
18336
  }