# 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`](src/main.c:977) and its attached [`WebKitWebsiteDataManager`](src/main.c:993) for the entire process lifetime. That single data manager holds, process-wide: - HTTP disk + memory cache - Cookies (the primary leak — session cookies identify you to web pages) - localStorage / sessionStorage / IndexedDB / WebSQL - Service worker registrations + offline app cache - Favicon database ([`webkit_web_context_set_favicon_database_directory(web_ctx, NULL)`](src/main.c:1003)) The logout / identity-switch paths only clear the Nostr signer and do **nothing** to WebKit's data stores or to the already-loaded tabs: - [`app_menu_logout_proxy`](src/main.c:186) — frees `g_state.signer`, blanks pubkey, returns. - [`agent_logout`](src/agent_login.c:485) — `app_clear_signer()` + `nostr_bridge_set_signer(NULL, ...)`. - [`agent_switch_identity`](src/agent_login.c:492) — frees old signer, logs in new, no tab reload. 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`](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//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`](src/tab_manager.c)). 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 ```mermaid flowchart TD A[Login complete - pubkey known] --> B[profile_ensure_dir pubkey] B --> C[Build per-user WebKitWebsiteDataManager
rooted at profiles/pubkey/webkit] C --> D[Build per-user WebKitWebContext
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.c) / [`src/profile.h`](src/profile.h) add: - `profile_get_webkit_dir(const char *pubkey_hex, char *out, size_t out_sz)` → `~/.sovereign_browser/profiles//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: - [`nostr_bridge_register`](src/nostr_bridge.c) — already takes `ctx`; ensure it can be called twice (track prior registration, disconnect old handlers). - [`nostr_scheme_register`](src/nostr_scheme.c) — make it take `ctx` (currently uses default) and idempotent. - [`tor_scheme_register`](src/tor_scheme.c) — same. - [`net_services_init`](src/net_services.c) — currently calls `webkit_web_context_get_default()` internally ([`net_services.c:69`](src/net_services.c)); pass `ctx` in instead. ### 4. Refactor `tab_manager` to support context swap [`tab_manager_init`](src/tab_manager.h:46) 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`](src/tab_manager.c). ### 5. Wire the login/logout/switch flows - [`app_menu_logout_proxy`](src/main.c:186): 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`](src/agent_login.c:485): 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`](src/agent_login.c:492): teardown old, build new with the new pubkey, restore session. - Initial startup in [`main.c`](src/main.c:974): 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)`](src/main.c:1003) 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//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/`](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 - New: `src/web_context.c`, `src/web_context.h` - [`src/profile.c`](src/profile.c) / [`src/profile.h`](src/profile.h) — webkit dir helpers - [`src/main.c`](src/main.c) — startup ordering, logout proxy, use `web_context_*` - [`src/agent_login.c`](src/agent_login.c) — `agent_logout`, `agent_switch_identity` - [`src/tab_manager.c`](src/tab_manager.c) / [`src/tab_manager.h`](src/tab_manager.h) — context swap support, sidebar teardown - [`src/nostr_bridge.c`](src/nostr_bridge.c) — re-runnable registration - [`src/nostr_scheme.c`](src/nostr_scheme.c) — accept ctx, idempotent - [`src/tor_scheme.c`](src/tor_scheme.c) — accept ctx, idempotent - [`src/net_services.c`](src/net_services.c) — accept ctx instead of default - [`Makefile`](Makefile) — add `web_context.o` - New test page: `tests/local-site/identity-leak-test.html` ## 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`](src/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`](src/tab_manager.h:198)) 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.