{"record":{"id":"13500b8655c54dbe","repo":"ruvnet/ruflo","slug":"page-agent-bundle-still-contains-a-demo-sandbox-en","errorCode":null,"errorMessage":"page-agent bundle still contains a demo/sandbox endpoint (matched /${signature}/) after stripping — refusing to inject to avoid leaking page content to Alibaba's sandbox","messagePattern":"page-agent bundle still contains a demo/sandbox endpoint \\(matched /(.+?)/\\) after stripping — refusing to inject to avoid leaking page content to Alibaba's sandbox","errorType":"exception","errorClass":"DemoLeakError","httpStatus":null,"severity":"critical","filePath":"v3/@claude-flow/cli/src/mcp-tools/browser-intent-tools.ts","lineNumber":348,"sourceCode":" * the (demo-stripped) IIFE bundle, followed by our own controlled\n * construction + `execute()` call whose settled result lands on a window\n * global we can poll for.\n *\n * KEY-SAFETY: `pageConfig.apiKey` MUST be `PLACEHOLDER_API_KEY` (or another\n * non-secret placeholder) — callers are responsible for routing the real key\n * through `startLocalLLMProxy` first. This function only assembles strings;\n * it does not itself guarantee key safety, so callers MUST NOT pass a real\n * key here (see `browserIntentTools` handler for the enforced call site).\n */\nexport function buildPageAgentInjection(\n  iifeSource: string,\n  pageConfig: PageAgentPageConfig,\n  task: string,\n): string {\n  const safeIife = stripDemoAutoInit(iifeSource);\n  // Fail-closed: never inject a bundle that still carries the demo endpoint.\n  const leak = findDemoLeak(safeIife);\n  if (leak) throw new DemoLeakError(leak);\n  const cfgJson = JSON.stringify(pageConfig);\n  const taskJson = JSON.stringify(task);\n  return `${safeIife}\n;(function(){\n  window.${BROWSER_ACT_RESULT_GLOBAL} = null;\n  try {\n    if (!window.PageAgent) {\n      window.${BROWSER_ACT_RESULT_GLOBAL} = { success: false, error: 'PageAgent not defined after injection' };\n      return;\n    }\n    var agent = new window.PageAgent(${cfgJson});\n    window.__ruflo_pageAgent__ = agent;\n    agent.execute(${taskJson}).then(function(r){\n      window.${BROWSER_ACT_RESULT_GLOBAL} = { success: true, result: r };\n    }).catch(function(e){\n      window.${BROWSER_ACT_RESULT_GLOBAL} = { success: false, error: String((e && e.message) || e) };\n    });\n  } catch (e) {","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/mcp-tools/browser-intent-tools.ts#L330-L366","documentation":"DemoLeakError from buildPageAgentInjection (browser-intent-tools.ts:348), the fail-closed firewall for browser_act page-agent injection. The shipped page-agent IIFE (page-agent.demo.js) auto-POSTs page content to an Alibaba Function Compute sandbox; after the best-effort stripDemoAutoInit() text slice, findDemoLeak() scans for three version-independent signatures — /page-ag-testing/i, /\\.fcapp\\.run/i, /\\bDEMO_MODEL\\b/. If ANY survive, injection is refused outright so page content can never silently leak to the demo sandbox (the old strip-only behavior fell open when the marker moved; this cannot).","triggerScenarios":"Upgrading page-agent so the demo auto-init tail moved or was renamed — stripDemoAutoInit's DEMO_TAIL_MARKER no longer matches, the tail (with its fcapp.run endpoint) survives the strip, and the firewall trips. Also any custom/self-built bundle that legitimately contains the literal tokens 'page-ag-testing', '.fcapp.run', or 'DEMO_MODEL' anywhere in the IIFE source.","commonSituations":"A new page-agent release changes its minified tail format; injecting the unmodified page-agent.demo.js; a locally rebuilt bundle that keeps DEMO_MODEL as a build flag name; bundler config that reorders or duplicates the demo tail so the single indexOf-based slice misses part of it.","solutions":["Use a production page-agent build compiled without the demo auto-init tail instead of page-agent.demo.js","If building the bundle yourself, ensure the strings page-ag-testing / .fcapp.run / DEMO_MODEL are absent from the final IIFE — they trip the firewall regardless of intent","After a page-agent upgrade, update stripDemoAutoInit's DEMO_TAIL_MARKER to match the new tail format — until the strip actually removes the tail, the firewall keeps failing closed by design","Do not catch-and-inject the raw source anyway; the refusal is the security guarantee"],"exampleFix":"// before: injecting whatever bundle is on disk\nconst iife = readFileSync('page-agent.demo.js', 'utf8');\nconst injectable = buildPageAgentInjection(iife, cfg, task); // may throw DemoLeakError\n\n// after: guard with the same firewall first, use a clean build\nimport { findDemoLeak, stripDemoAutoInit } from './browser-intent-tools';\nconst iife = readFileSync('page-agent.production.js', 'utf8');\nconst leak = findDemoLeak(stripDemoAutoInit(iife));\nif (leak) throw new Error(`refusing to inject: bundle matches /${leak}/`);\nconst injectable = buildPageAgentInjection(iife, cfg, task);","handlingStrategy":"try-catch","validationCode":"import { findDemoLeak, stripDemoAutoInit } from './browser-intent-tools';\n// run the same fail-closed firewall BEFORE assembling the injection\nconst leak = findDemoLeak(stripDemoAutoInit(iifeSource));\nif (leak) {\n  throw new Error(`bundle fails demo-leak firewall (/${leak}/): use a build without the demo tail`);\n}","typeGuard":"const isDemoLeakError = (e: unknown): e is import('./browser-intent-tools').DemoLeakError =>\n  e instanceof Error && e.name === 'DemoLeakError';","tryCatchPattern":"import { DemoLeakError } from './browser-intent-tools';\ntry {\n  const injectable = buildPageAgentInjection(iife, cfg, task);\n} catch (e) {\n  if (e instanceof DemoLeakError) {\n    // fail closed: report and stop — never fall back to injecting the raw bundle\n    throw new Error(`page-agent bundle failed the demo-leak firewall: /${e.signature}/; rebuild without the demo auto-init tail`);\n  }\n  throw e;\n}","preventionTips":["Ship a production page-agent build without the demo auto-init tail; never inject page-agent.demo.js as-is","After every page-agent upgrade, verify stripDemoAutoInit's marker still matches the new tail — the firewall will keep refusing until it does","Run findDemoLeak() over the bundle in CI so a leak is caught before runtime, not at injection time"],"tags":["security","fail-closed","browser","injection","bundle-validation"],"backgroundTag":"fail-closed-security-check","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}