fix(browser-extension): 1.9.8 — Firefox driving, hardened evaluate path

executeScript now tries world: 'MAIN' first (so callers reading
page-internal state still work) and silently falls back to ISOLATED on
the recognisable CSP-blocked-Function() error string. This rescues every
strict 'script-src self' page (banking sites, Porkbun account pages,
GitHub, Google Docs) without forcing the caller to know which world it
needs.

Default Runtime.evaluate timeout bumped from 10s → 30s. Page-walks
against jQuery-rendered domain-management UIs with 1.5k+ DOM nodes
routinely need more than 10s for the first selector pass; caller can
still override via params.timeout for short queries.

Together with 1.9.7's reconnect-chain fix the extension can now drive
any Firefox tab end-to-end: stays glued to its ZAP server through
restarts, evaluates JS regardless of page CSP, and exposes the result
on a clean shape (1.9.2's null-coercion + exception-details wiring).
This commit is contained in:
Hanzo AI
2026-05-08 11:51:47 -07:00
parent ea747cff45
commit 719cd51919
5 changed files with 37 additions and 22 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hanzo-ai-monorepo",
"version": "1.9.7",
"version": "1.9.8",
"private": true,
"description": "Hanzo AI Development Platform Monorepo",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hanzo/browser-extension",
"version": "1.9.7",
"version": "1.9.8",
"description": "Hanzo AI Browser Extension",
"main": "dist/background.js",
"scripts": {
+33 -18
View File
@@ -223,23 +223,36 @@ class HanzoFirefoxExtension {
// MV3 path — preferred on every modern build (Chrome 88+, FF 109+).
const scripting = (browser as any).scripting;
if (scripting?.executeScript) {
const results = await scripting.executeScript({
target: { tabId },
world: 'MAIN',
args: [code],
func: (codeStr: string) => {
try {
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
return Function(codeStr)();
} catch (e: any) {
return { __hanzo_error: e?.message || String(e) };
}
},
});
// scripting.executeScript returns an array of {result, frameId}.
// Surface the same shape the legacy MV2 API returned (one result
// per frame) so runInPage's caller doesn't have to change.
return Array.isArray(results) ? results.map((r: any) => r?.result) : [results];
const runIn = async (world: 'MAIN' | 'ISOLATED'): Promise<any[]> => {
const results = await scripting.executeScript({
target: { tabId },
world,
args: [code],
func: (codeStr: string) => {
try {
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
return Function(codeStr)();
} catch (e: any) {
return { __hanzo_error: e?.message || String(e) };
}
},
});
// scripting.executeScript returns an array of {result, frameId}.
return Array.isArray(results) ? results.map((r: any) => r?.result) : [results];
};
// Try MAIN (page CSP applies — see page-internal vars). If the page
// CSP blocks Function() the inner func returns __hanzo_error;
// ISOLATED world has its own CSP and almost always allows
// Function(), so we silently fall back. This rescues e.g. GitHub,
// Google Docs, banking sites with strict 'script-src self'.
const main = await runIn('MAIN');
const blocked = main.length > 0 && main.every((r) =>
r && typeof r === 'object' && r.__hanzo_error &&
/CSP|unsafe-eval|Content Security|Function|eval/i.test(String(r.__hanzo_error))
);
if (!blocked) return main;
return runIn('ISOLATED');
}
// MV2 fallback — only Firefox <109 reaches this branch.
const legacy = (browser as any).tabs?.executeScript;
@@ -517,7 +530,9 @@ class HanzoFirefoxExtension {
if (!tabId) throw new Error('No active tab');
// Accept both `expression` (CDP) and `code` (MCP/SDK convention).
const expression = (params.expression as string) ?? (params.code as string) ?? '';
const timeout = (params.timeout as number) || 10000;
// Default 30s — Porkbun-class pages with 1500+ DOM nodes routinely
// need more than 10s for first-paint queries. Caller can override.
const timeout = (params.timeout as number) || 30000;
// Caller can opt into Promise-await semantics. CDP names this flag
// `awaitPromise`; the MCP/SDK callers pass `await_promise`.
const awaitPromise = Boolean(params.awaitPromise ?? params.await_promise);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hanzo AI",
"version": "1.9.7",
"version": "1.9.8",
"description": "AI-powered dev assistant — click-to-code navigation, source maps, WebGPU AI, and MCP integration for Claude Code.",
"permissions": [
"activeTab",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hanzo AI",
"version": "1.9.7",
"version": "1.9.8",
"description": "AI-powered dev assistant — click-to-code navigation, source maps, WebGPU AI, and MCP integration for Claude Code.",
"permissions": [
"activeTab",