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

225 lines
11 KiB
Markdown

# 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/<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`](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<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.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/<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:
- [`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/<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/`](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.