Files
sovereign_browser/plans/webkit-data-isolation.md
T

11 KiB

WebKit Website Data Isolation Per Nostr Identity

Status: IMPLEMENTED (Phase 0 + Phase A, verified end-to-end).

Problem

When a user logs in, visits a page, logs out, then logs in as a different identity and revisits the same page, the second user sees the first user's data (session cookies, localStorage, cached responses, service workers).

Root Cause

The browser uses the default WebKitWebContext and its attached WebKitWebsiteDataManager for the entire process lifetime. That single data manager holds, process-wide:

The logout / identity-switch paths only clear the Nostr signer and do nothing to WebKit's data stores or to the already-loaded tabs:

Two compounding problems:

  1. Persistent WebKit data survives the identity switch.
  2. Live tabs keep user A's DOM/localStorage in memory; wiping the data manager alone does not clear already-loaded pages.

The existing plans/per-user-profiles.md isolates the SQLite layer (bookmarks, history, session, Nostr events) but does not cover WebKit's own storage — this plan closes that gap.

Solution (chosen: per-user data manager + clear-on-switch safety net)

Part A — Per-user WebKitWebsiteDataManager

Give each pubkey its own data manager rooted at ~/.sovereign_browser/profiles/<pubkey_hex>/webkit/ so cookies, cache, storage, service workers, and favicons persist per-user and never cross-contaminate.

WebKitGTK constraint: a WebKitWebContext is bound to one WebKitWebsiteDataManager for its lifetime — you cannot swap the data manager on an existing context. Two viable strategies:

Strategy 1 (preferred): per-user WebKitWebContext. Create a fresh webkit_web_context_new_with_website_data_manager(dm) for each login. All tabs/webviews are created from this context. On logout/switch, destroy the old context (which tears down its webviews) and build a new one. This is the cleanest isolation and matches how Epiphary/GNOME Web does per-profile isolation.

Strategy 2 (fallback): single context, manual clear + per-user dirs. Keep the default context but on each login call webkit_website_data_manager_clear(dm, WEBKIT_WEBSITE_DATA_ALL, 0, NULL, NULL, NULL) then point the cookie/cache/storage dirs at the per-user path. This is fragile because the context caches some state internally and the favicon DB directory is set once at context init.

Recommend Strategy 1. It requires reworking how the context is created and how tab_manager / nostr_bridge / tor_scheme / nostr_scheme / net_services reference it (they currently call webkit_web_context_get_default() or receive the context at init), but it gives true isolation.

Part B — Clear-on-switch safety net

Even with per-user data managers, the live tabs (and the agent chat sidebar webview) hold user A's DOM/localStorage in memory. On any identity change:

  1. Close all tabs: tab_manager_close_all() (tab_manager.c:3727).
  2. Destroy the agent chat sidebar webview if present.
  3. (Strategy 1) Destroy the old WebKitWebContext + data manager.
  4. Build the new per-user context + data manager.
  5. Re-register URI schemes (sovereign://, nostr://, tor://) on the new context — they are per-context.
  6. Re-init nostr_bridge, nostr_scheme, tor_scheme, net_services against the new context.
  7. Re-init tab_manager against the new context (or expose a tab_manager_set_context()).
  8. Restore the new user's session (session_restore()) or open the default new-tab URL.

Architecture

flowchart TD
    A[Login complete - pubkey known] --> B[profile_ensure_dir pubkey]
    B --> C[Build per-user WebKitWebsiteDataManager<br/>rooted at profiles/pubkey/webkit]
    C --> D[Build per-user WebKitWebContext<br/>new_with_website_data_manager]
    D --> E[Register sovereign/nostr/tor schemes on new ctx]
    E --> F[Re-init nostr_bridge, net_services, tab_manager on new ctx]
    F --> G[session_restore or open new-tab URL]

    H[Logout / switch_identity] --> I[tab_manager_close_all]
    I --> J[Destroy sidebar webview]
    J --> K[Destroy old WebKitWebContext + data manager]
    K --> L{switch or logout?}
    L -->|switch| A
    L -->|logout| M[Show login dialog / wait for agent login]
    M --> A

Implementation Steps

1. Profile helpers for WebKit data dir

In src/profile.c / src/profile.h add:

  • profile_get_webkit_dir(const char *pubkey_hex, char *out, size_t out_sz)~/.sovereign_browser/profiles/<pubkey>/webkit/
  • profile_ensure_webkit_dir(const char *pubkey_hex) — mkdir -p.

2. New module src/web_context.c / src/web_context.h

Centralize context + data manager construction so login/logout/switch share one code path. Functions:

  • web_context_build_for_pubkey(const char *pubkey_hex) → returns a new WebKitWebContext* with a per-user WebKitWebsiteDataManager (cookies, cache, local_storage, indexed_db, websql, offline_app_cache, service_worker registrations, itp, hsts all pointed at the per-user webkit dir), favicon DB enabled, TLS errors ignored, security manager configured (sovereign/tor/file secure+local).
  • web_context_teardown(WebKitWebContext *ctx) — unrefs context + data manager, frees per-user state.
  • Holds the current context pointer (g_web_ctx) so the rest of the codebase can fetch it without webkit_web_context_get_default().

3. Refactor scheme/bridge/service init to be re-runnable

Currently these are one-shot inits tied to the default context. Make them accept a WebKitWebContext* and be safe to call again on a new context:

4. Refactor tab_manager to support context swap

tab_manager_init currently stores the context once. Add:

  • tab_manager_set_context(WebKitWebContext *ctx) — updates the stored context so subsequent tab_manager_new_tab calls use the new context. Existing tabs are already closed (Part B step 1) before this is called.
  • Or simpler: tear down and re-init tab_manager on each switch. Pick whichever is less invasive given the static globals in tab_manager.c.

5. Wire the login/logout/switch flows

  • app_menu_logout_proxy: after clearing the signer, run the teardown sequence (close all tabs, destroy sidebar, destroy context) then re-show the login dialog. On successful login, run the build sequence.
  • agent_logout: same teardown, then leave the browser in a logged-out state (no context, or a minimal ephemeral context showing a "logged out" page).
  • agent_switch_identity: teardown old, build new with the new pubkey, restore session.
  • Initial startup in main.c: replace webkit_web_context_get_default() with web_context_build_for_pubkey() after login completes (this means deferring context creation until after the login dialog — reordering the startup sequence).

6. Favicon database per user

webkit_web_context_set_favicon_database_directory(web_ctx, NULL) currently uses the default shared location. In web_context_build_for_pubkey, pass the per-user webkit dir (or a favicons/ subdir) so favicons don't leak between users.

7. Read-only / no-login mode

When running with --no-login or read-only, there is no pubkey to key the data dir on. Use an ephemeral data manager (webkit_website_data_manager_new_ephemeral()) so no data persists at all, or a shared ~/.sovereign_browser/webkit-anon/ dir. Decide and document.

8. Migration

Existing users have data in WebKit's default location (~/.cache/sovereign_browser/ or similar). On first login with the new system, optionally copy the default WebKit data dir into profiles/<pubkey>/webkit/ so they keep their cookies/cache. Low priority — can ship without migration and just start fresh per user.

9. Tests

  • Manual: log in as user A, visit a site that sets a cookie/localStorage, log out, log in as user B, visit the same site, confirm user A's data is gone and user B gets a fresh session.
  • Add a test page under tests/local-site/ that writes a visible marker to localStorage + a cookie, so this is reproducible.
  • Verify sovereign://, nostr://, tor:// schemes still work after a switch (they are re-registered on the new context).
  • Verify the agent chat sidebar works after a switch (its webview is rebuilt on the new context).

Files to Modify

Risk Assessment

  • High risk — changes how the WebKitWebContext is created and tears down, which touches every subsystem that references the context.
  • Startup reordering — context creation must move to after login, which changes the long-standing flow in main.c.
  • Re-runnable scheme registration — WebKit may not support unregistering scheme handlers cleanly; verify that building a fresh context and re-registering works without leaking the old context.
  • Sidebar webview — the agent chat sidebar (tab_manager_toggle_sidebar) keeps a long-lived webview; must be destroyed on context swap or it will hold a ref to the dead context.
  • Mitigation: implement Part B (clear-on-switch) first as a standalone change — it alone fixes the leak even without per-user dirs. Ship that, then layer Part A (per-user data managers) on top.