Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/ci-suites.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"group": "desktop-inbox",
"task": "ci:e2e-desktop-inbox"
},
{
"group": "browser-extension",
"task": "ci:e2e-browser-extension"
},
{
"group": "seo",
"task": "ci:e2e-seo"
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/ci-plan.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ query($base: String!, $head: String!) {
affectedTasks(base: $base, head: $head, tasks: [
"test", "build", "type-check", "ci:integration", "ci:desktop-live",
"ci:e2e-scim", "ci:e2e-cli", "ci:e2e-stop-after", "ci:e2e-desktop-inbox",
"ci:e2e-project-files", "ci:e2e-mobile", "ci:e2e-seo"
"ci:e2e-browser-extension", "ci:e2e-project-files", "ci:e2e-mobile", "ci:e2e-seo"
]) {
items {
name
Expand Down
31 changes: 28 additions & 3 deletions .github/scripts/http-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# CI groups, which reuse the production build from `e2e-build`.
# The job extracts it into apps/sim/.next before running these suites.
#
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo> (run from apps/sim)
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|browser-extension|project-files|mobile|seo> (run from apps/sim)
#
# The job provides DATABASE_URL, BETTER_AUTH_SECRET and ENCRYPTION_KEY; each group sets the rest of
# its app's environment here. Reports and server logs land in $RUNNER_TEMP/e2e.
Expand All @@ -20,7 +20,7 @@
# telemetry flush runs detached and still writes .next/dev).
set -euo pipefail

group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo>}
group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|browser-extension|project-files|mobile|seo>}
report_dir="$RUNNER_TEMP/e2e"
ready_timeout_seconds=600
mkdir -p "$report_dir"
Expand Down Expand Up @@ -198,7 +198,7 @@ case "$group" in
;;

# The desktop background executor's protocol: device registration, the SSE doorbell over Redis
# pub/sub, presence, leased claims, Stop and isolation. The only group whose app gets Redis.
# pub/sub, presence, leased claims, Stop and isolation.
desktop-inbox)
export REDIS_URL=redis://127.0.0.1:6379
export NEXT_PUBLIC_FORCE_HOSTED=false
Expand Down Expand Up @@ -227,6 +227,31 @@ case "$group" in
bun --no-env-file scripts/test-organization-search-e2e.ts
;;

browser-extension)
export REDIS_URL=redis://127.0.0.1:6379
export NEXT_PUBLIC_FORCE_HOSTED=false
export KNOWLEDGE_MEMBER_ACCESS=true CREDENTIAL_GROUPS=true ACCESS_CONTROL_ENABLED=true
export NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true
export API_ENCRYPTION_KEY="$ENCRYPTION_KEY"
bunx --no-install playwright install --with-deps chromium
extension_identity=$(bun - <<'JS'
import { generateKeyPairSync, createHash } from 'node:crypto'
const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
const der = publicKey.export({ type: 'spki', format: 'der' })
const id = [...createHash('sha256').update(der).digest('hex').slice(0, 32)]
.map(c => String.fromCharCode(97 + parseInt(c, 16))).join('')
process.stdout.write(`${der.toString('base64')}\n${id}`)
JS
)
export SIM_EXTENSION_KEY="${extension_identity%%$'\n'*}"
export BROWSER_EXTENSION_IDS="${extension_identity#*$'\n'}"
start_app browser-extension 3007 'browser extension' record-http-status
EXTENSION_APP_E2E_BASE_URL="$local_app_url" \
EXTENSION_APP_E2E_DATABASE_URL="$DATABASE_URL" \
EXTENSION_APP_E2E_REPORT_PATH="$report_dir/browser-extension/report.json" \
bun run test:browser-extension:e2e
;;

project-files)
PROJECT_FILES_E2E_ADMIN_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres \
PROJECT_FILES_E2E_REDIS_URL=redis://127.0.0.1:6379 \
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/browser-extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: browser-extension

on:
pull_request:
paths:
- '.github/workflows/browser-extension.yml'
- 'apps/extension/**'
- 'packages/browser-extension-protocol/**'
- 'apps/sim/app/extension/**'
- 'apps/sim/app/workspace/**/home/**'
- 'apps/sim/stores/mothership-drafts/**'
- 'apps/sim/scripts/test-browser-extension-e2e.ts'
- 'apps/sim/lib/core/security/**'
- 'apps/sim/lib/mothership/chat/**'
- 'apps/sim/lib/api/contracts/mothership-chat-context.ts'
- 'apps/sim/proxy.ts'
- 'apps/sim/next.config.ts'
- 'apps/sim/package.json'
- 'packages/emcn/**'
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
- 'packages/logger/**'
- 'packages/utils/**'
- 'bun.lock'
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
workflow_dispatch:

permissions:
contents: read

concurrency:
group: browser-extension-${{ github.ref }}
cancel-in-progress: true

jobs:
transport:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.4
- run: bun install --frozen-lockfile
- run: bunx playwright install --with-deps chromium
working-directory: apps/extension
- run: bun run type-check && bun run build && bun run test:e2e
working-directory: apps/extension
env:
EXTENSION_REPORT_PATH: test-results/extension-report.json
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: browser-extension-results
path: apps/extension/test-results
retention-days: 7
8 changes: 5 additions & 3 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ jobs:
--health-interval 5s
--health-timeout 5s
--health-retries 10
# Desktop inbox and Project Files use Redis.
# Desktop inbox, browser extension, and Project Files use Redis.
redis:
image: ${{ (matrix.group == 'desktop-inbox' || matrix.group == 'project-files') && 'redis:8.2-alpine' || '' }}
image: ${{ (matrix.group == 'desktop-inbox' || matrix.group == 'browser-extension' || matrix.group == 'project-files') && 'redis:8.2-alpine' || '' }}
ports:
- 6379:6379
options: >-
Expand Down Expand Up @@ -296,7 +296,9 @@ jobs:
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: e2e-reports-${{ matrix.group }}
path: ${{ runner.temp }}/e2e/**/*.json
path: |
${{ runner.temp }}/e2e/**/*.json
${{ runner.temp }}/e2e/browser-extension/**/*.png
if-no-files-found: ignore
retention-days: 7

Expand Down
159 changes: 159 additions & 0 deletions apps/extension/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Sim browser extension

Implementation scope and research, 2026-10-29.

## Product contract

The extension is another surface of Sim. A native Chrome side panel hosts the actual
Sim composer and transcript, using existing authenticated Mothership requests,
conversation persistence, streaming recovery, tool permissions, and organization
or workspace authorization. The panel keeps Fast mode and omits the model selector.
A conversation can continue in the full app.

The user signs in in a normal Sim tab and lands in their organization chat. Choosing
a workspace is optional; members without Build access use Assistant. They can open
or create a conversation and explicitly attach the current page or highlighted text.
The composer’s + menu offers Current page and Selected text. Pending snapshots sit
inside the composer as compact removable chips alongside file attachments. Opening one
shows the captured text, source, capture time, and conversation-retention disclosure.
Partial captures remain labeled in the attachment row. Site access lives in the
composer’s Site access action, which opens an extension-owned dialog; successful delivery adds no persistent status banner.
Snapshots are immutable: navigating to another page cannot silently change an attached
source. Removing an attachment, changing organization or workspace, or starting a new conversation
clears pending page context. Several explicitly attached pages can support comparison.
Assistant accepts page snapshots while retaining its existing restriction on
workspace-resource contexts.

The extension does not promise complete capture of virtualized documents, canvas
editors, restricted browser pages, inaccessible cross-origin frames, or PDFs.
Extraction failure is visible; the model must never infer absent page content.
Browser interaction, autonomous navigation, screenshot OCR, and native-app control
are separate capabilities requiring their own permission and execution contracts.
Existing Sim server tools remain subject to their existing authorization and approval.

## Ownership and data flow

1. `apps/extension`: packaged MV3 service worker, side-panel host, bounded content
extraction, per-site permission controls, build artifacts, browser verification.
2. `packages/browser-extension-protocol`: versioned, strictly validated messages and
immutable page snapshots shared by the extension, app, and server.
3. `apps/sim/app/extension`: session gate, organization landing, optional workspace picker, app providers,
real chat components, history and a narrow parent-window bridge.
4. Existing chat context pipeline: `browser_page` input validation, durable message
projection, restored context chips, untrusted-content formatting, Assistant admission.
5. Existing Sim API and Mothership: authentication, authorization, billing, tool
execution and streaming; no alternate inference service or extension API key.

The content script returns text to its extension caller. The extension verifies the
captured document still belongs to the intended tab, then sends a typed snapshot to
the exact hosted frame. The frame validates its parent origin and identity, and puts
the snapshot into pending UI context. The existing send path persists it with the
message and passes quoted content to Mothership. A page snapshot grants no access to
other workspace resources or browser-control tools.

## Permission and lifetime rules

- 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.
- 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.
- No history, bookmarks, cookie API, debugger, network interception, native
messaging, external messaging, or arbitrary remote code execution.
- No always-running page reader. Extraction is tied to an explicit attachment action.
- Page bytes are not written to extension local/sync storage. Temporary context-menu
handoff uses trusted-context session storage, expiration and navigation cleanup.
- Sent snapshots become chat data and follow Sim's chat retention and deletion
policy. This must be disclosed in the interface and store listing. No claim of
zero retention is made once a message is sent.
- No page content or full page URL in extension telemetry or routine logs.

## Boundary controls

- Package all privileged JavaScript. The hosted iframe has no extension API access.
- Only exact configured Sim origins and exact configured extension IDs are trusted.
Both directions validate source window, origin, protocol version and message shape.
There is no arbitrary fetch, JavaScript, navigation or extension-API command bridge.
- `BROWSER_EXTENSION_IDS` is a bounded comma-separated list of Chrome IDs. Missing
or malformed configuration disables embedding. Only `/extension` and its children
receive the corresponding frame policy; ordinary app pages keep their frame denial.
- Extension pages use a restrictive CSP; the hosted app keeps its app CSP with only
the embedding ancestor list changed. Sign-in happens in a top-level tab.
- Allow HTTPS production app origins; HTTP is development-only on loopback.
- Capture uses bounded traversal and output size, skips hidden and editable/form
content, and records truncation. It never serializes the entire DOM or reads
cookies, browser storage, input values, or page-world JavaScript state.
- Validate snapshots again server-side, including headless execution paths. Bound
individual and aggregate text/byte sizes. Only HTTP(S) source URLs without embedded
credentials are accepted; URL secrets should not be carried as source metadata.
- Snapshot content is explicitly untrusted quoted JSON. This is a model cue, not
an authorization mechanism: tool access remains independently enforced by Sim.

## Verification planned before implementation

The credible failure modes are cross-origin/frame message forgery; schema-version
confusion; hostile URL metadata; hidden/form data leakage; unbounded DOM traversal;
selection or extraction delivered after navigation; stale context on workspace
switch; persistence mutating the captured text; Assistant silently dropping context;
and an embed exception accidentally allowing unrelated app pages to be framed.

Use a real Chromium extension against controlled HTTP pages for extraction,
navigation, permission and message-boundary checks. Emit a caller-configured JSON
report and screenshot/trace artifacts. Security-boundary tests may cover header
isolation and durable-context semantics where the running app needs unavailable
services. Do not replace real Chrome APIs with mocks and describe that as E2E.

The authenticated live-app acceptance gate covers organization chat, Assistant, and
permitted workspaces: capture a real page, receive a grounded Mothership answer,
reopen the conversation, stop/reconnect streaming, and switch scopes. Verify revoked
access, live policy changes, retained drafts, and late history reads after completion.
An absent backend or account is an unverified gate, not a pass.

## Research and decisions

- [Glean sidebar](https://docs.glean.com/user-guide/apps/extension-sidebar) and
[page permissions](https://docs.glean.com/user-guide/apps/why-does-the-chrome-extension-ask-for-those-permissions):
page context can use address and text without indexing the page. Adopt explicit
page attachment alongside the existing Sim knowledge and tool experience.
- [Chrome activeTab](https://developer.chrome.com/docs/extensions/develop/concepts/activeTab)
and [optional permissions](https://developer.chrome.com/docs/extensions/reference/api/permissions):
temporary access follows user invocation; cross-origin navigation requires fresh
authority. Request persistent access per site only when selected by the user.
- [Chrome extension security](https://developer.chrome.com/docs/extensions/develop/security-privacy/stay-secure)
and [OWASP extension guidance](https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html):
minimize privileges and treat all incoming page messages as attacker-controlled.
Do not expose a generic privileged bridge.
- [Service worker lifecycle](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle):
workers are disposable. Stream from the app document; bound temporary handoffs
independently of worker globals.
- [Storage and cookies](https://developer.chrome.com/docs/extensions/develop/concepts/storage-and-cookies):
extension frames have special partition and cookie behavior. Reuse Sim sessions,
verify enterprise policy and blocked-cookie cases, and show a top-level sign-in
recovery path instead of copying credentials into extension storage.
- [MV3 remote-code policy](https://developer.chrome.com/docs/webstore/program-policies/mv3-requirements):
isolated remote frames are permitted subject to review and user-data rules. All
privileged capture and message logic remains packaged and inspectable.
- [User-data disclosures](https://developer.chrome.com/docs/webstore/program-policies/user-data-faq):
local processing of website content still requires disclosure. Publish actual
data handling and permission justifications with the store submission.

## Release scope and remaining expansion

Planning estimate for one engineer familiar with Sim: 5–8 engineering days for
the core surface, context pipeline and security boundaries; 2–4 more days for
authenticated deployment QA, enterprise cookie-policy checks and release packaging.
These are estimates for production readiness, not a promise that a working local
build has completed those gates. Chrome Web Store review is an external timeline.
Richer document adapters and screenshot capture are roughly another 1–2 weeks;
autonomous browser actions need a separate design and estimate.

The first implementation targets Chrome MV3 and a configured Sim deployment. It
includes the shared chat UI and engine, explicit page/selection context, Assistant
context support, organization/workspace/history navigation, permission controls and verification.
It does not constitute store publication or proof of every third-party site's DOM.

Follow-on work: screenshot/image attachment through existing authorized upload paths;
document-specific adapters with measured coverage; Firefox/Safari shells; managed
enterprise distribution; and separately authorized browser-action tools. Each must
retain the same source, identity, consent and lifecycle boundaries.
Loading
Loading