Skip to content

Commit c3062e1

Browse files
committed
feat(extension): bring Sim chat and page context to Chrome
1 parent 322abb2 commit c3062e1

69 files changed

Lines changed: 3711 additions & 80 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: browser-extension
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/browser-extension.yml'
7+
- 'apps/extension/**'
8+
- 'packages/browser-extension-protocol/**'
9+
- 'apps/sim/app/extension/**'
10+
- 'apps/sim/app/workspace/**/home/**'
11+
- 'apps/sim/stores/mothership-drafts/**'
12+
- 'apps/sim/scripts/test-browser-extension-e2e.ts'
13+
- 'apps/sim/lib/core/security/**'
14+
- 'apps/sim/lib/mothership/chat/**'
15+
- 'apps/sim/lib/api/contracts/mothership-chat-context.ts'
16+
- 'apps/sim/proxy.ts'
17+
- 'apps/sim/next.config.ts'
18+
- 'packages/emcn/**'
19+
- 'bun.lock'
20+
workflow_dispatch:
21+
22+
permissions:
23+
contents: read
24+
25+
concurrency:
26+
group: browser-extension-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
jobs:
30+
browser:
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 20
33+
services:
34+
postgres:
35+
image: pgvector/pgvector:pg17
36+
env:
37+
POSTGRES_USER: postgres
38+
POSTGRES_PASSWORD: postgres
39+
POSTGRES_DB: extension_test
40+
ports:
41+
- 5432:5432
42+
options: >-
43+
--health-cmd "pg_isready -U postgres -d extension_test"
44+
--health-interval 5s
45+
--health-timeout 5s
46+
--health-retries 10
47+
redis:
48+
image: redis:8.2-alpine
49+
ports:
50+
- 6379:6379
51+
options: >-
52+
--health-cmd "redis-cli ping"
53+
--health-interval 5s
54+
--health-timeout 5s
55+
--health-retries 10
56+
env:
57+
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/extension_test
58+
REDIS_URL: redis://127.0.0.1:6379
59+
NEXT_PUBLIC_APP_URL: http://127.0.0.1:3007
60+
BETTER_AUTH_URL: http://127.0.0.1:3007
61+
BETTER_AUTH_SECRET: browser-extension-ci-auth-secret-32-characters
62+
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000'
63+
API_ENCRYPTION_KEY: '1111111111111111111111111111111111111111111111111111111111111111'
64+
NEXT_TELEMETRY_DISABLED: '1'
65+
DISABLE_AUTH: 'false'
66+
steps:
67+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
68+
with:
69+
persist-credentials: false
70+
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
71+
with:
72+
node-version: '24'
73+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
74+
with:
75+
bun-version: 1.4.4
76+
- run: bun install --frozen-lockfile
77+
- run: bunx playwright install --with-deps chromium
78+
working-directory: apps/extension
79+
- run: bun run type-check && bun run build && bun run test:e2e
80+
working-directory: apps/extension
81+
env:
82+
EXTENSION_REPORT_PATH: test-results/extension-report.json
83+
- name: Provision the disposable app database
84+
run: bun run --cwd packages/db db:migrate
85+
- name: Generate the fixture extension identity
86+
run: |
87+
bun - <<'JS' >> "$GITHUB_ENV"
88+
import { generateKeyPairSync, createHash } from "node:crypto";
89+
const { publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
90+
const der = publicKey.export({ type: "spki", format: "der" });
91+
const id = [...createHash("sha256").update(der).digest("hex").slice(0, 32)]
92+
.map(c => String.fromCharCode(97 + parseInt(c, 16))).join("");
93+
process.stdout.write(`SIM_EXTENSION_KEY=${der.toString("base64")}\nBROWSER_EXTENSION_IDS=${id}\n`);
94+
JS
95+
- name: Start the real Sim app
96+
working-directory: apps/sim
97+
run: |
98+
node "$(bun -p 'require.resolve("next/dist/bin/next")')" dev --hostname 127.0.0.1 --port 3007 > "$RUNNER_TEMP/extension-app.log" 2>&1 &
99+
for ((attempt=0; attempt<60; attempt++)); do
100+
if curl --fail --silent http://127.0.0.1:3007/api/auth/get-session > /dev/null; then exit 0; fi
101+
sleep 2
102+
done
103+
exit 1
104+
- name: Exercise authenticated app integration
105+
working-directory: apps/sim
106+
run: bun run test:browser-extension:e2e
107+
env:
108+
EXTENSION_APP_E2E_BASE_URL: http://127.0.0.1:3007
109+
EXTENSION_APP_E2E_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/extension_test
110+
EXTENSION_APP_E2E_REPORT_PATH: ../extension/test-results/real-app-report.json
111+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
112+
if: always()
113+
with:
114+
name: browser-extension-results
115+
path: apps/extension/test-results
116+
retention-days: 7
117+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
118+
if: failure()
119+
with:
120+
name: browser-extension-app-log
121+
path: ${{ runner.temp }}/extension-app.log
122+
retention-days: 7

‎apps/extension/DESIGN.md‎

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Sim browser extension
2+
3+
Implementation scope and research, 2026-10-29.
4+
5+
## Product contract
6+
7+
The extension is another surface of Sim. A native Chrome side panel hosts the actual
8+
Sim composer and transcript, using existing authenticated Mothership requests,
9+
conversation persistence, streaming recovery, model controls, tool permissions,
10+
and workspace authorization. A conversation can continue in the full app.
11+
12+
The user signs in in a normal Sim tab, chooses a workspace, opens or creates a
13+
conversation, and explicitly attaches the current page or highlighted text.
14+
Pending snapshots sit beside the composer as compact removable attachments. Opening one
15+
shows the captured text, source, capture time, and conversation-retention disclosure.
16+
Partial captures remain labeled in the attachment row. Site access lives in the
17+
extension options menu; successful delivery adds no persistent status banner.
18+
They are immutable: navigating to another page cannot silently change an attached
19+
source. Removing an attachment, changing workspace, or starting a new conversation
20+
clears pending page context. Several explicitly attached pages can support comparison.
21+
Assistant accepts page snapshots while retaining its existing restriction on
22+
workspace-resource contexts.
23+
24+
The extension does not promise complete capture of virtualized documents, canvas
25+
editors, restricted browser pages, inaccessible cross-origin frames, or PDFs.
26+
Extraction failure is visible; the model must never infer absent page content.
27+
Browser interaction, autonomous navigation, screenshot OCR, and native-app control
28+
are separate capabilities requiring their own permission and execution contracts.
29+
Existing Sim server tools remain subject to their existing authorization and approval.
30+
31+
## Ownership and data flow
32+
33+
1. `apps/extension`: packaged MV3 service worker, side-panel host, bounded content
34+
extraction, per-site permission controls, build artifacts, browser verification.
35+
2. `packages/browser-extension-protocol`: versioned, strictly validated messages and
36+
immutable page snapshots shared by the extension, app, and server.
37+
3. `apps/sim/app/extension`: session gate, workspace picker, minimal app providers,
38+
real chat components, history and a narrow parent-window bridge.
39+
4. Existing chat context pipeline: `browser_page` input validation, durable message
40+
projection, restored context chips, untrusted-content formatting, Assistant admission.
41+
5. Existing Sim API and Mothership: authentication, authorization, billing, tool
42+
execution and streaming; no alternate inference service or extension API key.
43+
44+
The content script returns text to its extension caller. The extension verifies the
45+
captured document still belongs to the intended tab, then sends a typed snapshot to
46+
the exact hosted frame. The frame validates its parent origin and identity, and puts
47+
the snapshot into pending UI context. The existing send path persists it with the
48+
message and passes quoted content to Mothership. A page snapshot grants no access to
49+
other workspace resources or browser-control tools.
50+
51+
## Permission and lifetime rules
52+
53+
- Required browser capabilities: side panel, scripting, active tab, context menus,
54+
and short-lived session storage for selection handoff. Required host access is
55+
limited to the configured Sim origin.
56+
- Other HTTP(S) origins are optional grants requested for the current site from a
57+
user gesture. Each grant can be revoked. Keeping the panel open is not a grant
58+
to read every subsequently selected tab.
59+
- No history, bookmarks, cookie API, debugger, network interception, native
60+
messaging, external messaging, or arbitrary remote code execution.
61+
- No always-running page reader. Extraction is tied to an explicit attachment action.
62+
- Page bytes are not written to extension local/sync storage. Temporary context-menu
63+
handoff uses trusted-context session storage, expiration and navigation cleanup.
64+
- Sent snapshots become chat data and follow Sim's chat retention and deletion
65+
policy. This must be disclosed in the interface and store listing. No claim of
66+
zero retention is made once a message is sent.
67+
- No page content or full page URL in extension telemetry or routine logs.
68+
69+
## Boundary controls
70+
71+
- Package all privileged JavaScript. The hosted iframe has no extension API access.
72+
- Only exact configured Sim origins and exact configured extension IDs are trusted.
73+
Both directions validate source window, origin, protocol version and message shape.
74+
There is no arbitrary fetch, JavaScript, navigation or extension-API command bridge.
75+
- `BROWSER_EXTENSION_IDS` is a bounded comma-separated list of Chrome IDs. Missing
76+
or malformed configuration disables embedding. Only `/extension` and its children
77+
receive the corresponding frame policy; ordinary app pages keep their frame denial.
78+
- Extension pages use a restrictive CSP; the hosted app keeps its app CSP with only
79+
the embedding ancestor list changed. Sign-in happens in a top-level tab.
80+
- Allow HTTPS production app origins; HTTP is development-only on loopback.
81+
- Capture uses bounded traversal and output size, skips hidden and editable/form
82+
content, and records truncation. It never serializes the entire DOM or reads
83+
cookies, browser storage, input values, or page-world JavaScript state.
84+
- Validate snapshots again server-side, including headless execution paths. Bound
85+
individual and aggregate text/byte sizes. Only HTTP(S) source URLs without embedded
86+
credentials are accepted; URL secrets should not be carried as source metadata.
87+
- Snapshot content is explicitly untrusted quoted JSON. This is a model cue, not
88+
an authorization mechanism: tool access remains independently enforced by Sim.
89+
90+
## Verification planned before implementation
91+
92+
The credible failure modes are cross-origin/frame message forgery; schema-version
93+
confusion; hostile URL metadata; hidden/form data leakage; unbounded DOM traversal;
94+
selection or extraction delivered after navigation; stale context on workspace
95+
switch; persistence mutating the captured text; Assistant silently dropping context;
96+
and an embed exception accidentally allowing unrelated app pages to be framed.
97+
98+
Use a real Chromium extension against controlled HTTP pages for extraction,
99+
navigation, permission and message-boundary checks. Emit a caller-configured JSON
100+
report and screenshot/trace artifacts. Security-boundary tests may cover header
101+
isolation and durable-context semantics where the running app needs unavailable
102+
services. Do not replace real Chrome APIs with mocks and describe that as E2E.
103+
104+
The authenticated live-app acceptance gate is: sign in, choose a permitted workspace,
105+
capture a real page, receive a grounded Mothership answer, reopen the conversation,
106+
stop/reconnect streaming, switch workspaces, and verify access denial. An absent
107+
backend or account is an unverified gate, not a pass.
108+
109+
## Research and decisions
110+
111+
- [Glean sidebar](https://docs.glean.com/user-guide/apps/extension-sidebar) and
112+
[page permissions](https://docs.glean.com/user-guide/apps/why-does-the-chrome-extension-ask-for-those-permissions):
113+
page context can use address and text without indexing the page. Adopt explicit
114+
page attachment alongside the existing Sim knowledge and tool experience.
115+
- [Chrome activeTab](https://developer.chrome.com/docs/extensions/develop/concepts/activeTab)
116+
and [optional permissions](https://developer.chrome.com/docs/extensions/reference/api/permissions):
117+
temporary access follows user invocation; cross-origin navigation requires fresh
118+
authority. Request persistent access per site only when selected by the user.
119+
- [Chrome extension security](https://developer.chrome.com/docs/extensions/develop/security-privacy/stay-secure)
120+
and [OWASP extension guidance](https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html):
121+
minimize privileges and treat all incoming page messages as attacker-controlled.
122+
Do not expose a generic privileged bridge.
123+
- [Service worker lifecycle](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle):
124+
workers are disposable. Stream from the app document; bound temporary handoffs
125+
independently of worker globals.
126+
- [Storage and cookies](https://developer.chrome.com/docs/extensions/develop/concepts/storage-and-cookies):
127+
extension frames have special partition and cookie behavior. Reuse Sim sessions,
128+
verify enterprise policy and blocked-cookie cases, and show a top-level sign-in
129+
recovery path instead of copying credentials into extension storage.
130+
- [MV3 remote-code policy](https://developer.chrome.com/docs/webstore/program-policies/mv3-requirements):
131+
isolated remote frames are permitted subject to review and user-data rules. All
132+
privileged capture and message logic remains packaged and inspectable.
133+
- [User-data disclosures](https://developer.chrome.com/docs/webstore/program-policies/user-data-faq):
134+
local processing of website content still requires disclosure. Publish actual
135+
data handling and permission justifications with the store submission.
136+
137+
## Release scope and remaining expansion
138+
139+
Planning estimate for one engineer familiar with Sim: 5–8 engineering days for
140+
the core surface, context pipeline and security boundaries; 2–4 more days for
141+
authenticated deployment QA, enterprise cookie-policy checks and release packaging.
142+
These are estimates for production readiness, not a promise that a working local
143+
build has completed those gates. Chrome Web Store review is an external timeline.
144+
Richer document adapters and screenshot capture are roughly another 1–2 weeks;
145+
autonomous browser actions need a separate design and estimate.
146+
147+
The first implementation targets Chrome MV3 and a configured Sim deployment. It
148+
includes the shared chat UI and engine, explicit page/selection context, Assistant
149+
context support, workspace/history navigation, permission controls and verification.
150+
It does not constitute store publication or proof of every third-party site's DOM.
151+
152+
Follow-on work: screenshot/image attachment through existing authorized upload paths;
153+
document-specific adapters with measured coverage; Firefox/Safari shells; managed
154+
enterprise distribution; and separately authorized browser-action tools. Each must
155+
retain the same source, identity, consent and lifecycle boundaries.

0 commit comments

Comments
 (0)