Skip to content

Commit 83b0c87

Browse files
committed
improvement(extension): remove opt-in search recovery
Shortcuts now work without setup, so recovery was extra config that needed search-site access and could catch real searches like go/ast. An update clears the rules and setting from earlier versions, and the recover route stays until those versions have updated.
1 parent c8f811f commit 83b0c87

16 files changed

Lines changed: 35 additions & 600 deletions

File tree

‎apps/docs/content/docs/browser-extension/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Change the binding in Chrome’s **Extensions → Keyboard shortcuts**. If Chrom
3535

3636
Type a shortcut such as `go/handbook` or `sim/handbook` in Chrome’s address bar. Both work once you're signed in to Sim and Links is on for your organization.
3737

38-
For suggestions, type `sim`, press **Space**, and enter part of a link name. If Chrome searches instead of opening a shortcut, open the Sim extension and sign in. If it still searches, enable your search engine under the extension’s **+ → Browser shortcuts**. Other searches stay unchanged.
38+
For suggestions, type `sim`, press **Space**, and enter part of a link name. If Chrome searches instead of opening a shortcut, open the Sim extension and sign in.
3939

4040
See [Links](/search/sim-links#open-links-from-the-address-bar) for supported inputs, Glean conflicts, and team rollout. Administrators can [allow the extension for a Chrome organizational unit](https://support.google.com/chrome/a/answer/6306504).
4141

‎apps/docs/content/docs/search/sim-links.mdx‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ For suggestions, type `sim`, press **Space**, and enter part of a name. Matching
2323

2424
This can happen if you installed the extension while signed out, or just cleared your browsing history. Opening the Sim extension and signing in fixes the first case, and the second fixes itself within a week. Until then, a trailing slash (`go/handbook/`) or `http://go/handbook` tells Chrome to navigate directly.
2525

26-
To catch these searches automatically, open the extension’s **+ → Browser shortcuts** and turn on your search engine. Approve Chrome’s site-access prompt if shown. Google (`google.com`), Bing, DuckDuckGo, and Yahoo are supported. Other searches and your default search engine stay unchanged.
27-
28-
This handles whole shortcuts such as `go/handbook` or `go/jira/123`. For spaces, query parameters, unsupported search sites, or blocked site access, use `sim` + **Space** or the full `https://www.sim.ai/sim/<name>` address.
29-
30-
Turning shortcuts off keeps site access. To remove it, open Chrome’s **Extensions → Manage extensions → Sim → Details**.
31-
3226
### Roll out to your team
3327

3428
Administrators can [force-install Sim for Chrome](https://support.google.com/chrome/a/answer/6306504) for an organizational unit. Shortcuts start working once each person signs in through the extension, or the next time Chrome starts after they've signed in.

‎apps/extension/DESIGN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ other workspace resources or browser-control tools.
6868
capture across sites; switching tabs alone does not extract content.
6969
- No history permission, bookmarks, cookie API, debugger, native messaging, external messaging,
7070
or arbitrary remote code execution. The only network interception is the static
71-
`sim/` and `go/` redirect rule above, plus opt-in search recovery.
71+
`sim/` and `go/` redirect rule above.
7272
- Background network requests are limited to two. Once a week at browser start, and after
7373
signing in to the panel, the worker asks the Sim origin whether links are on for the session;
7474
if so, it opens `http://go/` and `http://sim/` in background tabs that close themselves, which

‎apps/extension/README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,18 @@ SIM_EXTENSION_DEV=1 SIM_EXTENSION_ORIGIN=http://localhost:3000 bun run --cwd app
3232
- Required capabilities: `activeTab`, `scripting`, `sidePanel`, `storage`, `contextMenus`, `declarativeNetRequestWithHostAccess`, and access to the configured Sim origin and to `http(s)://sim/*` and `http(s)://go/*`.
3333
- Type `sim`, press Space, enter the name, and press Enter to open an organization's [link](https://docs.sim.ai/search/sim-links). `go/<name>` opens the same link. Chrome treats bare `go/<name>` or `sim/<name>` as a search until its history holds that short host, so on install, and at browser start once a week, the extension opens `http://go/` and `http://sim/` in background tabs that close themselves. It does this only once Sim says links are on for the signed-in session, so `go/x` stays an ordinary search for everyone else; signing in through the side panel checks again. Chrome counts a visit to an unfamiliar single-label host as typed, which is what the address bar checks, and repeating covers history that expires after 90 days or is cleared. A trailing slash (`go/<name>/`) always selects navigation. A single static redirect rule rewrites top-level navigations to `http(s)://sim/*` and `http(s)://go/*` to `<Sim origin>/sim/*` with the path and query unchanged. The `sim` and `go` host permissions exist only for that rule: every navigation to those hosts is redirected before it loads, and no other request is matched or observed. An organization that already serves an intranet host named `sim` or `go` should not install this build, since its links would be redirected to Sim. When another extension (such as another go-links tool) also redirects `go/`, Chrome applies the most recently installed extension's rule.
3434
- The `sim` address-bar keyword (`sim`, then Space) lists matching Sim links while you type and opens what you enter through the same resolver. For these suggestions, the background worker sends the text typed before the first `/` or `?`, and nothing else, unless that text contains a space (a search) or is longer than a link name can be, to `<Sim origin>/api/sim-links/suggestions` with the signed-in session, and the extension's content security policy allows connections to that origin alone. Unlisted links are never suggested.
35-
- **+ → Browser shortcuts** separately opts into recovery for exact Google, Bing, DuckDuckGo, or Yahoo HTTPS search hosts. After a direct Chrome permission grant, dynamic request rules redirect only top-level GET requests with one query parameter containing a whole `go/<name>` or `sim/<name>` shortcut. Only that value reaches `/sim-links/recover`, which validates it and redirects to the existing authenticated resolver. Ordinary search terms, additional search parameters, subframes, and unsupported inputs are not forwarded. Chrome applies these rules without an extension search-URL observer. Turning a provider off removes its rules while preserving site access; revoking access clears its recovery choice. This does not enable page reference or change the default search provider.
3635
- Optional HTTP/HTTPS access is requested together by **Allow across websites**, from a direct user action. Chrome's `activeTab` access is temporary and does not follow arbitrary new sites.
3736
- 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.
3837
- Extraction stops at 50,000 text characters, 150,000 serialized UTF-8 bytes, 15,000 visited nodes, or 150ms. The composer exposes truncation.
3938
- Attached pages share a 150,000-character and 450,000-byte snapshot budget per message, within the general 100-context ceiling. Shorter pages can share the available budget without a separate page-count restriction.
4039
- Source URLs omit credentials, query parameters, and fragments. Human-readable text can still contain sensitive information; it is the user-selected material they review and send into their conversation.
4140
- Snapshots stay in memory until handed to Sim and acknowledged or expired after 30 seconds. For captures, extension storage contains only a short-lived intent with a tab/document identity and a SHA-256 URL digest, never raw URLs, captured text, or credentials. Closing a tab or navigating invalidates its pending intent. Sim applies its normal conversation retention once a message is sent.
4241
- No browser history permission, bookmarks, cookie access, debugger access, remote extension JavaScript, arbitrary network proxy, or background content collection is requested. Incognito use is disabled.
43-
- The iframe bridge checks both the exact configured origin and the exact frame, then validates versioned messages. The hosted composer may request only page capture, selection capture, the native page-access and browser-shortcut dialogs, or the browser's fixed microphone settings page. Requests require the current conversation nonce and Chrome transient user activation; arbitrary URLs, tabs, script execution, and network operations are never accepted. Permission grants still require a click in the extension-owned page-access control and browser approval. Shortcut settings are advertised separately so older installed shells keep working without the new action.
42+
- The iframe bridge checks both the exact configured origin and the exact frame, then validates versioned messages. The hosted composer may request only page capture, selection capture, the native page-access dialog, or the browser's fixed microphone settings page. Requests require the current conversation nonce and Chrome transient user activation; arbitrary URLs, tabs, script execution, and network operations are never accepted. Permission grants still require a click in the extension-owned page-access control and browser approval.
4443
- Captures and queued toolbar intents are scoped to the receiving conversation. Changing conversation, workspace, or account discards an unfinished handoff. Captured attachments remain explicit snapshots when the source tab changes.
4544
- The complete hosted document, including portaled source dialogs and menus, is blocked from session replay. Clipboard writing and microphone capture are delegated only to the configured Sim origin; clipboard reading and camera access are not delegated. Voice input starts only after the user clicks the microphone and the browser grants permission. The shared Sim voice service streams microphone audio to ElevenLabs for transcription into the editable draft. Stop ends capture while allowing the final transcript to arrive; Send and conversation changes cancel unfinished transcription. There is no tab-audio capture or background recording.
4645

47-
With **Reference current page** enabled, a composer Send requests a single fresh snapshot for that conversation. Local storage holds only this preference, search recovery choices, and when the short hosts were last visited. A tab change does not read any content. Explicit snapshots take precedence; access denial, navigation and timeouts keep the draft unsent. **Allow across websites** works even when Chrome hides the active address; no address entry is required. The grant covers HTTP and HTTPS sites across tabs until the user revokes or restricts it in browser settings. Uploaded screenshot files stay in the hosted app’s normal attachment flow.
46+
With **Reference current page** enabled, a composer Send requests a single fresh snapshot for that conversation. Local storage holds only this preference and when the short hosts were last visited. A tab change does not read any content. Explicit snapshots take precedence; access denial, navigation and timeouts keep the draft unsent. **Allow across websites** works even when Chrome hides the active address; no address entry is required. The grant covers HTTP and HTTPS sites across tabs until the user revokes or restricts it in browser settings. Uploaded screenshot files stay in the hosted app’s normal attachment flow.
4847

4948
## Verification
5049

‎apps/extension/scripts/test-e2e.ts‎

Lines changed: 2 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,6 @@ const fixture = Bun.serve({
9696
)
9797
},
9898
})
99-
const searchProviders = [
100-
{ id: 'google', hosts: ['www.google.com', 'google.com'], path: '/search', parameter: 'q' },
101-
{ id: 'bing', hosts: ['www.bing.com', 'bing.com'], path: '/search', parameter: 'q' },
102-
{ id: 'duckduckgo', hosts: ['duckduckgo.com', 'www.duckduckgo.com'], path: '/', parameter: 'q' },
103-
{ id: 'yahoo', hosts: ['search.yahoo.com'], path: '/search', parameter: 'p' },
104-
] as const
105-
const searchRequests: { url: string; method: string }[] = []
106-
let searchFixture: ReturnType<typeof Bun.serve> | undefined
10799
const temporary = await mkdtemp(resolve(tmpdir(), 'sim-extension-e2e-'))
108100
const extensionPath = resolve(temporary, 'extension')
109101
let context: BrowserContext | undefined
@@ -147,50 +139,6 @@ try {
147139
)
148140
assert(!existsSync(rejectedOutput), 'Invalid key material reached build output')
149141
})
150-
const searchKey = resolve(temporary, 'search-key.pem')
151-
const searchCertificate = resolve(temporary, 'search-cert.pem')
152-
const certificate = spawnSync(
153-
'openssl',
154-
[
155-
'req',
156-
'-x509',
157-
'-newkey',
158-
'rsa:2048',
159-
'-nodes',
160-
'-days',
161-
'1',
162-
'-keyout',
163-
searchKey,
164-
'-out',
165-
searchCertificate,
166-
'-subj',
167-
'/CN=extension-search.test',
168-
],
169-
{ encoding: 'utf8', timeout: 10_000 }
170-
)
171-
assert(certificate.status === 0, 'Could not create the isolated search fixture certificate')
172-
searchFixture = Bun.serve({
173-
hostname: '127.0.0.1',
174-
port: 0,
175-
tls: { key: await readFile(searchKey), cert: await readFile(searchCertificate) },
176-
fetch(request) {
177-
const path = new URL(request.url).pathname
178-
if (path === '/' || path === '/search')
179-
searchRequests.push({ url: request.url, method: request.method })
180-
return new Response(
181-
'<!doctype html><title>Local search fixture</title><h1>Search fixture</h1>',
182-
{
183-
headers: { 'content-type': 'text/html' },
184-
}
185-
)
186-
},
187-
})
188-
const searchHosts = searchProviders.flatMap((provider) => [...provider.hosts])
189-
const searchPort = searchFixture.port
190-
const fixtureSearchHosts = [...searchHosts, 'm.google.com', 'www.google.com.evil.test']
191-
const resolverRules = fixtureSearchHosts
192-
.map((host) => `MAP ${host} 127.0.0.1:${searchPort}`)
193-
.join(', ')
194142
const origin = `http://127.0.0.1:${fixture.port}`
195143
const pageOrigin = `http://localhost:${fixture.port}`
196144
const built = spawnSync(process.execPath, ['scripts/build.ts', `--outdir=${extensionPath}`], {
@@ -200,18 +148,14 @@ try {
200148
})
201149
assert(built.status === 0, `Extension build failed: ${built.stderr}`)
202150
const manifest = JSON.parse(await readFile(resolve(extensionPath, 'manifest.json'), 'utf8'))
203-
// Pregrants isolate routing from consent; approved search hosts resolve to the local fixture.
204-
manifest.host_permissions.push(
205-
`${pageOrigin}/*`,
206-
...fixtureSearchHosts.map((host) => `https://${host}/*`)
207-
)
151+
// Pregrants isolate routing from consent.
152+
manifest.host_permissions.push(`${pageOrigin}/*`)
208153
await writeFile(resolve(extensionPath, 'manifest.json'), JSON.stringify(manifest))
209154
context = await chromium.launchPersistentContext(resolve(temporary, 'profile'), {
210155
channel: 'chromium',
211156
headless: true,
212157
ignoreHTTPSErrors: true,
213158
args: [
214-
`--host-resolver-rules=${resolverRules}`,
215159
'--no-proxy-server',
216160
'--enable-unsafe-extension-debugging',
217161
`--disable-extensions-except=${extensionPath}`,
@@ -1430,121 +1374,6 @@ try {
14301374
await expect(app.locator('body')).toHaveAttribute('data-reference-request', denied.requestId)
14311375
}
14321376
)
1433-
await check(
1434-
'Opted-in shortcut recovery redirects only explicit searches on the chosen providers',
1435-
async () => {
1436-
const tab = await page.context().newPage()
1437-
const toggle = async (provider: string, enabled: boolean) => {
1438-
const result = await panel.evaluate(
1439-
({ provider, enabled }) =>
1440-
chrome.runtime.sendMessage({ type: 'set-search-recovery', provider, enabled }),
1441-
{ provider, enabled }
1442-
)
1443-
assert(
1444-
result?.ok === true,
1445-
`Could not ${enabled ? 'enable' : 'disable'} ${provider} recovery`
1446-
)
1447-
}
1448-
const unchanged = async (url: string) => {
1449-
await tab.goto(url)
1450-
assert(tab.url() === url, `An ordinary or ambiguous search was redirected: ${url}`)
1451-
assert(
1452-
(await tab.title()) === 'Local search fixture',
1453-
'A provider request escaped the local fixture'
1454-
)
1455-
}
1456-
try {
1457-
for (const provider of searchProviders) {
1458-
const endpoint = `https://${provider.hosts[0]}${provider.path}`
1459-
const url = `${endpoint}?${provider.parameter}=sim%2Faskit`
1460-
await unchanged(url)
1461-
await toggle(provider.id, true)
1462-
for (const host of provider.hosts) {
1463-
for (const shortcut of SIM_LINK_HOSTS.flatMap((short) => [
1464-
`${short}%2Faskit`,
1465-
`${short}/jira/ABC-123`,
1466-
])) {
1467-
const candidate = `https://${host}${provider.path}?client=chrome&${provider.parameter}=${shortcut}&source=qa`
1468-
const prior = searchRequests.filter((request) => request.url === candidate).length
1469-
await tab.goto(candidate)
1470-
assert(
1471-
tab.url() === `${origin}/sim-links/recover?q=${shortcut}`,
1472-
`${provider.id} lost the explicit shortcut`
1473-
)
1474-
assert(
1475-
searchRequests.filter((request) => request.url === candidate).length === prior,
1476-
'The matched shortcut reached the search provider'
1477-
)
1478-
}
1479-
}
1480-
for (const query of [
1481-
`${provider.parameter}=quarterly+planning`,
1482-
`${provider.parameter}=sim%2Faskit%20help`,
1483-
`${provider.parameter}=sim%2Faskit+help`,
1484-
`${provider.parameter}=sim%252Faskit`,
1485-
`${provider.parameter}=sim%2Faskit%ZZ`,
1486-
`${provider.parameter}=sim%2Faskit&${provider.parameter}=other`,
1487-
`${provider.parameter}=other&${provider.parameter}=sim%2Faskit`,
1488-
`${provider.parameter}=sim%2Faskit&%${provider.parameter.charCodeAt(0).toString(16)}=other`,
1489-
])
1490-
await unchanged(`${endpoint}?${query}`)
1491-
await toggle(provider.id, false)
1492-
await unchanged(url)
1493-
assert(
1494-
await worker.evaluate(
1495-
(origins) => chrome.permissions.contains({ origins }),
1496-
provider.hosts.map((host) => `https://${host}/*`)
1497-
),
1498-
'Disabling recovery removed a capture permission'
1499-
)
1500-
}
1501-
await toggle('google', true)
1502-
for (const url of [
1503-
'https://www.google.com/other?q=sim%2Faskit',
1504-
'https://m.google.com/search?q=sim%2Faskit',
1505-
'https://www.google.com.evil.test/search?q=sim%2Faskit',
1506-
'https://www.google.com/search?%71=sim%2Faskit',
1507-
])
1508-
await unchanged(url)
1509-
const search = 'https://www.google.com/search?q=sim%2Faskit'
1510-
await tab.goto(`${origin}/post-fixture`)
1511-
await Promise.all([
1512-
tab.waitForNavigation(),
1513-
tab.evaluate((url) => {
1514-
const form = document.createElement('form')
1515-
form.method = 'POST'
1516-
form.action = url
1517-
document.body.append(form)
1518-
form.submit()
1519-
}, search),
1520-
])
1521-
assert(
1522-
tab.url() === search &&
1523-
searchRequests.some((request) => request.url === search && request.method === 'POST'),
1524-
'A POST search was recovered'
1525-
)
1526-
await tab.goto(`${origin}/frame-fixture`)
1527-
await Promise.all([
1528-
tab.waitForEvent(
1529-
'framenavigated',
1530-
(child) => child.parentFrame() !== null && child.url() === search
1531-
),
1532-
tab.evaluate((url) => {
1533-
const child = document.createElement('iframe')
1534-
child.src = url
1535-
document.body.append(child)
1536-
}, search),
1537-
])
1538-
assert(
1539-
tab.url() === `${origin}/frame-fixture`,
1540-
'A subframe search changed the top-level page'
1541-
)
1542-
await toggle('google', false)
1543-
} finally {
1544-
await tab.close()
1545-
}
1546-
}
1547-
)
15481377
await mkdir(dirname(output), { recursive: true })
15491378
await panel.screenshot({ path: output.replace(/\.json$/, '.png'), fullPage: true })
15501379
} catch (error) {
@@ -1559,7 +1388,6 @@ try {
15591388
} finally {
15601389
await context?.close()
15611390
fixture.stop(true)
1562-
searchFixture?.stop(true)
15631391
await mkdir(dirname(output), { recursive: true })
15641392
await writeFile(output, JSON.stringify({ suite: 'browser-extension', checks }, null, 2))
15651393
await rm(temporary, { recursive: true, force: true })

0 commit comments

Comments
 (0)