{"record":{"id":"21eecc1eeb48c9ce","repo":"garrytan/gstack","slug":"js-execution-blocked-current-page-hostname-d","errorCode":null,"errorMessage":"JS execution blocked: current page (${hostname}) does not match any cookie-imported domain. Imported cookies for: ${[...importedDomains].join(', ')}. This prevents cross-origin cookie exfiltration. Navigate to an imported domain or run without imported cookies.","messagePattern":"JS execution blocked: current page \\((.+?)\\) does not match any cookie-imported domain\\. Imported cookies for: (.+?)\\. This prevents cross-origin cookie exfiltration\\. Navigate to an imported domain or run without imported cookies\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/read-commands.ts","lineNumber":203,"sourceCode":"function assertJsOriginAllowed(bm: BrowserManager, pageUrl: string): void {\n  if (!bm.hasCookieImports()) return;\n\n  let hostname: string;\n  try {\n    hostname = new URL(pageUrl).hostname;\n  } catch {\n    return; // about:blank, data: URIs — allow (no cookies at risk)\n  }\n\n  const importedDomains = bm.getCookieImportedDomains();\n  const allowed = [...importedDomains].some(domain => {\n    // Exact match or subdomain match (e.g., \".github.com\" matches \"api.github.com\")\n    const normalized = domain.startsWith('.') ? domain : '.' + domain;\n    return hostname === domain.replace(/^\\./, '') || hostname.endsWith(normalized);\n  });\n\n  if (!allowed) {\n    throw new Error(\n      `JS execution blocked: current page (${hostname}) does not match any cookie-imported domain. ` +\n      `Imported cookies for: ${[...importedDomains].join(', ')}. ` +\n      `This prevents cross-origin cookie exfiltration. Navigate to an imported domain or run without imported cookies.`\n    );\n  }\n}\n\nexport async function handleReadCommand(\n  command: string,\n  args: string[],\n  session: TabSession,\n  bm?: BrowserManager,\n): Promise<string> {\n  const page = session.getPage();\n  // Frame-aware target for content extraction\n  const target = session.getActiveFrameOrPage();\n\n  switch (command) {","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/read-commands.ts#L185-L221","documentation":"Thrown by assertJsOriginAllowed when cookies have been imported for specific domains and the current page's hostname does not match any imported domain. This blocks js/eval execution on untrusted pages to prevent cross-origin cookie exfiltration via document.cookie. The check is bypassed only when no cookies have been imported, or when the page is about:blank / a data: URI.","triggerScenarios":"After importing cookies for github.com, running `browse js ...` while the page is on a different hostname (e.g., evil.com, an OAuth redirect page, a login portal). bm.hasCookieImports() is true, the page hostname does not match any imported domain exactly or as a subdomain.","commonSituations":"Agent navigated through an OAuth/SSO redirect that crosses origins; testing flow legitimately spans multiple domains but cookies were imported for only one; forgot to import cookies for a subdomain (e.g., imported github.com but page is on api.github.com without the .github.com wildcard); landed on a phishing/interstitial page.","solutions":["Navigate to a page under an imported domain before running js","Import cookies for the additional domain (use .domain.com form to cover subdomains)","Run without imported cookies if cross-origin exfiltration is not a concern for this flow","Check the current URL: `browse js location.href` is itself blocked, so inspect via the text/links commands first"],"exampleFix":"// before: cookies imported for github.com, page is on evil.com\nbrowse js 'document.cookie'   // throws\n\n// after: navigate to an imported domain first\nbrowse navigate https://github.com\nbrowse js 'document.cookie'","handlingStrategy":"try-catch","validationCode":"function isOriginAllowed(pageUrl: string, importedDomains: Set<string>): boolean {\n  let hostname: string;\n  try { hostname = new URL(pageUrl).hostname; }\n  catch { return true; } // about:blank, data: URIs are allowed\n  if (importedDomains.size === 0) return true; // no imports → no restriction\n  return [...importedDomains].some(domain => {\n    const normalized = domain.startsWith('.') ? domain : '.' + domain;\n    return hostname === domain.replace(/^\\.//, '') || hostname.endsWith(normalized);\n  });\n}\n\n// before running js\nif (bm.hasCookieImports() && !isOriginAllowed(page.url(), bm.getCookieImportedDomains())) {\n  throw new Error(`Refusing js on ${page.url()}: not an imported domain. Navigate first.`);\n}","typeGuard":"function isAllowedHostname(hostname: string, importedDomains: Set<string>): boolean {\n  return [...importedDomains].some(domain => {\n    const normalized = domain.startsWith('.') ? domain : '.' + domain;\n    return hostname === domain.replace(/^\\.//, '') || hostname.endsWith(normalized);\n  });\n}","tryCatchPattern":"try {\n  await runJs(expr);\n} catch (e: any) {\n  if (/JS execution blocked/.test(e.message)) {\n    // navigate to an imported domain, then retry\n    const target = [...bm.getCookieImportedDomains()][0].replace(/^\\./, '');\n    await browse.navigate(`https://${target}`);\n    await runJs(expr);\n  } else throw e;\n}","preventionTips":["Import cookies with the .domain.com form to cover all subdomains","Before running js, verify the current page hostname with the text/links commands (js itself is blocked)","Clear imported cookies if you need to run js on an unrelated page","Watch for OAuth/SSO redirects that cross origins mid-flow — re-navigate before js"],"tags":["security","cookies","cross-origin","exfiltration","js-execution","origin-guard"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}