From 842691a989efb7cdfc9e80135772e26ae2e92f1c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 15:57:38 -0700 Subject: [PATCH 1/2] feat(extension): open typed sim/ links through the Sim link resolver --- apps/extension/DESIGN.md | 3 ++- apps/extension/README.md | 3 ++- apps/extension/scripts/build.ts | 27 +++++++++++++++++++++++++-- apps/extension/scripts/test-e2e.ts | 12 ++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/extension/DESIGN.md b/apps/extension/DESIGN.md index 7f81bb04311..86bfc5c55b2 100644 --- a/apps/extension/DESIGN.md +++ b/apps/extension/DESIGN.md @@ -55,7 +55,8 @@ other workspace resources or browser-control tools. - Required browser capabilities: side panel, scripting, active tab, context menus, and short-lived session storage for selection handoff. Required host access is - limited to the configured Sim origin. + limited to the configured Sim origin and to `http(s)://sim/*`, which exists only so + one static redirect rule can send a typed `sim/` to the Sim link resolver. - Other HTTP(S) origins are optional grants requested for the current site from a user gesture. Each grant can be revoked. Keeping the panel open is not a grant to read every subsequently selected tab. diff --git a/apps/extension/README.md b/apps/extension/README.md index 5b3763cda59..3d8f48c13ef 100644 --- a/apps/extension/README.md +++ b/apps/extension/README.md @@ -25,7 +25,8 @@ SIM_EXTENSION_DEV=1 SIM_EXTENSION_ORIGIN=http://localhost:3000 bun run --cwd app ## Data and permissions -- Required capabilities: `activeTab`, `scripting`, `sidePanel`, `storage`, `contextMenus`, and access to the configured Sim origin. +- Required capabilities: `activeTab`, `scripting`, `sidePanel`, `storage`, `contextMenus`, `declarativeNetRequestWithHostAccess`, and access to the configured Sim origin and to `http(s)://sim/*`. +- Typing `sim/` in the address bar opens the organization's [Sim link](https://docs.sim.ai/search/sim-links). A single static redirect rule rewrites top-level navigations to `http(s)://sim/*` to `/sim/*` with the path and query unchanged. The `sim` host permission exists only for that rule: every navigation to that host is redirected before it loads, and no other request is matched or observed. - Optional HTTP/HTTPS site access is requested only by a menu action naming that site. Chrome's `activeTab` access is temporary and does not follow arbitrary new sites. - Page capture runs on demand in Chrome's isolated world. It excludes editable fields, inputs, hidden elements, scripts, and embedded documents. It reads only the top frame. PDF viewers and canvas documents may have no accessible text. - Extraction stops at 50,000 text characters, 150,000 serialized UTF-8 bytes, 15,000 visited nodes, or 150ms. The composer exposes truncation. diff --git a/apps/extension/scripts/build.ts b/apps/extension/scripts/build.ts index ebabf41ea13..072321b0ca0 100644 --- a/apps/extension/scripts/build.ts +++ b/apps/extension/scripts/build.ts @@ -88,6 +88,19 @@ for (const [size, file] of [ ] as const) { await cp(resolve(root, '../sim/public/favicon', file), resolve(dist, `icon-${size}.png`)) } +/** + * Chrome navigates a typed `sim/` to `http://sim/`; this rewrites it to the Sim link + * resolver on the configured origin, keeping the rest of the path and query verbatim. + */ +const simLinkRules = [ + { + id: 1, + priority: 1, + action: { type: 'redirect', redirect: { regexSubstitution: `${origin.origin}/sim/\\1` } }, + condition: { regexFilter: '^https?://sim/(.*)$', resourceTypes: ['main_frame'] }, + }, +] +await writeFile(resolve(dist, 'sim-links.json'), JSON.stringify(simLinkRules, null, 2)) await writeFile( resolve(dist, 'manifest.json'), JSON.stringify( @@ -98,8 +111,18 @@ await writeFile( description: 'Ask Sim about the page you are reading, with your existing Sim conversations and tools.', minimum_chrome_version: '120', - permissions: ['activeTab', 'scripting', 'sidePanel', 'storage', 'contextMenus'], - host_permissions: [`${origin.origin}/*`], + permissions: [ + 'activeTab', + 'scripting', + 'sidePanel', + 'storage', + 'contextMenus', + 'declarativeNetRequestWithHostAccess', + ], + host_permissions: [`${origin.origin}/*`, 'http://sim/*', 'https://sim/*'], + declarative_net_request: { + rule_resources: [{ id: 'sim_links', enabled: true, path: 'sim-links.json' }], + }, optional_host_permissions: ['https://*/*', 'http://*/*'], background: { service_worker: 'background.js' }, side_panel: { default_path: 'sidepanel.html' }, diff --git a/apps/extension/scripts/test-e2e.ts b/apps/extension/scripts/test-e2e.ts index 4bb45960f88..d9ff9255368 100644 --- a/apps/extension/scripts/test-e2e.ts +++ b/apps/extension/scripts/test-e2e.ts @@ -220,6 +220,18 @@ try { await app.waitForFunction((id) => document.body.dataset.barrierId === id, barrierId) } + await check( + 'A typed sim/ link opens the Sim link resolver on the configured origin', + async () => { + const tab = await page.context().newPage() + for (const path of ['handbook', 'jira/123?view=board']) { + await tab.goto(`http://sim/${path}`) + assert(tab.url() === `${origin}/sim/${path}`, `sim/${path} reached ${tab.url()}`) + } + await tab.close() + } + ) + await check('Capture stays local until the trusted hosted frame is ready', async () => { await capture() await panel.getByRole('status').filter({ hasText: 'Waiting for Sim' }).waitFor() From 4c04eb716f456b3e8fc5398c3f00a16d2c288b4c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 16:33:24 -0700 Subject: [PATCH 2/2] docs(extension): note the sim intranet host collision --- apps/extension/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/README.md b/apps/extension/README.md index 3d8f48c13ef..7f7c4166efa 100644 --- a/apps/extension/README.md +++ b/apps/extension/README.md @@ -26,7 +26,7 @@ SIM_EXTENSION_DEV=1 SIM_EXTENSION_ORIGIN=http://localhost:3000 bun run --cwd app ## Data and permissions - Required capabilities: `activeTab`, `scripting`, `sidePanel`, `storage`, `contextMenus`, `declarativeNetRequestWithHostAccess`, and access to the configured Sim origin and to `http(s)://sim/*`. -- Typing `sim/` in the address bar opens the organization's [Sim link](https://docs.sim.ai/search/sim-links). A single static redirect rule rewrites top-level navigations to `http(s)://sim/*` to `/sim/*` with the path and query unchanged. The `sim` host permission exists only for that rule: every navigation to that host is redirected before it loads, and no other request is matched or observed. +- Typing `sim/` in the address bar opens the organization's [Sim link](https://docs.sim.ai/search/sim-links). A single static redirect rule rewrites top-level navigations to `http(s)://sim/*` to `/sim/*` with the path and query unchanged. The `sim` host permission exists only for that rule: every navigation to that host is redirected before it loads, and no other request is matched or observed. An organization that already serves an intranet host named `sim` should not install this build, since its links would be redirected to Sim. - Optional HTTP/HTTPS site access is requested only by a menu action naming that site. Chrome's `activeTab` access is temporary and does not follow arbitrary new sites. - Page capture runs on demand in Chrome's isolated world. It excludes editable fields, inputs, hidden elements, scripts, and embedded documents. It reads only the top frame. PDF viewers and canvas documents may have no accessible text. - Extraction stops at 50,000 text characters, 150,000 serialized UTF-8 bytes, 15,000 visited nodes, or 150ms. The composer exposes truncation.