feat(browser): Browser-8 — per-site trust relax for login-gated SPAs (x.com)#139
Merged
Conversation
…e + enforcement)
Login-gated SPAs (x.com etc.) are unusable in the Browser app: the strict
default (third-party-cookie block + tracker blocklist) breaks their SSO +
scripts. Fix per the decided approach — keep the strict default, add a per-site
opt-in TRUST ("compatibility mode") that relaxes both for pages whose FIRST
PARTY is the trusted origin. Never softens the global default.
- `site-trust.ts` — `SiteTrustStore` (mirrors `site-permissions`): strict by
default, `isTrusted(origin)` sync for the webRequest glue, only `trusted:true`
rows persist (untrust deletes). Per-vault `web-site-trust.json`.
- `web-privacy-runtime.ts` — a third vault-scoped store alongside permissions +
egress; `trust.isTrusted/set/revokeOrigin/list`, load + persist + re-key on
vault switch (fail-closed strict with no vault).
- `web-view-factory.ts` — a `trustedFirstParty(details)` check keyed on the
tab's top-level origin (never the request URL); a trusted first party skips
the blocklist cancel AND the 3p-cookie strip. `isTrustedOrigin` dep wired from
the runtime in `main/index.ts`.
17 tests (store parse/CRUD + runtime load/persist/re-key). typecheck + lint green.
Remaining (Slice 2): IPC handlers + Settings → Privacy trusted-sites management.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 2: the management surface over the Browser-8 trust store.
- `web-privacy-handlers.ts` — `web-privacy:site-trust:{list,set,revoke}` privileged
IPC (dashboard-bound, never the broker). `set` canonicalizes + validates the
origin via `webOriginOf` (bare host / non-http(s) → no-op), so only a real web
origin is ever stored.
- preload — `webPrivacy.trust.{list,set,revoke}`.
- `browser-privacy-panel.tsx` — a "Trusted sites" group: an add-origin form
(`x.com` → `https://x.com`, inline invalid hint) + the trusted list with a
per-origin remove, between site-permissions and egress. Uses the shared
`<Button>`; input shares `--control-height-sm` with it. + en.json strings.
typecheck + full lint (biome/reactivity/control-faces/css-tokens/i18n) green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Login-gated SPAs (x.com and similar) are unusable in the in-app Browser: the strict privacy default — third-party-cookie block + tracker blocklist — breaks their SSO and scripts (
flow/viewer.json403,ERR_BLOCKED_BY_CLIENT). There was no per-site escape hatch (site-permissionsonly does device perms).The approach (as decided)
Keep the strict default; add a per-site opt-in TRUST ("compatibility mode"). Trusting an origin relaxes the blocklist + third-party-cookie strip only for pages whose first party is that origin. Never softens the global default — it's opt-in, per site.
Slice 1 — core (store + runtime + enforcement)
site-trust.ts—SiteTrustStore(mirrorssite-permissions): strict by default, syncisTrusted(origin)for the webRequest glue, onlytrusted:truerows persist (untrust deletes). Per-vaultweb-site-trust.json.web-privacy-runtime.ts— a third vault-scoped store; load + persist + re-key on vault switch, fail-closed strict with no vault.web-view-factory.ts— atrustedFirstParty(details)check keyed on the tab's top-level origin (never the request URL); a trusted first party skips the blocklist cancel and the 3p-cookie strip.isTrustedOriginwired from the runtime.Slice 2 — management UI
web-privacy:site-trust:{list,set,revoke}privileged IPC (setcanonicalizes + validates the origin) + preloadwebPrivacy.trust.*.x.com→https://x.com, inline invalid hint) + trusted list with per-origin remove. Shared<Button>; input shares the control height.Verification
117 web tests green (22 new: store parse/CRUD + runtime load/persist/re-key), typecheck (packages) + full lint (biome/reactivity/control-faces/css-tokens/i18n) clean.
Follow-up (not in this PR)
An in-browser "Trust this site" affordance in the Browser chrome (one click when a site is failing) — app-side UI, best verified with a running shell. Settings management here already makes the feature fully usable; repro-pinning x.com under a real shell confirms the cookie-block was the decisive restriction.
🤖 Generated with Claude Code