granclaw 0.0.1-beta.22 → 0.0.1-beta.24

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.
@@ -217,4 +217,52 @@
217
217
  return result.replace(/at .*puppeteer_evaluation_script.*\n?/g, '');
218
218
  };
219
219
  });
220
+
221
+ // ── 10. navigator.userAgent — strip HeadlessChrome ─────────────────────
222
+ // Headless Chrome embeds "HeadlessChrome/" in the UA string instead of
223
+ // "Chrome/". Strip it here so JS-readable UA matches a real user session.
224
+ // Network-level UA is handled at launch via --user-agent; this covers
225
+ // all runtime reads (navigator.userAgent, performance.getEntriesByType, etc.)
226
+ safe(() => {
227
+ const realUA = navigator.userAgent.replace('HeadlessChrome/', 'Chrome/');
228
+ Object.defineProperty(Navigator.prototype, 'userAgent', {
229
+ get: () => realUA,
230
+ configurable: true,
231
+ });
232
+ });
233
+
234
+ // ── 11. navigator.userAgentData — Client Hints API ─────────────────────
235
+ // The modern Client Hints UA API also leaks "Headless" in its brand list.
236
+ // Patch brands to strip the "Headless" prefix so sites using getHighEntropyValues
237
+ // or brands directly see a normal Chrome brand string.
238
+ safe(() => {
239
+ if (typeof navigator.userAgentData === 'undefined') return;
240
+ const uad = navigator.userAgentData;
241
+ const brands = (uad.brands || []).map((b) => ({
242
+ brand: b.brand.replace(/^Headless/i, ''),
243
+ version: b.version,
244
+ }));
245
+ Object.defineProperty(Navigator.prototype, 'userAgentData', {
246
+ get: () => new Proxy(uad, {
247
+ get(target, prop) {
248
+ if (prop === 'brands') return brands;
249
+ if (prop === 'mobile') return false;
250
+ const val = Reflect.get(target, prop);
251
+ return typeof val === 'function' ? val.bind(target) : val;
252
+ },
253
+ }),
254
+ configurable: true,
255
+ });
256
+ });
257
+
258
+ // ── 12. navigator.deviceMemory ──────────────────────────────────────────
259
+ // Headless Chrome may expose the host's actual RAM or a low default.
260
+ // Spoofing 8 GB matches the most common desktop tier and avoids leaking
261
+ // the container/VM memory footprint to fingerprinting scripts.
262
+ safe(() => {
263
+ Object.defineProperty(Navigator.prototype, 'deviceMemory', {
264
+ get: () => 8,
265
+ configurable: true,
266
+ });
267
+ });
220
268
  })();
@@ -64,7 +64,10 @@ let cachedChromePath;
64
64
  function detectChromePath() {
65
65
  if (cachedChromePath !== undefined)
66
66
  return cachedChromePath;
67
- const override = process.env.GRANCLAW_CHROME_PATH;
67
+ // GRANCLAW_CHROME_PATH takes priority; fall back to the agent-browser env
68
+ // var so Docker containers (which set AGENT_BROWSER_CHROME_PATH=/usr/bin/chromium)
69
+ // automatically point stealth at the same binary agent-browser will use.
70
+ const override = process.env.GRANCLAW_CHROME_PATH || process.env.AGENT_BROWSER_CHROME_PATH;
68
71
  if (override && fs_1.default.existsSync(override)) {
69
72
  cachedChromePath = override;
70
73
  return override;
@@ -113,12 +116,9 @@ function detectChromePath() {
113
116
  * const argv = ['--session', agentId, ...stealthArgv(), 'open', url];
114
117
  */
115
118
  function stealthArgv() {
116
- // DISABLED 2026-04-11: the stealth extension / real-Chrome swap was
117
- // producing buggy behaviour in agent browser sessions. Keep the code
118
- // in place so we can re-enable with one edit once we understand why.
119
- // Set GRANCLAW_STEALTH_ENABLED=1 to opt back in.
120
- if (process.env.GRANCLAW_STEALTH_ENABLED !== '1')
121
- return [];
119
+ // Re-enabled 2026-04-14: UA/deviceMemory patches added, AGENT_BROWSER_CHROME_PATH
120
+ // wired up so Docker containers automatically use the in-place Chromium.
121
+ // Set GRANCLAW_STEALTH_DISABLED=1 to opt out entirely.
122
122
  if (process.env.GRANCLAW_STEALTH_DISABLED === '1')
123
123
  return [];
124
124
  const argv = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granclaw",
3
- "version": "0.0.1-beta.22",
3
+ "version": "0.0.1-beta.24",
4
4
  "description": "A personal AI assistant you run on your own machine.",
5
5
  "license": "MIT",
6
6
  "repository": {