Plan: a very secure browser in Seed
Phased work to make the web pane in Seed desktop safer than browsing in a normal browser, with the daemon, the app window and the release process included.

This is the plan that follows from Considerations of becoming a browser and Should Seed become a browser?. The goal is narrow on purpose: a reading and research browser inside Seed desktop where the user's keys, private documents and signed-in sites are safer than they would be in a normal browser, because Seed asks less of the engine and grants less to the page.

Written 2026-10-05 from a survey of the desktop, daemon and release code on feat/integrated-browser. Each item names where the work goes and how it is verified. Items marked app-wide change the desktop app for everyone, not only the experiment.

Status, 2026-10-06

Every phase of this plan is implemented and open for review as a stack of pull requests on GitHub:

    #1184 Electron 44, fuses, trimmed entitlements, signed update manifests, Chromium kill switch (Phase 0.2 and 0.4)

    #1185 app window guards, content security policy, preload allowlist, hostile page suite (Phase 0.3)

    #1186 the daemon authenticates the desktop app; loopback by default; server mode flag (Phase 0.1)

    #1138 the web pane itself as a main-process view, with the consent model, network defaults, origin display, private-network block and download warning (Phase 0.5 and part of Phase 1)

    #1193 third-party cookie blocking, private pages, prompts, dialog notices, guest limits (Phase 1.2, 1.5, 1.6)

    #1194 phishing blocklist with an interstitial; tracker and content blocking with a shield (Phase 2.1, 2.2)

    #1191 agent read-only default, action consent, activity log; paste and browser-result hardening (Phase 2.3, 3)

    #1192 security e2e on pull requests, Electron static scan, SECURITY.md, Electron checklist, audit log (Phase 4)

Verified locally: Go daemon suite, desktop and agents unit suites, the Electron end-to-end suites (hostile page, web pane, real daemon), a packaged macOS build with fuses read back, Electron 44.5.1 at runtime. Disclosure issues #1187 and #1188 were filed at fix time.

Update 2026-10-15: browser authentication is opt-in (the desktop passes -http.loopback and the app secret; servers keep the open default, so no infra change is needed). Still for people to decide or do: generate the production update-signing key and flip UPDATE_SIGNATURE_REQUIRED; notarize a build with the trimmed entitlements; an outside penetration test and a bug bounty decision; the pnpm audit job is red on main for pre-existing advisories.

Principles

    Websites never reach anything that is Seed's: the daemon, the app's IPC, the keys, other windows.

    Chromium security fixes ship within days. The pane turns itself off when they do not.

    Deny by default, grant by click.

    Every rule is a test that runs a hostile page in the real Electron build in CI.

    Seed is not a general browser. No autofill, no extensions, no sync. "Open in default browser" is always one click away.

Phase 0: close the gaps that exist today

These come first because they protect the app whether or not the pane ever ships. The pane stays behind the experiment flag until all of them are done.

0.1 The daemon authenticates the desktop (app-wide, daemon)

Today the daemon trusts whoever can reach its localhost port, and its cross-origin policy is open. That must become:

    Listen on loopback only by default. Server deployments opt in to other interfaces with a flag.

    A per-launch secret. The daemon writes it where only the app can read it (or the app passes it in at start); the desktop main process hands it to the renderer; the gRPC-web transport sends it on every call.

    Every route that reads private data or writes anything requires the secret, or an existing bearer token, when the request looks like it came from a browser page (Fetch Metadata headers present and not same-origin, or an Origin header that is not an app origin). Non-browser local clients (the CLI) keep working with their current auth.

    Cross-origin allowlist instead of *. GET /ipfs/{cid} stays public.

    Verify: an Electron end-to-end test loads a page from an address marked public and proves that gRPC-web, file upload and private reads are refused while public IPFS reads succeed. Backend: Julio and Burdiyan.

0.2 Supported Electron, now (app-wide, desktop)

    Upgrade to the current Electron stable line and fix what breaks.

    Policy: at most 14 days behind an Electron stable security release; an automated dependency bot opens the upgrade PR the day it ships.

    Kill switch: latest.json carries a minimum Chromium version. An app older than that turns off the pane, opens links in the real browser, and tells the user to update. The app also refuses the pane when its own Chromium is older than a fixed age, in case the manifest cannot be fetched.

    Verify: CI check that fails when the Electron major is no longer in Electron's supported list.

0.3 Harden the app window (app-wide, desktop)

    A permission handler on the app's own session that denies everything, with a short allowlist for what embeds really need.

    Embed frames: match hosts exactly, never hand a frame navigation or a child window URL to the operating system, and only allow http(s) when the user clicked.

    Strip frame-protection headers only for the app's exact origin.

    A content security policy on the app's own pages.

    Replace the generic ipc.send(command, args) bridge in the preload with named methods, so a renderer bug cannot reach every handler.

    Verify: unit tests for the matchers; an end-to-end test that embeds a hostile page in a document and checks nothing escapes.

0.4 Harden the binary (app-wide, release)

    Electron fuses: no runAsNode, no NODE_OPTIONS, no CLI inspect flags, ASAR integrity on, only load the app from ASAR.

    macOS entitlements trimmed to what the app uses; review why library validation is disabled.

    Signed update manifests: latest.json lists a hash per asset and carries a signature from a release key the app verifies before installing. TLS alone is not enough for a channel that replaces the whole app.

    Verify: a packaging test reads the fuses from the built binary; an update test rejects a manifest with a bad signature.

0.5 Move the pane to WebContentsView (desktop)

The pane uses the webview tag, which means every app window is created with webviewTag: true. A main-process-owned WebContentsView gives the same pane with the app renderer holding no web view privilege at all, and with navigation, permissions and lifetimes handled in one place.

    Verify: the existing Electron end-to-end test moves to the new implementation unchanged; webviewTag is off everywhere.

Phase 1: what every secure browser needs

1.1 Origin display and HTTPS only

    An address area owned by Seed, above the page, that the page cannot cover: scheme, host, lock state. Punycode hosts shown raw. Seed sites show the verified account beside the host, using the same domain-to-account check the omnibar already does.

    HTTPS-only mode: upgrade http to https before loading; show an interstitial when upgrade fails; no "proceed anyway" for certificate errors. Keep the explicit certificate-error handler that always denies.

    Verify: tests for punycode display, a page that sets a misleading title and icon, http upgrade, and a bad certificate.

1.2 Permissions, popups, downloads

    Keep deny-all. Add prompts only for clipboard write and fullscreen, and show a persistent banner while a page is fullscreen.

    Downloads: save dialog after a click (done), a warning for executable types, quarantine attribute set, never auto-open.

    Page dialogs (alert, confirm, prompt) carry the origin and are rate-limited.

    Verify: the end-to-end test asks for each permission and each dialog from a page and checks the outcome.

1.3 Private network and local services

    Block navigation and sub-requests from public pages to loopback, private and link-local addresses, and to hostnames that resolve to them, for all pages, not only agent actions. Addresses the user typed are allowed for that site.

    Deny the local network access permission when the Chromium build exposes it, and confirm on each Electron upgrade whether Chromium now blocks it by default.

    Verify: the probe from Phase 0.1 is kept as a permanent test against both a mock service and the real daemon.

1.4 Network privacy defaults

    WebRTC restricted to the public interface.

    Encrypted DNS through Chromium's secure DNS setting, with an option for a resolver that filters known-bad domains.

    Sec-GPC: 1 on every request from the pane.

    No referrer when a link is opened from a Seed document; Chromium's default referrer policy inside the pane.

    Verify: header and WebRTC checks in the end-to-end test.

1.5 Storage

    Third-party cookies blocked. If Electron cannot do that directly, give each top-level site its own partition.

    Private window: an in-memory partition that is dropped on close.

    "Clear browsing data" stays one click away; add it to the pane's own menu.

    Verify: a test that sets a cookie on site A, embeds A in site B, and checks that B cannot read it.

1.6 Site isolation and process limits

    Confirm site-per-process is on and never pass flags that weaken it.

    One renderer per site, a cap on open pages, and a crash handler that reloads nothing automatically.

Phase 2: protections against specific attacks

2.1 Known-bad sites

    Start with open blocklists (phishing and malware feeds) applied to main-frame navigations, with an interstitial the user can read.

    Evaluate Google Safe Browsing's update API and its terms; adopt it if allowed.

    Verify: a navigation to a listed test domain shows the interstitial.

2.2 Tracker and content blocking

    A tracker-blocking engine with EasyList and EasyPrivacy, on by default, with a per-site off switch.

    Verify: a test page with a known tracker request shows it blocked.

2.3 Anti-spoofing

    The pane never draws over Seed's chrome; fullscreen is indicated; no page can open a window that looks like a Seed window.

    Anything that asks to sign or publish is a native Seed dialog that names the page's real origin, like the extension signing dialog does.

    Paste and drag from a page into the editor go through the same sanitizer as paste from the web.

    Verify: tests for fullscreen banner and for a page that tries to open a Seed-looking popup.

2.4 Seed links from the web

    Switch to a Seed page only after a real click (done); never from redirects or scripts. Seed pages opened from the web show the same warning as links from unknown email.

Phase 3: agents in the pane

The rules on the security task stay. Add:

    A read-only default: snapshot and screenshot are allowed by the website grant; click, type and navigate need a second approval per site.

    A visible action log in the panel: every command the agent ran, with the page it ran on.

    Agent results never enter automatic titling, search indexes or shared memory (done), and that stays covered by tests.

Phase 4: verification and process

    Hostile page suite. One Playwright test file that runs in CI on every pull request with the real Electron build and a page that tries everything on this list: the daemon, the app's IPC, popups, downloads, permissions, external protocols, private addresses, certificate errors, misleading titles. Every rule above is a case here.

    Static checks. An Electron security scanner in CI, and the Electron security checklist kept as a page with the status of each item.

    Release checks. Supported Electron major, fuses present, update manifest signed, entitlements unchanged.

    Disclosure. A SECURITY.md with a contact and a response time, and the existing audit log kept up to date. A bug bounty once the pane is on by default.

    External review. One outside penetration test of the pane and the daemon API before the pane leaves the experiment.

Order of work

    Phase 0.1 (daemon) and 0.2 (Electron) first, in parallel, because they protect everyone today.

    Phase 0.3 to 0.5 next; then the pane can go to more testers behind the flag.

    Phase 1 before the pane is on by default.

    Phase 2 and 3 as the pane gets real use.

    Phase 4 runs throughout; the hostile page suite starts in Phase 0 and grows with every item.

Non-goals

Tabs beyond what reading needs, bookmarks, sync, a password manager, browser extensions, becoming the default browser. If any of these turns out to be needed, it is a new decision, not a step in this plan.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime