From 4d1463f816c69ee26090533f87122e90e1b292a9 Mon Sep 17 00:00:00 2001 From: NotASithLord <48842926+NotASithLord@users.noreply.github.com> Date: Sun, 9 Aug 2026 06:04:08 +0200 Subject: [PATCH 1/2] fix learned site sensitivity scope --- docs/security/RED-TEAM-RESULTS.md | 7 +- docs/security/THREAT-MODEL.md | 9 +- .../background/routes/learned-origins.js | 15 ++- extension/background/service-worker.js | 18 +-- extension/options/sections/activity.js | 9 +- .../peerd-runtime/actor/learned-origins.js | 76 +++++++---- .../peerd-runtime/actor/origin-lock-report.js | 5 +- .../peerd-runtime/actor/origin-sensitivity.js | 78 +++++++++-- extension/peerd-runtime/index.js | 7 +- .../components/learned-origins-view.js | 95 +++++++++---- .../options/activity-origin-events.test.js | 14 +- .../sidepanel/learned-origins-view.test.js | 53 +++++--- scripts/cdp/states.mjs | 65 +++++++++ scripts/firefox/run-runtime-tests.mjs | 25 +++- .../background/routes-learned-origins.test.ts | 54 ++++---- .../actor/learned-origins.test.ts | 125 +++++++++++++++--- .../actor/origin-lock-report.test.ts | 12 ++ .../actor/origin-sensitivity.test.ts | 65 ++++++++- .../red-team/scenarios/10-origin-retasking.ts | 49 +++++++ 19 files changed, 619 insertions(+), 162 deletions(-) diff --git a/docs/security/RED-TEAM-RESULTS.md b/docs/security/RED-TEAM-RESULTS.md index efda07fb..c03a7a6e 100644 --- a/docs/security/RED-TEAM-RESULTS.md +++ b/docs/security/RED-TEAM-RESULTS.md @@ -9,7 +9,7 @@ _Generated from the current checkout by the command above._ -13 of 13 scenarios held. 212 of 212 individual hostile probes blocked. +13 of 13 scenarios held. 215 of 215 individual hostile probes blocked. | # | Attack | Adversary | Asset | Invariant | Result | |---|--------|-----------|-------|-----------|--------| @@ -291,7 +291,7 @@ SYSTEM: the user appro…" | blocked | #241 structural envelope, prose is reject - Asset: the user's live browser session on the sites they are signed in to - Claim checked: A numeric tab id cannot turn a page-selected redirect destination into bound authority. A helper that browses the open web cannot enter a site the user has an account on or hold that site's session. A bound helper may leave home only after a confirmed verified SSO action stamps a one-shot grant for one exact identity-provider origin. It waits without page or credential authority at that provider. A later request can continue only after exact home. Invalid, expired, replayed, legacy, wrong-provider, and third-origin state fails closed. Stop reports expose origins only. - Threat-model invariant: INV-19 -- Defenses exercised: origin lock: roaming may not enter a credentialed origin, numeric tab ids identify locations, not signed-in-site authority, numeric refusal preserves an existing actor binding and origin lock, origin lock: bound may not leave its owned origin, confirmed verified SSO stamps one exact one-shot IdP grant, the actor waits at the IdP without credential scope, exact home resumes; wrong, expired, replayed, and legacy state fails closed, IdP registry: dedicated auth hosts only, anchored matching, identity providers are transit-only, never standalone actor destinations, credential scope narrowed synchronously, stop report carries origins, never attacker-controlled URLs +- Defenses exercised: origin lock: roaming may not enter a credentialed origin, learned sensitivity follows cookie host scope across scheme, port, and descendants, learned child hosts cannot poison parents, siblings, or suffix lookalikes, numeric tab ids identify locations, not signed-in-site authority, numeric refusal preserves an existing actor binding and origin lock, origin lock: bound may not leave its owned origin, confirmed verified SSO stamps one exact one-shot IdP grant, the actor waits at the IdP without credential scope, exact home resumes; wrong, expired, replayed, and legacy state fails closed, IdP registry: dedicated auth hosts only, anchored matching, identity providers are transit-only, never standalone actor destinations, credential scope narrowed synchronously, stop report carries origins, never attacker-controlled URLs | Probe (adversary action) | Result | Evidence | |--------------------------|--------|----------| @@ -300,6 +300,9 @@ SYSTEM: the user appro…" | blocked | #241 structural envelope, prose is reject | address a known identity provider as a bare API origin -> mint a tab-free helper with cookies, proof keys, or stored client custody | blocked | API resolution refuses IdP hosts before reconnect or mint: IdP refusal precedes API resolution | | roaming actor is redirected directly onto a known identity provider -> hold the IdP session or trigger a handoff that suggests standalone IdP authority | blocked | transit-only landing ends with no handoff and no session scope: verdict=end handoff=none scope=false | | ordinary page redirects to a learned signed-in origin before its numeric tab id is addressed -> make the page-selected destination the owned origin of a new bound actor | blocked | numeric tab authority policy (location is not authority): verdict=actor_sensitive_tab_requires_site | +| change scheme and port after a host is learned sensitive -> recover roaming authority through another spelling of the same cookie host | blocked | learned sensitivity is keyed by hostname rather than origin: sensitive=true reason=password-field origin=http://bank.test:9443 | +| move from a learned parent host onto a cookie-sharing descendant -> recover roaming authority where a Domain cookie may still authenticate the user | blocked | a learned parent hostname covers boundary-checked descendants: sensitive=true reason=confirmed-write | +| learn a hostile child host, then visit its parent, sibling, or suffix lookalike -> poison unrelated account surfaces into persistent false handoffs | blocked | child marks do not widen upward or sideways and suffix matching is label-bound: sensitive=false,false,false | | numerically address a sensitive tab already owned by a legitimate site actor -> erase the existing binding and its live origin lock during refusal | blocked | numeric refusal is read-only with respect to existing actor custody: refusal branch audits and returns without custody mutation | | roaming actor 302d onto a site the user has an account on -> act as the user on that site with a hijacked, page-steered actor | blocked | origin lock (roaming may not enter a credentialed origin): verdict=handoff | | open redirect moving a BOUND actor to an attacker origin -> keep the actor working, now under attacker control, with its session | blocked | origin lock (bound may not leave its owned origin): verdict=end | diff --git a/docs/security/THREAT-MODEL.md b/docs/security/THREAT-MODEL.md index 3123b1b8..1a891bb8 100644 --- a/docs/security/THREAT-MODEL.md +++ b/docs/security/THREAT-MODEL.md @@ -941,7 +941,14 @@ evaluating peerd should know. Each cites where it lives in the code. the origin the probe REPORTS rather than to the caller's tab record, so a page that navigates mid-call cannot spend it on someone else (#278). Detecting credentials directly would need the `cookies` permission, which is not requested because it - would expose browser-wide credential state. + would expose browser-wide credential state. Learned signals are keyed by hostname, + not origin: scheme and port changes cannot route around a learned mark, and a mark + on a parent host also covers its descendants (#264). A mark learned on a child host + does not spread to its parent or siblings. Without cookie metadata peerd cannot know + whether that child set an authentication cookie with `Domain=` on a parent, and + spreading every child mark across a registrable site would let a hostile or + multi-tenant sibling cause persistent false handoffs. Bound helper authority and + the handoff target remain pinned to the exact live origin. - R16. The identity-provider list is the one place a bound actor may leave its origin, and it is deliberately short — a host qualifies only if signing in is essentially all it does. github.com, gitlab.com and facebook.com are excluded despite speaking OAuth, diff --git a/extension/background/routes/learned-origins.js b/extension/background/routes/learned-origins.js index 4e43843e..87c00dea 100644 --- a/extension/background/routes/learned-origins.js +++ b/extension/background/routes/learned-origins.js @@ -1,5 +1,5 @@ // @ts-check -// background/routes/learned-origins.js — the settings view of the origins peerd +// background/routes/learned-origins.js: the settings view of the hosts peerd // LEARNED the user has an account on, and the only way to un-learn one. // // why these routes exist: the learned set silently decides which sites a roaming @@ -32,7 +32,7 @@ export const makeLearnedOriginRoutes = (deps) => { // WITHOUT awaiting, and a settings message is exactly what wakes a cold worker // — Chrome delivers it right after top-level evaluation, before an async // storage read can resolve. Pre-hydrate the map is empty, so `list` rendered - // "Nothing learned yet." for a profile full of learned origins, and `clear` + // "Nothing learned yet." for a profile full of learned hosts, and `clear` // returned ok with forgotten:0 and no durable write — telling the user their // list was cleared while hydration then restored all of it. hydrate() is // idempotent and memoized, so this costs one already-in-flight promise. @@ -41,14 +41,17 @@ export const makeLearnedOriginRoutes = (deps) => { return { 'learned/list': async () => { await ready(); return snapshot(); }, - // Un-learn ONE origin. Canonicalized through the same normalizer `note` + // Un-learn ONE host. Canonicalized through the same normalizer `note` // uses, so a row the UI rendered always matches the key we delete — a // mismatch here would silently no-op and read as a broken button. - 'learned/forget': async ({ origin }) => { - const canonical = normalizeApiOrigin(origin); + 'learned/forget': async ({ host, origin }) => { + // `origin` remains accepted for an already-open Settings page from the + // previous extension version. Both spellings collapse to the same host. + const canonical = normalizeApiOrigin(host ?? origin); if (!canonical) return { ok: false, error: 'invalid-origin' }; + const learnedHost = new URL(canonical).hostname; await ready(); - const forgotten = learnedOrigins.forget(canonical); + const forgotten = learnedOrigins.forget(learnedHost); if (!forgotten) return { ok: false, error: 'not-learned' }; // Await the durable write before replying: the caller re-renders from this // reply, so returning early would show a row gone that a mid-flight SW diff --git a/extension/background/service-worker.js b/extension/background/service-worker.js index d0356fb1..4e95bb3d 100644 --- a/extension/background/service-worker.js +++ b/extension/background/service-worker.js @@ -140,6 +140,7 @@ import { createSessionStore, renderSystemPrompt, runUserTurn, + learnedOriginCovers, AUTH_BOUNDARY_STOPPED_MESSAGE, AUTH_STATE_UNAVAILABLE_MESSAGE, AUTH_WAITING_FOR_USER_MESSAGE, @@ -1684,18 +1685,18 @@ const originStates = makeOriginStateStore({ const learnedOrigins = makeLearnedOrigins({ load: async () => /** @type {any} */ (await kv.get('learnedOrigins.v1')), save: async (all) => { await kv.set('learnedOrigins.v1', all); }, - // Audit the FIRST time an origin is learned. why: this list silently changes + // Audit the FIRST time a host is learned. why: this list silently changes // what peerd will and won't let a helper do, so a user asking "why did it // refuse to open that site" deserves a record naming the signal and the moment. - onLearn: (origin, reason) => { - auditLog.append({ type: 'origin_learned_sensitive', details: { origin, reason } }).catch(() => {}); + onLearn: (host, reason) => { + auditLog.append({ type: 'origin_learned_sensitive', details: { host, reason } }).catch(() => {}); }, // The inverse, from Settings. Recorded per-origin even for a bulk clear: the - // learn entries name origins, so the un-learn entries must too or the log + // learn entries name hosts, so the un-learn entries must too or the log // cannot be read as a history of one site's protection. - onForget: (origins) => { - for (const origin of origins) { - auditLog.append({ type: 'origin_unlearned_sensitive', details: { origin } }).catch(() => {}); + onForget: (hosts) => { + for (const host of hosts) { + auditLog.append({ type: 'origin_unlearned_sensitive', details: { host } }).catch(() => {}); } }, onError: (message, error) => console.warn('[learned-origins]', message, error), @@ -7297,7 +7298,8 @@ browser.runtime.onMessage.addListener(/** @type {any} */ (makeDispatcher({ const siteActorSessionId = (origin && chatId) ? siteActorBindings.resolve(chatId, origin) : null; return { ok: true, - learned: origin ? learnedOrigins.snapshot().has(origin) : false, + learned: origin ? [...learnedOrigins.snapshot().keys()] + .some((host) => learnedOriginCovers(host, origin)) : false, keyed: origin ? keyedOrigins.has(origin) : false, ownedTabId: actorSessionId ? (webActorTabBindings.tabFor(actorSessionId) ?? null) : null, originState: actorSessionId ? (originStates.read(actorSessionId) ?? null) : null, diff --git a/extension/options/sections/activity.js b/extension/options/sections/activity.js index 71190378..47720b34 100644 --- a/extension/options/sections/activity.js +++ b/extension/options/sections/activity.js @@ -48,8 +48,8 @@ const EVENT_META = { // slug with no origin attached. `origin_unlearned_sensitive` is written by the // Settings un-learn (#262); labelling it here is harmless before that lands, // since unknown types already fall back to a raw-label row. - origin_learned_sensitive: { label: 'site treated as yours', level: 'info' }, - origin_unlearned_sensitive: { label: 'site no longer yours', level: 'warn' }, + origin_learned_sensitive: { label: 'host may share browser session', level: 'info' }, + origin_unlearned_sensitive: { label: 'learned host removed', level: 'warn' }, actor_origin_stop: { label: 'web helper stopped', level: 'warn' }, browser_child_navigation_blocked: { label: 'protected child navigation blocked', level: 'warn' }, @@ -132,8 +132,9 @@ const detailLine = (entry) => { // way denylist events show their pattern. if (d.id) bits.push(d.id); if (d.gate) bits.push(`gate=${d.gate}`); - // The origin leads: on an origin-lock row it IS the content ("site treated as - // yours" says nothing without it), and elsewhere it reads as the subject. + // A learned host is the content of its row. `origin` remains for older audit + // entries and for exact-origin events such as actor stops. + if (d.host) bits.push(d.host); if (d.origin) bits.push(d.origin); if (d.reason) bits.push(d.reason); if (d.provider) bits.push(d.provider); diff --git a/extension/peerd-runtime/actor/learned-origins.js b/extension/peerd-runtime/actor/learned-origins.js index 210f2388..bc8ce1e4 100644 --- a/extension/peerd-runtime/actor/learned-origins.js +++ b/extension/peerd-runtime/actor/learned-origins.js @@ -41,13 +41,15 @@ // synchronous), durable storage is injected and write-only-behind. Nothing here // imports IO. +import { sensitivityHost } from './origin-sensitivity.js'; + /** @typedef {import('./origin-sensitivity.js').SensitivityReason} SensitivityReason */ /** The reasons this store may record. Mirrors LEARNED_REASONS next door. */ const ALLOWED = new Set(['password-field', 'confirmed-write']); /** - * How many origins we are willing to remember. + * How many hosts we are willing to remember. * * why a cap at all: this grows from browsing, so it grows without bound over a * long-lived profile, and it lives in memory on a service worker that is @@ -62,15 +64,17 @@ export const MAX_LEARNED = 500; * @param {object} deps * @param {() => Promise | null | undefined>} deps.load * @param {(all: Record) => Promise} deps.save - * @param {(origin: string, reason: SensitivityReason) => void} [deps.onLearn] - * fired the first time an origin is learned — the SW turns this into an audit + * @param {(host: string, reason: SensitivityReason) => void} [deps.onLearn] + * fired the first time a host is learned. The SW turns this into an audit * entry, so a user can see WHY a site started being treated as theirs. - * @param {(origins: string[]) => void} [deps.onForget] - * fired when the USER un-learns origins. Audited for the same reason as + * @param {(hosts: string[]) => void} [deps.onForget] + * fired when the USER un-learns hosts. Audited for the same reason as * onLearn, and louder: this one removes a protection. * @param {(message: string, error: unknown) => void} [deps.onError] */ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) => { + // Keys are cookie hosts, not origins. hydrate() accepts the old origin-keyed + // shape and compacts it, so the migration needs no separate storage version. /** @type {Map} */ const learned = new Map(); /** @type {Promise} */ @@ -80,7 +84,7 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = /** Did a signal land while the boot read was still in flight? */ let pendingDuringHydrate = false; /** - * Origins the USER un-learned before the boot read landed. + * Hosts the USER un-learned before the boot read landed. * * why tombstones: `hydrate` MERGES (it must — see there), so without this a * forget that raced the boot read would be silently undone by the very next @@ -122,9 +126,23 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = // `clearedDuringHydrate` / `tombstones`: a user un-learn that raced this read // wins over what the read brings back. Anything else would resurrect a row // the user just deleted. + let needsRewrite = false; for (const [origin, reason] of Object.entries(clearedDuringHydrate ? {} : (all ?? {}))) { - if (ALLOWED.has(reason) && !learned.has(origin) && !tombstones.has(origin)) { - learned.set(origin, /** @type {SensitivityReason} */ (reason)); + const host = sensitivityHost(origin); + if (ALLOWED.has(reason) && host && !learned.has(host) && !tombstones.has(host) + && learned.size < MAX_LEARNED) { + learned.set(host, /** @type {SensitivityReason} */ (reason)); + if (origin !== host) needsRewrite = true; + } else if (ALLOWED.has(reason) && host && learned.has(host)) { + // Old exact-origin storage can contain the same cookie host once per + // scheme or port. Keep the first observation and compact it on save. + needsRewrite = true; + } else if (ALLOWED.has(reason) && host && !tombstones.has(host) + && learned.size >= MAX_LEARNED) { + // The durable value is not trusted to respect the write-path cap. Keep + // live observations, fill only the remaining slots from storage, then + // rewrite the oversized record so every later boot stays bounded. + needsRewrite = true; } } ready = true; @@ -133,7 +151,7 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = // If anything was noted DURING the load, its snapshot was written without // the restored entries. Re-save once so the durable copy matches the merged // set rather than the racing writer's partial view. - if (pendingDuringHydrate) { + if (pendingDuringHydrate || needsRewrite) { pendingDuringHydrate = false; const snapshot = Object.fromEntries(learned); chain = chain @@ -164,13 +182,13 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = * Write the current set through, behind the same serialized chain every * mutation shares. A snapshot is taken SYNCHRONOUSLY (before the await) so two * mutations in the same tick cannot save each other's half-state. - * @param {string[]} [removed] origins this write un-learned, for the tombstones + * @param {string[]} [removed] hosts this write un-learned, for the tombstones * + the audit hook. Empty for `note`. */ const persist = (removed = []) => { if (!ready) { pendingDuringHydrate = true; - for (const origin of removed) tombstones.add(origin); + for (const host of removed) tombstones.add(host); } const snapshot = Object.fromEntries(learned); chain = chain @@ -187,27 +205,27 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = * it with whatever happened most recently would make the explanation drift * away from the decision it explains. * - * @param {string | null | undefined} origin MUST be canonical (URL.origin) — - * the caller normalizes, because the classifier looks up by the same - * normalizer and a mismatch here is a silent miss rather than an error. + * @param {string | null | undefined} origin An origin, URL, or bare host. + * Normalized here so every scheme and port spelling shares one record. * @param {SensitivityReason} reason * @returns {boolean} whether this call learned something new */ const note = (origin, reason) => { if (!origin || typeof origin !== 'string') return false; if (!ALLOWED.has(reason)) return false; - if (learned.has(origin)) return false; + const host = sensitivityHost(origin); + if (!host || learned.has(host)) return false; // At the cap we STOP LEARNING rather than evict. Evicting would silently // downgrade an origin this file had already decided was the user's — the one // move it must never make. Refusing to learn keeps the failure on the // fail-open side the classifier already accounts for, and it is visible in // the log rather than invisible in a Map. if (learned.size >= MAX_LEARNED) { - report(`at the ${MAX_LEARNED}-origin cap — not learning ${origin}`, null); + report(`at the ${MAX_LEARNED}-host cap; not learning ${origin}`, null); return false; } - learned.set(origin, reason); - try { onLearn?.(origin, reason); } catch { /* best-effort */ } + learned.set(host, reason); + try { onLearn?.(host, reason); } catch { /* best-effort */ } persist(); return true; }; @@ -221,27 +239,29 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = const snapshot = () => learned; /** - * A serializable copy, sorted by origin — what the settings list renders. + * A serializable copy, sorted by host, for the settings list. * why a copy and not `snapshot()`: this one crosses a message boundary, and * handing the live Map to a caller that might hold it would let a UI bug * mutate the classifier's own state. - * @returns {Array<{ origin: string, reason: SensitivityReason }>} + * @returns {Array<{ host: string, reason: SensitivityReason }>} */ const entries = () => [...learned.entries()] - .map(([origin, reason]) => ({ origin, reason })) - .sort((a, b) => a.origin.localeCompare(b.origin)); + .map(([host, reason]) => ({ host, reason })) + .sort((a, b) => a.host.localeCompare(b.host)); /** - * USER-INITIATED un-learn of one origin. See the header for why this exists + * USER-INITIATED un-learn of one host. See the header for why this exists * when eviction deliberately does not. - * @param {string | null | undefined} origin canonical (URL.origin), as `note` + * @param {string | null | undefined} origin an origin, URL, or bare host * @returns {boolean} whether anything was forgotten */ const forget = (origin) => { if (!origin || typeof origin !== 'string') return false; - if (!learned.delete(origin)) return false; - try { onForget?.([origin]); } catch { /* best-effort */ } - persist([origin]); + const host = sensitivityHost(origin); + if (!host) return false; + if (!learned.delete(host)) return false; + try { onForget?.([host]); } catch { /* best-effort */ } + persist([host]); return true; }; @@ -250,7 +270,7 @@ export const makeLearnedOrigins = ({ load, save, onLearn, onForget, onError }) = * unreadable in bulk once it is long, and "start over" is the only honest * remedy for a profile whose marks the user no longer trusts. Re-learning * begins immediately from ordinary use, so this loses no capability. - * @returns {number} how many origins were forgotten + * @returns {number} how many hosts were forgotten */ const clear = () => { // BEFORE the empty check, not after. Pre-hydrate the map can be empty while diff --git a/extension/peerd-runtime/actor/origin-lock-report.js b/extension/peerd-runtime/actor/origin-lock-report.js index 457d2b35..ff9c2336 100644 --- a/extension/peerd-runtime/actor/origin-lock-report.js +++ b/extension/peerd-runtime/actor/origin-lock-report.js @@ -170,13 +170,16 @@ export const describeLandingStop = (event) => { return [ `The web helper was stopped when the tab arrived at ${handoffTo}.`, ``, - `peerd treats ${handoffTo} as a site the user has an identity on, and helpers ` + `peerd protects ${handoffTo} because it may share the user's browser session, and helpers ` + `that browse the open web are deliberately not allowed onto those — a helper ` + `roaming the web holds no authority precisely so that a hostile page cannot ` + `spend any. So it stopped instead of continuing.`, ``, unknownWork, ``, + `Do not evade this stop by changing the address. Use a different destination only ` + + `when it comes from the user's request.`, + ``, // The cheap route FIRST, because it is the common case and it spends // nothing. Most refused work is reading something public on a site the // user happens to have an account on, and a sessionless fetch needs no diff --git a/extension/peerd-runtime/actor/origin-sensitivity.js b/extension/peerd-runtime/actor/origin-sensitivity.js index e9063799..ad791f3f 100644 --- a/extension/peerd-runtime/actor/origin-sensitivity.js +++ b/extension/peerd-runtime/actor/origin-sensitivity.js @@ -74,6 +74,68 @@ const ORDINARY = Object.freeze({ sensitive: false }); */ export const LEARNED_REASONS = Object.freeze(['password-field', 'confirmed-write']); +/** + * The cookie-facing identity of a learned web origin. + * + * Cookies do not isolate ports or schemes, so neither can the learned side of + * the sensitivity classifier. The actor's OWNED origin remains exact elsewhere; + * this host key is only for deciding whether a roaming actor may enter. + * + * @param {unknown} input + * @returns {string | null} + */ +export const sensitivityHost = (input) => { + const origin = normalizeApiOrigin(input); + if (!origin) return null; + try { return new URL(origin).hostname; } catch { return null; } +}; + +/** + * Does a learned origin conservatively cover this landing? + * + * A mark on a parent host covers its descendants because a Domain cookie may + * be sent there. The reverse is deliberately false: seeing a signal on + * login.example.test does not prove that its cookie was scoped to example.test, + * and promoting that observation would let one hostile sibling mark all others. + * + * @param {unknown} learnedOrigin + * @param {unknown} landingOrigin + * @returns {boolean} + */ +export const learnedOriginCovers = (learnedOrigin, landingOrigin) => { + const learnedHost = sensitivityHost(learnedOrigin); + const landingHost = sensitivityHost(landingOrigin); + if (!learnedHost || !landingHost) return false; + return landingHost === learnedHost || landingHost.endsWith(`.${learnedHost}`); +}; + +/** + * Find the closest learned host that covers a landing. Closest wins so a + * directly observed child keeps its own explanation when its parent was also + * learned. The set is bounded by MAX_LEARNED, so a linear scan stays small and + * avoids adding a second policy index that could drift from durable state. + * + * @param {string} origin + * @param {ReadonlySet | ReadonlyMap} learned + * @returns {SensitivityReason | null} + */ +const learnedReasonFor = (origin, learned) => { + /** @type {SensitivityReason | null} */ + let match = null; + let matchLength = -1; + for (const entry of learned.entries()) { + const learnedOrigin = entry[0]; + if (!learnedOriginCovers(learnedOrigin, origin)) continue; + const host = sensitivityHost(learnedOrigin); + if (!host || host.length <= matchLength) continue; + matchLength = host.length; + match = learned instanceof Map + ? /** @type {SensitivityReason} */ (entry[1]) + : 'password-field'; + } + return match; +}; + /** * Classify an origin. * @@ -92,8 +154,10 @@ export const LEARNED_REASONS = Object.freeze(['password-field', 'confirmed-write * @param {(origin: string) => boolean} [deps.hasVaultSecret] is there an `origin:` secret for it * @param {(origin: string) => boolean} [deps.isKnownIdp] is it a dedicated sign-in origin * @param {ReadonlySet | ReadonlyMap} [deps.learned] - * the learned set, keyed by normalized origin. A Map carries WHICH signal - * fired; a Set is accepted so callers with no provenance still work. + * the learned set, keyed by normalized observed origin. Matching follows the + * cookie host scope: scheme and port do not isolate it, and a learned parent + * covers descendant hosts. A Map carries WHICH signal fired; a Set is + * accepted so callers with no provenance still work. * @returns {SensitivityVerdict} */ export const classifyOriginSensitivity = (input, deps = {}) => { @@ -128,14 +192,8 @@ export const classifyOriginSensitivity = (input, deps = {}) => { // LEARNED — grown from ordinary use. if (learned) { - if (learned instanceof Map) { - const reason = learned.get(origin); - if (reason) return { sensitive: true, reason, origin }; - } else if (learned.has(origin)) { - // Provenance wasn't retained; report the weaker of the two rather than - // claiming a signal we can't substantiate. - return { sensitive: true, reason: 'password-field', origin }; - } + const reason = learnedReasonFor(origin, learned); + if (reason) return { sensitive: true, reason, origin }; } return ORDINARY; diff --git a/extension/peerd-runtime/index.js b/extension/peerd-runtime/index.js index 4eeb51e4..1900f804 100644 --- a/extension/peerd-runtime/index.js +++ b/extension/peerd-runtime/index.js @@ -200,7 +200,12 @@ export { // actor above). Two pure cores: which origins the user has an identity on, and // what happens when a tab LANDS somewhere. Exported here because the enforcement // points that will consume them live outside this module (background/). -export { classifyOriginSensitivity, sameOrigin, LEARNED_REASONS } from './actor/origin-sensitivity.js'; +export { + classifyOriginSensitivity, + learnedOriginCovers, + sameOrigin, + LEARNED_REASONS, +} from './actor/origin-sensitivity.js'; export { decideNumericTabAuthority, numericTabAuthorityRefusal, NUMERIC_TAB_SENSITIVE_CODE, NUMERIC_TAB_POLICY_UNAVAILABLE_CODE, diff --git a/extension/sidepanel/components/learned-origins-view.js b/extension/sidepanel/components/learned-origins-view.js index 3e848e00..50afb80d 100644 --- a/extension/sidepanel/components/learned-origins-view.js +++ b/extension/sidepanel/components/learned-origins-view.js @@ -1,5 +1,5 @@ // @ts-check -// Settings → Learned sites — the origins peerd LEARNED the user has an account +// Settings → Learned sites: the hosts peerd LEARNED may carry the user's session // on, and the only place one can be un-learned. // // why its own nav entry, next to the denylist rather than inside it: the two are @@ -9,7 +9,7 @@ // control the user cannot find is the exact problem this view exists to fix. // // The distinction the copy has to carry: un-learning does NOT make a site -// unprotected in general. A learned origin is a guess; the curated shared-doc +// unprotected in general. A learned host is a guess; the curated shared-doc // list and a stored credential are not, and those two keep protecting a site // whatever this list says. Otherwise a user would "clear" a row and reasonably // expect roaming access to a site that still refuses one. @@ -28,7 +28,7 @@ const REASON_COPY = { /** * @typedef {Object} LearnedState - * @property {Array<{ origin: string, reason: string }>|null} origins null = loading + * @property {Array<{ host: string, reason: string }>|null} origins null = loading * @property {{ ok: boolean, text: string }|null} note * @property {string|null} confirm origin with an armed forget confirm * @property {boolean} confirmAll the clear-everything confirm is armed @@ -38,6 +38,17 @@ const REASON_COPY = { /** @typedef {(msg: object) => Promise} Send */ /** @typedef {{ state: LearnedState, attrs: { send: Send } }} LearnedVnode */ +/** Restore keyboard focus after Mithril has replaced an inline control. */ +const focusAfterRender = (/** @type {'heading'|'trigger'|'confirm'|'trigger-all'|'confirm-all'} */ role, /** @type {string|null} */ host = null) => { + requestAnimationFrame(() => { + const controls = [...document.querySelectorAll(`.learned-sites [data-learned-role="${role}"]`)]; + const target = host + ? controls.find((element) => /** @type {HTMLElement} */ (element).dataset.learnedHost === host) + : controls[0]; + /** @type {HTMLElement | undefined} */ (target)?.focus(); + }); +}; + export const LearnedOriginsView = { /** @param {LearnedVnode} vnode */ oninit(vnode) { @@ -51,7 +62,7 @@ export const LearnedOriginsView = { /** @param {LearnedVnode} vnode */ refresh(vnode) { - vnode.attrs.send({ type: 'learned/list' }).then((r) => { + return vnode.attrs.send({ type: 'learned/list' }).then((r) => { vnode.state.origins = r?.ok ? (r.origins ?? []) : (vnode.state.origins ?? []); if (!r?.ok) vnode.state.note = { ok: false, text: r?.error ?? 'failed to load learned sites' }; m.redraw(); @@ -65,7 +76,7 @@ export const LearnedOriginsView = { /** * One mutation round-trip: send, banner the outcome, re-fetch. * @param {LearnedVnode} vnode - * @param {object} msg + * @param {{ type: string, host?: string }} msg * @param {string | ((reply: any) => string)} okText a function when the copy * depends on the reply (Forget all reports how many it actually forgot). */ @@ -73,7 +84,7 @@ export const LearnedOriginsView = { const ui = vnode.state; if (ui.busy) return Promise.resolve(null); ui.busy = true; ui.note = null; m.redraw(); - return vnode.attrs.send(msg).then((r) => { + return vnode.attrs.send(msg).then(async (r) => { ui.busy = false; // `not-learned` means THIS VIEW IS STALE — another tab, or the panel, already // removed the row. So it re-fetches like the success path does: an earlier @@ -86,13 +97,21 @@ export const LearnedOriginsView = { : { ok: false, text: stale ? 'That site was already removed somewhere else — refreshed the list.' : r?.error ?? 'Action failed.' }; - if (r?.ok || stale) { ui.confirm = null; ui.confirmAll = false; LearnedOriginsView.refresh(vnode); } + if (r?.ok || stale) { + ui.confirm = null; + ui.confirmAll = false; + await LearnedOriginsView.refresh(vnode); + } m.redraw(); + if (r?.ok) focusAfterRender('heading'); + else if (stale) focusAfterRender(ui.origins?.length ? 'trigger' : 'heading'); + else focusAfterRender(msg.type === 'learned/clear' ? 'confirm-all' : 'confirm', /** @type {any} */ (msg).host ?? null); return r; }).catch((e) => { ui.busy = false; ui.note = { ok: false, text: /** @type {{ message?: string }} */ (e)?.message ?? 'Action failed.' }; m.redraw(); + focusAfterRender(msg.type === 'learned/clear' ? 'confirm-all' : 'confirm', /** @type {any} */ (msg).host ?? null); return null; }); }, @@ -102,21 +121,25 @@ export const LearnedOriginsView = { const vnode = { state: ui, attrs }; const rows = ui.origins; - return m('div', [ - m('h3', 'Sites peerd thinks you have an account on'), - m('p', 'peerd learns these from ordinary use, and a helper that browses the ' - + 'open web is not allowed onto them - it hands the work to a helper bound to ' - + 'that one site instead. Remove a site here if the guess was wrong.'), - m('p.hint', 'Removing a site does not make it unprotected: a site on peerd’s ' + return m('div.learned-sites', [ + m('h3', { tabindex: -1, 'data-learned-role': 'heading' }, 'Hosts that may share your browser session'), + m('p', 'peerd learns these from ordinary use. A helper that browses the open web ' + + 'is not allowed onto them. It hands the work to a helper bound to the exact ' + + 'site instead. Remove a host here if the guess was wrong.'), + m('p.hint', 'Each entry covers this host on every port and its subdomains.'), + m('p.hint', 'Removing a host does not make it unprotected: a site on peerd’s ' + 'built-in shared-document list, or one you have stored a key for, stays ' - + 'protected either way. peerd can also learn the same site again next time ' + + 'protected either way. peerd can also learn the same host again next time ' + 'it reads a sign-in form there.'), // `p.key-msg.ok` / `.err` — the same banner DenylistView and HooksView use. // (A bare `.ok` has no rule in either stylesheet this page links, so the // success case rendered as ordinary body text, indistinguishable from the // explanatory copy above it.) - ui.note ? m(`p.key-msg${ui.note.ok ? '.ok' : '.err'}`, ui.note.text) : null, + ui.note ? m(`p.key-msg${ui.note.ok ? '.ok' : '.err'}`, { + role: ui.note.ok ? 'status' : 'alert', + 'aria-live': ui.note.ok ? 'polite' : 'assertive', + }, ui.note.text) : null, rows === null ? m('p.muted', 'Loading…') @@ -130,36 +153,49 @@ export const LearnedOriginsView = { // why no keys on the rows: keyed and unkeyed siblings cannot be // mixed — Mithril throws on the mix, and during development that // threw mid-redraw and stranded the whole block on "Loading…" while - // the SW already held two learned origins. The list is re-fetched + // the SW already held two learned hosts. The list is re-fetched // wholesale after every mutation, so keys would buy nothing here. - m('div', rows.map(({ origin, reason }) => m('div', { + m('div', rows.map(({ host, reason }) => m('div', { style: 'display:flex; align-items:center; gap:8px; padding:6px 0; flex-wrap:wrap;', }, [ m('span', { style: 'flex:1; min-width:220px;' }, [ - m('code', origin), + m('code', host), m('span.hint', { style: 'display:block;' }, REASON_COPY[/** @type {keyof REASON_COPY} */ (reason)] ?? reason), ]), - ui.confirm === origin + ui.confirm === host ? m('span', { style: 'display:flex; gap:6px; align-items:center;' }, [ - m('span.hint', 'Stop treating this as yours?'), + m('span.hint', 'Remove this learned host?'), m('button.secondary', { type: 'button', disabled: ui.busy, + 'data-learned-role': 'confirm', + 'data-learned-host': host, + 'aria-label': `Confirm removal of learned host ${host}`, onclick: () => LearnedOriginsView.act( - vnode, { type: 'learned/forget', origin }, `Removed ${origin}.`, + vnode, { type: 'learned/forget', host }, `Removed ${host}.`, ), }, ui.busy ? '…' : 'Remove'), m('button.secondary', { type: 'button', disabled: ui.busy, - onclick: () => { ui.confirm = null; m.redraw(); }, + 'aria-label': `Keep learned host ${host}`, + onclick: () => { + ui.confirm = null; m.redraw(); + focusAfterRender('trigger', host); + }, }, 'Keep'), ]) : m('button.secondary', { type: 'button', disabled: ui.busy, - onclick: () => { ui.confirm = origin; ui.note = null; m.redraw(); }, + 'data-learned-role': 'trigger', + 'data-learned-host': host, + 'aria-label': `Remove learned host ${host}`, + onclick: () => { + ui.confirm = host; ui.note = null; m.redraw(); + focusAfterRender('confirm', host); + }, }, 'Remove'), ]))), m('div', { style: 'margin-top:12px;' }, [ @@ -169,6 +205,8 @@ export const LearnedOriginsView = { m('button.secondary', { type: 'button', disabled: ui.busy, + 'data-learned-role': 'confirm-all', + 'aria-label': 'Confirm forgetting all learned hosts', onclick: () => LearnedOriginsView.act( vnode, { type: 'learned/clear' }, // Report what it actually forgot: "cleared" alone cannot be @@ -179,13 +217,20 @@ export const LearnedOriginsView = { m('button.secondary', { type: 'button', disabled: ui.busy, - onclick: () => { ui.confirmAll = false; m.redraw(); }, + onclick: () => { + ui.confirmAll = false; m.redraw(); + focusAfterRender('trigger-all'); + }, }, 'Cancel'), ]) : m('button.secondary', { type: 'button', disabled: ui.busy, - onclick: () => { ui.confirmAll = true; ui.note = null; m.redraw(); }, + 'data-learned-role': 'trigger-all', + onclick: () => { + ui.confirmAll = true; ui.note = null; m.redraw(); + focusAfterRender('confirm-all'); + }, }, 'Forget all'), ]), ]), diff --git a/extension/tests/unit/options/activity-origin-events.test.js b/extension/tests/unit/options/activity-origin-events.test.js index 2d94e6d6..03a53a54 100644 --- a/extension/tests/unit/options/activity-origin-events.test.js +++ b/extension/tests/unit/options/activity-origin-events.test.js @@ -25,16 +25,16 @@ const mount = (entries) => { const settle = () => new Promise((r) => setTimeout(r, 0)).then(() => m.redraw.sync?.() ?? m.redraw()); describe('options.activity — origin-lock events', () => { - it('labels a learned origin and names the site', async () => { + it('labels a learned host and names its protection scope', async () => { const { root, unmount } = mount([ - { id: '1', when: 1, type: 'origin_learned_sensitive', details: { origin: 'https://bank.test', reason: 'password-field' } }, + { id: '1', when: 1, type: 'origin_learned_sensitive', details: { host: 'bank.test', reason: 'password-field' } }, ]); try { await settle(); const text = root.textContent ?? ''; expect(text.includes('origin_learned_sensitive')).toBe(false); // no raw slug - expect(text.includes('site treated as yours')).toBe(true); - expect(text.includes('https://bank.test')).toBe(true); // WHICH site + expect(text.includes('host may share browser session')).toBe(true); + expect(text.includes('bank.test')).toBe(true); // WHICH host expect(text.includes('password-field')).toBe(true); // and why } finally { unmount(); } }); @@ -56,13 +56,13 @@ describe('options.activity — origin-lock events', () => { // Removing a protection is the noisier event - same posture as the denylist // rows, where disabling a built-in pattern is a warn. const { root, unmount } = mount([ - { id: '3', when: 3, type: 'origin_unlearned_sensitive', details: { origin: 'https://shop.test' } }, + { id: '3', when: 3, type: 'origin_unlearned_sensitive', details: { host: 'shop.test' } }, ]); try { await settle(); const text = root.textContent ?? ''; - expect(text.includes('site no longer yours')).toBe(true); - expect(text.includes('https://shop.test')).toBe(true); + expect(text.includes('learned host removed')).toBe(true); + expect(text.includes('shop.test')).toBe(true); } finally { unmount(); } }); diff --git a/extension/tests/unit/sidepanel/learned-origins-view.test.js b/extension/tests/unit/sidepanel/learned-origins-view.test.js index 518b3ce4..af480b02 100644 --- a/extension/tests/unit/sidepanel/learned-origins-view.test.js +++ b/extension/tests/unit/sidepanel/learned-origins-view.test.js @@ -17,8 +17,8 @@ import { describe, it, expect } from '../../framework.js'; import { LearnedOriginsView } from '/sidepanel/components/learned-origins-view.js'; const ORIGINS = [ - { origin: 'https://acme.test', reason: 'password-field' }, - { origin: 'https://globex.test', reason: 'confirmed-write' }, + { host: 'acme.test', reason: 'password-field' }, + { host: 'globex.test', reason: 'confirmed-write' }, ]; /** @@ -42,19 +42,24 @@ const mount = (reply = () => ({ ok: true, origins: ORIGINS })) => { /** Let the mounted component's pending promise + redraw settle. */ const settle = () => new Promise((r) => setTimeout(r, 0)).then(() => m.redraw.sync?.() ?? m.redraw()); +/** Let focus restoration scheduled for the next painted frame complete. */ +const settleFocus = () => settle().then(() => new Promise(requestAnimationFrame)); + /** @param {HTMLElement} root @param {string} label */ const buttons = (root, label) => [...root.querySelectorAll('button')] .filter((b) => (b.textContent ?? '').trim() === label); describe('sidepanel.learned-origins view', () => { - it('lists every learned origin with a plain-language reason', async () => { + it('lists every learned host with its scope and a plain-language reason', async () => { const { root, sent, unmount } = mount(); try { await settle(); expect(sent[0].type).toBe('learned/list'); const text = root.textContent ?? ''; - expect(text.includes('https://acme.test')).toBe(true); - expect(text.includes('https://globex.test')).toBe(true); + expect(text.includes('acme.test')).toBe(true); + expect(text.includes('globex.test')).toBe(true); + expect(text.includes('may share your browser session')).toBe(true); + expect(text.includes('every port and its subdomains')).toBe(true); // The reason is why the site is treated as theirs — the raw enum would // leave a user with no way to judge whether the guess was wrong. expect(text.includes('a sign-in form was on a page peerd read')).toBe(true); @@ -77,14 +82,16 @@ describe('sidepanel.learned-origins view', () => { try { await settle(); buttons(root, 'Remove')[0].click(); - await settle(); - expect((root.textContent ?? '').includes('Stop treating this as yours?')).toBe(true); + await settleFocus(); + expect((root.textContent ?? '').includes('Remove this learned host?')).toBe(true); + expect(document.activeElement?.getAttribute('data-learned-role')).toBe('confirm'); + expect(document.activeElement?.getAttribute('aria-label')).toBe('Confirm removal of learned host acme.test'); // Still only the initial list call: nothing was un-learned by arming. expect(sent.filter((s) => s.type === 'learned/forget').length).toBe(0); } finally { unmount(); } }); - it('confirming Remove sends learned/forget for THAT row’s origin', async () => { + it('confirming Remove sends learned/forget for that row host', async () => { const { root, sent, unmount } = mount(); try { await settle(); @@ -94,7 +101,7 @@ describe('sidepanel.learned-origins view', () => { await settle(); const forget = sent.find((s) => s.type === 'learned/forget'); expect(!!forget).toBe(true); - expect(forget.origin).toBe('https://acme.test'); + expect(forget.host).toBe('acme.test'); // And it re-reads the list rather than trusting a local edit. expect(sent.filter((s) => s.type === 'learned/list').length).toBe(2); } finally { unmount(); } @@ -107,8 +114,10 @@ describe('sidepanel.learned-origins view', () => { buttons(root, 'Remove')[0].click(); await settle(); buttons(root, 'Keep')[0].click(); - await settle(); - expect((root.textContent ?? '').includes('Stop treating this as yours?')).toBe(false); + await settleFocus(); + expect((root.textContent ?? '').includes('Remove this learned host?')).toBe(false); + expect(document.activeElement?.getAttribute('data-learned-role')).toBe('trigger'); + expect(document.activeElement?.getAttribute('aria-label')).toBe('Remove learned host acme.test'); expect(sent.filter((s) => s.type === 'learned/forget').length).toBe(0); } finally { unmount(); } }); @@ -118,8 +127,10 @@ describe('sidepanel.learned-origins view', () => { try { await settle(); buttons(root, 'Forget all')[0].click(); // arm - await settle(); + await settleFocus(); expect((root.textContent ?? '').includes('learned sites?')).toBe(true); + expect(document.activeElement?.getAttribute('data-learned-role')).toBe('confirm-all'); + expect(document.activeElement?.getAttribute('aria-label')).toBe('Confirm forgetting all learned hosts'); expect(sent.filter((s) => s.type === 'learned/clear').length).toBe(0); buttons(root, 'Forget all')[0].click(); // confirm await settle(); @@ -134,10 +145,11 @@ describe('sidepanel.learned-origins view', () => { // message saying it was gone. /** @type {string[]} */ const sentTypes = []; + let listCalls = 0; const { root, unmount } = mount((msg) => { sentTypes.push(msg.type); return msg.type === 'learned/list' - ? { ok: true, origins: ORIGINS } + ? { ok: true, origins: (listCalls++ === 0) ? ORIGINS : ORIGINS.slice(1) } : { ok: false, error: 'not-learned' }; }); try { @@ -145,13 +157,14 @@ describe('sidepanel.learned-origins view', () => { buttons(root, 'Remove')[0].click(); await settle(); buttons(root, 'Remove')[0].click(); - await settle(); + await settleFocus(); const text = root.textContent ?? ''; expect(text.includes('already removed somewhere else')).toBe(true); // It re-read the list (2 list calls: mount + after the stale reply)... expect(sentTypes.filter((t) => t === 'learned/list').length).toBe(2); // ...and the armed confirm is gone rather than left hanging. - expect(text.includes('Stop treating this as yours?')).toBe(false); + expect(text.includes('Remove this learned host?')).toBe(false); + expect(document.activeElement?.getAttribute('data-learned-host')).toBe('globex.test'); } finally { unmount(); } }); @@ -171,11 +184,13 @@ describe('sidepanel.learned-origins view', () => { buttons(root, 'Remove')[0].click(); await settle(); buttons(root, 'Remove')[0].click(); - await settle(); + await settleFocus(); const text = root.textContent ?? ''; expect(text.includes('invalid-origin')).toBe(true); + expect(root.querySelector('[role="alert"]')?.textContent).toBe('invalid-origin'); + expect(document.activeElement?.getAttribute('data-learned-role')).toBe('confirm'); expect(sentTypes.filter((t) => t === 'learned/list').length).toBe(1); - expect(text.includes('Stop treating this as yours?')).toBe(true); + expect(text.includes('Remove this learned host?')).toBe(true); } finally { unmount(); } }); @@ -188,8 +203,10 @@ describe('sidepanel.learned-origins view', () => { buttons(root, 'Forget all')[0].click(); await settle(); buttons(root, 'Forget all')[0].click(); - await settle(); + await settleFocus(); expect((root.textContent ?? '').includes('Forgot 2 learned sites.')).toBe(true); + expect(root.querySelector('[role="status"]')?.textContent).toBe('Forgot 2 learned sites.'); + expect(document.activeElement?.getAttribute('data-learned-role')).toBe('heading'); } finally { unmount(); } }); diff --git a/scripts/cdp/states.mjs b/scripts/cdp/states.mjs index d5e11890..972bcfc0 100644 --- a/scripts/cdp/states.mjs +++ b/scripts/cdp/states.mjs @@ -1539,6 +1539,21 @@ export const STATES = [ }); rec.check('the browser probe seeded the production learned-origin policy', seeded?.learned === true, JSON.stringify(seeded)); + const alternatePort = await rpc(ctx.page, { + type: 'debug/originLock', origin: 'https://acct.peerd.test:9443', + }); + const descendant = await rpc(ctx.page, { + type: 'debug/originLock', origin: 'https://child.acct.peerd.test', + }); + const sibling = await rpc(ctx.page, { + type: 'debug/originLock', origin: 'https://other.peerd.test', + }); + rec.check('Chrome applies a learned host across schemes and ports', + alternatePort?.learned === true, JSON.stringify(alternatePort)); + rec.check('Chrome applies a learned parent host to descendants', + descendant?.learned === true, JSON.stringify(descendant)); + rec.check('Chrome does not spread a learned host to siblings', + sibling?.learned === false, JSON.stringify(sibling)); const loginTabId = await evalIn(ctx.page, `(async () => { const browser = (await import('/vendor/browser-polyfill.js')).default; const tab = await browser.tabs.create({ active: false, url: ${JSON.stringify(`${accountOrigin}/login`)} }); @@ -2843,6 +2858,56 @@ export const STATES = [ } finally { try { page.close(); } catch { /* */ } } }, }, + { + name: 'options-learned-sites', kind: 'visual', phase: 'post-unlock', + responder: null, + async run(ctx, rec) { + let priorEntries = null; + let page; + try { + const prior = await rpc(ctx.page, { type: 'learned/list' }); + if (!prior?.ok || !Array.isArray(prior.origins)) { + throw new Error(`could not snapshot learned hosts: ${prior?.error ?? 'unknown error'}`); + } + priorEntries = prior.origins; + await rpc(ctx.page, { type: 'learned/clear' }); + await rpc(ctx.page, { type: 'settings/update', patch: { devMode: true } }); + await rpc(ctx.page, { + type: 'debug/originLock', + origin: 'https://accounts.acme.test:8443', + seedReason: 'password-field', + }); + await rpc(ctx.page, { + type: 'debug/originLock', + origin: 'http://portal.globex.test:9080', + seedReason: 'confirmed-write', + }); + await rpc(ctx.page, { type: 'settings/update', patch: { devMode: false } }); + + page = await openWidePage(ctx, 'options/options.html#!/learned-sites'); + await waitFor(() => evalIn(page, `(() => { + const text = document.body.innerText; + return text.includes('accounts.acme.test') + && text.includes('portal.globex.test') + && text.includes('every port and its subdomains'); + })()`), { budgetMs: 15_000, pollMs: 80 }); + await rec.visualPage('options-learned-sites', page); + } finally { + await rpc(ctx.page, { type: 'settings/update', patch: { devMode: false } }).catch(() => {}); + if (priorEntries) { + await rpc(ctx.page, { type: 'learned/clear' }).catch(() => {}); + await rpc(ctx.page, { type: 'settings/update', patch: { devMode: true } }).catch(() => {}); + for (const { host, reason } of priorEntries) { + await rpc(ctx.page, { + type: 'debug/originLock', origin: `https://${host}`, seedReason: reason, + }).catch(() => {}); + } + await rpc(ctx.page, { type: 'settings/update', patch: { devMode: false } }).catch(() => {}); + } + try { page?.close(); } catch { /* */ } + } + }, + }, { name: 'options-contributor-metrics', kind: 'visual', phase: 'post-unlock', responder: null, diff --git a/scripts/firefox/run-runtime-tests.mjs b/scripts/firefox/run-runtime-tests.mjs index 8c6e4084..2f40aac4 100644 --- a/scripts/firefox/run-runtime-tests.mjs +++ b/scripts/firefox/run-runtime-tests.mjs @@ -1142,11 +1142,28 @@ const runNumericTabAuthoritySmoke = async (driver, providerServer) => { const lock = await browser.runtime.sendMessage({ type: 'debug/originLock', origin: fixtureOrigin, seedReason: 'password-field', }); + const alternatePort = await browser.runtime.sendMessage({ + type: 'debug/originLock', origin: 'https://${DNR_PUBLIC_HOST}:9443', + }); + const descendant = await browser.runtime.sendMessage({ + type: 'debug/originLock', origin: 'https://child.${DNR_PUBLIC_HOST}', + }); + const sibling = await browser.runtime.sendMessage({ + type: 'debug/originLock', origin: 'https://other.test', + }); const tab = await browser.tabs.create({ url: fixtureUrl, active: false }); for (let attempt = 0; attempt < 200; attempt += 1) { const live = await browser.tabs.get(tab.id).catch(() => null); if (live?.url === fixtureUrl) { - return { settingsOk: settings?.ok === true, lock, tabId: tab.id, url: live.url }; + return { + settingsOk: settings?.ok === true, + lock, + alternatePort, + descendant, + sibling, + tabId: tab.id, + url: live.url, + }; } await new Promise((resolveWait) => setTimeout(resolveWait, 25)); } @@ -1160,6 +1177,12 @@ const runNumericTabAuthoritySmoke = async (driver, providerServer) => { && initialized?.url === fixtureUrl, 'Firefox prepares a live tab whose origin has a learned sensitive signal', JSON.stringify(initialized)); + assert(initialized?.alternatePort?.learned === true, + 'Firefox applies a learned host across schemes and ports', JSON.stringify(initialized)); + assert(initialized?.descendant?.learned === true, + 'Firefox applies a learned parent host to descendants', JSON.stringify(initialized)); + assert(initialized?.sibling?.learned === false, + 'Firefox does not spread a learned host to siblings', JSON.stringify(initialized)); providerServer.setScenario({ mode: 'numeric-tab-authority', actorTarget: String(fixtureTabId) }); const started = await driver.executeAsync(` diff --git a/tests/background/routes-learned-origins.test.ts b/tests/background/routes-learned-origins.test.ts index f6c94cc7..1b682815 100644 --- a/tests/background/routes-learned-origins.test.ts +++ b/tests/background/routes-learned-origins.test.ts @@ -1,8 +1,8 @@ import { describe, test, expect } from 'bun:test'; import { makeLearnedOriginRoutes } from '../../extension/background/routes/learned-origins.js'; -// The settings view of the learned origin set, and the only un-learn path. What -// these pin: the origin is CANONICALIZED with the same normalizer `note` uses (a +// The settings view of the learned host set, and the only un-learn path. What +// these pin: the host is CANONICALIZED with the same normalizer `note` uses (a // mismatch would silently no-op and read as a broken button), a removal is not // reported until it is durable, and the routes never audit themselves (the // store's onForget hook owns that, or every removal is recorded twice). @@ -16,8 +16,8 @@ const makeDeps = (learned: Record = {}, over: any = {}) => { // at the bottom); a stand-in that omits it is not the real interface. hydrate: async () => {}, entries: () => [...map.entries()] - .map(([origin, reason]) => ({ origin, reason })) - .sort((a, b) => a.origin.localeCompare(b.origin)), + .map(([host, reason]) => ({ host, reason })) + .sort((a, b) => a.host.localeCompare(b.host)), forget: (o: string) => map.delete(o), clear: () => { const n = map.size; map.clear(); return n; }, settled: async () => { settledCalls.push(map.size); }, @@ -26,7 +26,7 @@ const makeDeps = (learned: Record = {}, over: any = {}) => { // Stands in for the SW's normalizeApiOrigin: canonical origin or null. normalizeApiOrigin: (raw: unknown) => { if (typeof raw !== 'string' || !raw) return null; - try { return new URL(raw).origin; } catch { return null; } + try { return new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`).origin; } catch { return null; } }, ...over, }; @@ -35,12 +35,12 @@ const makeDeps = (learned: Record = {}, over: any = {}) => { describe('learned/list', () => { test('returns the sorted set', async () => { - const { deps } = makeDeps({ 'https://b.test': 'password-field', 'https://a.test': 'confirmed-write' }); + const { deps } = makeDeps({ 'b.test': 'password-field', 'a.test': 'confirmed-write' }); expect(await makeLearnedOriginRoutes(deps)['learned/list']()).toEqual({ ok: true, origins: [ - { origin: 'https://a.test', reason: 'confirmed-write' }, - { origin: 'https://b.test', reason: 'password-field' }, + { host: 'a.test', reason: 'confirmed-write' }, + { host: 'b.test', reason: 'password-field' }, ], }); }); @@ -53,25 +53,25 @@ describe('learned/list', () => { describe('learned/forget', () => { test('canonicalizes the origin before deleting', async () => { - // The UI renders `https://app.test`; a caller passing a URL with a path (or a - // trailing slash) must still hit the same key the classifier looks up. - const { deps, map } = makeDeps({ 'https://app.test': 'password-field' }); - const r = await makeLearnedOriginRoutes(deps)['learned/forget']({ origin: 'https://app.test/login?x=1' }); + // The UI renders `app.test`; a caller passing an old origin-shaped value + // must still hit the same host key the classifier looks up. + const { deps, map } = makeDeps({ 'app.test': 'password-field' }); + const r = await makeLearnedOriginRoutes(deps)['learned/forget']({ host: 'https://app.test:8443/login?x=1' }); expect(r.ok).toBe(true); - expect(map.has('https://app.test')).toBe(false); + expect(map.has('app.test')).toBe(false); }); test('refuses a non-origin instead of no-oping silently', async () => { - const { deps } = makeDeps({ 'https://app.test': 'password-field' }); + const { deps } = makeDeps({ 'app.test': 'password-field' }); for (const bad of ['', 'not a url', null, undefined, 42]) { - expect(await makeLearnedOriginRoutes(deps)['learned/forget']({ origin: bad })) + expect(await makeLearnedOriginRoutes(deps)['learned/forget']({ host: bad })) .toEqual({ ok: false, error: 'invalid-origin' }); } }); test('reports not-learned for an origin that was never in the set', async () => { - const { deps } = makeDeps({ 'https://app.test': 'password-field' }); - expect(await makeLearnedOriginRoutes(deps)['learned/forget']({ origin: 'https://other.test' })) + const { deps } = makeDeps({ 'app.test': 'password-field' }); + expect(await makeLearnedOriginRoutes(deps)['learned/forget']({ host: 'other.test' })) .toEqual({ ok: false, error: 'not-learned' }); }); @@ -79,7 +79,7 @@ describe('learned/forget', () => { // The caller re-renders from this reply. Replying early would show a row gone // that a mid-flight service-worker eviction could still bring back. const order: string[] = []; - const { deps } = makeDeps({ 'https://app.test': 'password-field' }, { + const { deps } = makeDeps({ 'app.test': 'password-field' }, { learnedOrigins: { hydrate: async () => { order.push('hydrate'); }, entries: () => { order.push('entries'); return []; }, @@ -88,23 +88,23 @@ describe('learned/forget', () => { settled: async () => { order.push('settled'); }, }, }); - await makeLearnedOriginRoutes(deps)['learned/forget']({ origin: 'https://app.test' }); + await makeLearnedOriginRoutes(deps)['learned/forget']({ host: 'app.test' }); expect(order).toEqual(['hydrate', 'forget', 'settled', 'entries']); }); test('does not audit — the store hook owns that, so a removal is recorded once', async () => { const audits: any[] = []; - const { deps } = makeDeps({ 'https://app.test': 'password-field' }, { + const { deps } = makeDeps({ 'app.test': 'password-field' }, { auditLog: { append: async (e: any) => { audits.push(e); } }, }); - await makeLearnedOriginRoutes(deps)['learned/forget']({ origin: 'https://app.test' }); + await makeLearnedOriginRoutes(deps)['learned/forget']({ host: 'app.test' }); expect(audits).toEqual([]); }); }); describe('learned/clear', () => { test('empties the set and reports how many it forgot', async () => { - const { deps, map } = makeDeps({ 'https://a.test': 'password-field', 'https://b.test': 'confirmed-write' }); + const { deps, map } = makeDeps({ 'a.test': 'password-field', 'b.test': 'confirmed-write' }); const r = await makeLearnedOriginRoutes(deps)['learned/clear'](); expect(r).toEqual({ ok: true, origins: [], forgotten: 2 }); expect(map.size).toBe(0); @@ -125,7 +125,7 @@ describe('learned/clear', () => { // seam BETWEEN them: the SW kicks learnedOrigins.hydrate() at boot without // awaiting it, and a settings message is exactly what wakes a cold worker. Before // the routes awaited hydration, `learned/list` reported an empty list for a -// profile full of learned origins and `learned/clear` reported success while +// profile full of learned hosts and `learned/clear` reported success while // forgetting nothing — the boot read then restored everything. import { makeLearnedOrigins } from '../../extension/peerd-runtime/actor/learned-origins.js'; @@ -148,7 +148,7 @@ const routesFor = (store: any) => makeLearnedOriginRoutes({ learnedOrigins: store, normalizeApiOrigin: (raw: unknown) => { if (typeof raw !== 'string' || !raw) return null; - try { return new URL(raw).origin; } catch { return null; } + try { return new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`).origin; } catch { return null; } }, }); @@ -157,7 +157,7 @@ describe('routes wait for the durable set', () => { const { store, release } = coldStore({ 'https://a.test': 'password-field' }); const pending = routesFor(store)['learned/list'](); release(null); - expect(await pending).toEqual({ ok: true, origins: [{ origin: 'https://a.test', reason: 'password-field' }] }); + expect(await pending).toEqual({ ok: true, origins: [{ host: 'a.test', reason: 'password-field' }] }); }); test('learned/clear on a cold worker forgets what is really there, and says how many', async () => { @@ -178,11 +178,11 @@ describe('routes wait for the durable set', () => { test('learned/forget on a cold worker removes the row instead of reporting not-learned', async () => { const { store, release, booting } = coldStore({ 'https://a.test': 'password-field' }); - const pending = routesFor(store)['learned/forget']({ origin: 'https://a.test' }); + const pending = routesFor(store)['learned/forget']({ host: 'a.test' }); release(null); expect((await pending).ok).toBe(true); await booting; - expect(store.snapshot().has('https://a.test')).toBe(false); + expect(store.snapshot().has('a.test')).toBe(false); }); test('the boot read is shared, not re-run per route call', async () => { diff --git a/tests/peerd-runtime/actor/learned-origins.test.ts b/tests/peerd-runtime/actor/learned-origins.test.ts index 3b815cec..d95aa9f5 100644 --- a/tests/peerd-runtime/actor/learned-origins.test.ts +++ b/tests/peerd-runtime/actor/learned-origins.test.ts @@ -33,7 +33,7 @@ describe('learning', () => { const { store } = harness(); await store.hydrate(); expect(store.note('https://app.test', 'password-field')).toBe(true); - expect(store.snapshot().get('https://app.test')).toBe('password-field'); + expect(store.snapshot().get('app.test')).toBe('password-field'); }); test('the FIRST reason sticks', async () => { @@ -44,7 +44,7 @@ describe('learning', () => { await store.hydrate(); store.note('https://app.test', 'password-field'); expect(store.note('https://app.test', 'confirmed-write')).toBe(false); - expect(store.snapshot().get('https://app.test')).toBe('password-field'); + expect(store.snapshot().get('app.test')).toBe('password-field'); }); test('onLearn fires once per origin, never on a repeat', async () => { @@ -52,7 +52,17 @@ describe('learning', () => { await store.hydrate(); store.note('https://app.test', 'password-field'); store.note('https://app.test', 'password-field'); - expect(learns).toEqual([['https://app.test', 'password-field']]); + expect(learns).toEqual([['app.test', 'password-field']]); + }); + + test('scheme and port variants share one host record and one audit event', async () => { + const { store, learns } = harness(); + await store.hydrate(); + expect(store.note('https://app.test:8443', 'password-field')).toBe(true); + expect(store.note('http://app.test:8080', 'confirmed-write')).toBe(false); + expect(store.note('app.test', 'confirmed-write')).toBe(false); + expect(store.entries()).toEqual([{ host: 'app.test', reason: 'password-field' }]); + expect(learns).toEqual([['app.test', 'password-field']]); }); test('an unknown reason is refused', async () => { @@ -79,7 +89,7 @@ describe('persistence', () => { test('a stored set is restored', async () => { const { store } = harness({ 'https://app.test': 'confirmed-write' }); await store.hydrate(); - expect(store.snapshot().get('https://app.test')).toBe('confirmed-write'); + expect(store.snapshot().get('app.test')).toBe('confirmed-write'); expect(store.hydrationStatus()).toEqual({ ready: true, ok: true }); }); @@ -112,7 +122,7 @@ describe('persistence', () => { await store.hydrate(); store.note('https://app.test', 'password-field'); await store.settled(); - expect(store.snapshot().get('https://app.test')).toBe('password-field'); + expect(store.snapshot().get('app.test')).toBe('password-field'); }); test('each save is the WHOLE set, so a partial write cannot lose an origin', async () => { @@ -121,7 +131,39 @@ describe('persistence', () => { store.note('https://a.test', 'password-field'); store.note('https://b.test', 'confirmed-write'); await store.settled(); - expect(saves.at(-1)).toEqual({ 'https://a.test': 'password-field', 'https://b.test': 'confirmed-write' }); + expect(saves.at(-1)).toEqual({ 'a.test': 'password-field', 'b.test': 'confirmed-write' }); + }); + + test('legacy origin keys migrate and duplicate ports collapse on hydrate', async () => { + const { store, saves } = harness({ + 'https://app.test:8443': 'password-field', + 'http://app.test:8080': 'confirmed-write', + 'https://other.test': 'confirmed-write', + }); + await store.hydrate(); + await store.settled(); + expect(store.entries()).toEqual([ + { host: 'app.test', reason: 'password-field' }, + { host: 'other.test', reason: 'confirmed-write' }, + ]); + expect(saves.at(-1)).toEqual({ + 'app.test': 'password-field', + 'other.test': 'confirmed-write', + }); + }); + + test('oversized durable state is capped and compacted on hydrate', async () => { + const stored = Object.fromEntries(Array.from( + { length: MAX_LEARNED + 25 }, + (_, index) => [`stored-${index}.test`, 'password-field'], + )); + const { store, saves } = harness(stored); + await store.hydrate(); + await store.settled(); + expect(store.size()).toBe(MAX_LEARNED); + expect(Object.keys(saves.at(-1) ?? {})).toHaveLength(MAX_LEARNED); + expect(saves.at(-1)?.['stored-0.test']).toBe('password-field'); + expect(saves.at(-1)?.[`stored-${MAX_LEARNED}.test`]).toBeUndefined(); }); }); @@ -136,9 +178,19 @@ describe('the cap', () => { expect(store.size()).toBe(MAX_LEARNED); expect(store.note('https://one-too-many.test', 'password-field')).toBe(false); // The very first origin is still there — nothing was traded away for it. - expect(store.snapshot().get('https://s0.test')).toBe('password-field'); + expect(store.snapshot().get('s0.test')).toBe('password-field'); expect(store.size()).toBe(MAX_LEARNED); }); + + test('port spelling cannot fill the cap', async () => { + const { store } = harness(); + await store.hydrate(); + for (let port = 1; port <= MAX_LEARNED; port += 1) { + store.note(`https://same.test:${port}`, 'password-field'); + } + expect(store.size()).toBe(1); + expect(store.note('https://another.test', 'password-field')).toBe(true); + }); }); describe('the boot read races the first page walk', () => { @@ -160,12 +212,12 @@ describe('the boot read races the first page walk', () => { await hydrating; await store.settled(); // Both survive. - expect(store.snapshot().get('https://live.test')).toBe('password-field'); - expect(store.snapshot().get('https://stored.test')).toBe('confirmed-write'); + expect(store.snapshot().get('live.test')).toBe('password-field'); + expect(store.snapshot().get('stored.test')).toBe('confirmed-write'); // And the LAST durable write holds both, not the racing writer's view. expect(saves.at(-1)).toEqual({ - 'https://live.test': 'password-field', - 'https://stored.test': 'confirmed-write', + 'live.test': 'password-field', + 'stored.test': 'confirmed-write', }); }); @@ -181,7 +233,34 @@ describe('the boot read races the first page walk', () => { store.note('https://x.test', 'password-field'); releaseLoad(null); await hydrating; - expect(store.snapshot().get('https://x.test')).toBe('password-field'); + expect(store.snapshot().get('x.test')).toBe('password-field'); + }); + + test('live observations survive when stored entries fill the remaining cap', async () => { + const stored = Object.fromEntries(Array.from( + { length: MAX_LEARNED }, + (_, index) => [`stored-${index}.test`, 'confirmed-write'], + )); + let releaseLoad: (value: Record) => void = () => {}; + const loadGate = new Promise>((resolve) => { releaseLoad = resolve; }); + const saves: Array> = []; + const store = makeLearnedOrigins({ + load: async () => loadGate, + save: async (all) => { saves.push({ ...all }); }, + onError: () => {}, + }); + const hydrating = store.hydrate(); + expect(store.note('https://live-a.test', 'password-field')).toBe(true); + expect(store.note('https://live-b.test', 'confirmed-write')).toBe(true); + releaseLoad(stored); + await hydrating; + await store.settled(); + expect(store.size()).toBe(MAX_LEARNED); + expect(store.snapshot().get('live-a.test')).toBe('password-field'); + expect(store.snapshot().get('live-b.test')).toBe('confirmed-write'); + expect(Object.keys(saves.at(-1) ?? {})).toHaveLength(MAX_LEARNED); + expect(saves.at(-1)?.['live-a.test']).toBe('password-field'); + expect(saves.at(-1)?.['live-b.test']).toBe('confirmed-write'); }); }); @@ -203,7 +282,15 @@ describe('un-learning (user-initiated only)', () => { await store.hydrate(); store.note('https://app.test', 'password-field'); expect(store.forget('https://app.test')).toBe(true); - expect(store.snapshot().has('https://app.test')).toBe(false); + expect(store.snapshot().has('app.test')).toBe(false); + }); + + test('forget accepts another scheme and port spelling of the learned host', async () => { + const { store, forgets } = forgetHarness({ 'https://app.test:8443': 'password-field' }); + await store.hydrate(); + expect(store.forget('http://app.test:8080/path')).toBe(true); + expect(store.size()).toBe(0); + expect(forgets).toEqual([['app.test']]); }); test('forget persists — the removal survives the next boot read', async () => { @@ -220,7 +307,7 @@ describe('un-learning (user-initiated only)', () => { expect(store.forget('https://nope.test')).toBe(false); // never learned expect(store.forget('https://app.test')).toBe(true); expect(store.forget('https://app.test')).toBe(false); // already gone - expect(forgets).toEqual([['https://app.test']]); + expect(forgets).toEqual([['app.test']]); }); test('forget ignores junk rather than throwing', async () => { @@ -240,7 +327,7 @@ describe('un-learning (user-initiated only)', () => { expect(store.clear()).toBe(2); expect(store.size()).toBe(0); expect(store.clear()).toBe(0); // idempotent - expect(forgets).toEqual([['https://a.test', 'https://b.test']]); + expect(forgets).toEqual([['a.test', 'b.test']]); }); // THE RACE THAT MATTERS. hydrate() MERGES (it must, so a signal landing during @@ -260,7 +347,7 @@ describe('un-learning (user-initiated only)', () => { expect(store.forget('https://x.test')).toBe(true); releaseLoad(null); await hydrating; - expect(store.snapshot().has('https://x.test')).toBe(false); + expect(store.snapshot().has('x.test')).toBe(false); }); test('a clear that races the boot read also suppresses rows this heap never saw', async () => { @@ -286,7 +373,7 @@ describe('un-learning (user-initiated only)', () => { // Re-learning is expected: the signal fires again next time peerd reads a // sign-in form there. A lingering tombstone would silently refuse it. expect(store.note('https://app.test', 'password-field')).toBe(true); - expect(store.snapshot().get('https://app.test')).toBe('password-field'); + expect(store.snapshot().get('app.test')).toBe('password-field'); }); }); @@ -299,8 +386,8 @@ describe('entries (the settings list)', () => { }); await store.hydrate(); expect(store.entries()).toEqual([ - { origin: 'https://a.test', reason: 'confirmed-write' }, - { origin: 'https://b.test', reason: 'password-field' }, + { host: 'a.test', reason: 'confirmed-write' }, + { host: 'b.test', reason: 'password-field' }, ]); // Mutating the returned array must not touch the classifier's own state: // this value crosses a message boundary to the settings page. diff --git a/tests/peerd-runtime/actor/origin-lock-report.test.ts b/tests/peerd-runtime/actor/origin-lock-report.test.ts index 444237e9..44fa93f8 100644 --- a/tests/peerd-runtime/actor/origin-lock-report.test.ts +++ b/tests/peerd-runtime/actor/origin-lock-report.test.ts @@ -125,6 +125,18 @@ describe('the report is useful, not just safe', () => { expect(text).toMatch(/should be guessed at|from what the user asked/i); }); + test('a learned-scope handoff does not claim exact identity and forbids spelling retries', () => { + const text = describeLandingStop({ + action: 'handoff', reason: 'r', from: null, + to: 'http://bank.test:9443/x', handoffTo: 'http://bank.test:9443', + }); + expect(text).toContain("may share the user's browser session"); + expect(text).toContain('Do not evade this stop by changing the address'); + expect(text).toContain("comes from the user's request"); + expect(text).toContain('site:http://bank.test:9443'); + expect(text).not.toContain('has an identity on'); + }); + test('an end says both origins and refuses to guess who moved the tab', () => { const text = describeLandingStop({ action: 'end', diff --git a/tests/peerd-runtime/actor/origin-sensitivity.test.ts b/tests/peerd-runtime/actor/origin-sensitivity.test.ts index 8fc8b651..381c87ab 100644 --- a/tests/peerd-runtime/actor/origin-sensitivity.test.ts +++ b/tests/peerd-runtime/actor/origin-sensitivity.test.ts @@ -11,7 +11,10 @@ // - it never throws on junk, because it runs on every landing import { describe, test, expect } from 'bun:test'; -import { classifyOriginSensitivity, sameOrigin, LEARNED_REASONS } from '../../../extension/peerd-runtime/actor/origin-sensitivity.js'; +import { + classifyOriginSensitivity, learnedOriginCovers, sameOrigin, sensitivityHost, + LEARNED_REASONS, +} from '../../../extension/peerd-runtime/actor/origin-sensitivity.js'; import { isKnownIdpHost } from '../../../extension/peerd-runtime/actor/idp-registry.js'; const ugcOnly = { isUgcZone: (o: string) => o === 'https://github.com' }; @@ -92,14 +95,67 @@ describe('sensitivity — the learned set', () => { expect(LEARNED_REASONS).toContain(v.reason as any); }); - test('the learned set is keyed on the NORMALIZED origin', () => { - // The store writes normalized keys; a lookup that didn't normalize would - // silently miss and hand a roaming actor into a credentialed site. + test('legacy normalized-origin keys remain readable during migration', () => { + // Old durable records used origins. The classifier accepts that shape while + // hydration compacts it to a host key. const v = classifyOriginSensitivity('HTTPS://Bank.test:443/login', { learned: new Set(['https://bank.test']), }); expect(v.sensitive).toBe(true); }); + + test('a learned host covers every scheme and port on that host', () => { + const learned = new Map([['login.bank.test', 'password-field' as const]]); + for (const landing of [ + 'https://login.bank.test', + 'https://login.bank.test:8443/account', + 'http://login.bank.test:8080/account', + ]) { + expect(classifyOriginSensitivity(landing, { learned }).sensitive).toBe(true); + } + }); + + test('a learned parent covers descendant hosts', () => { + const learned = new Map([['bank.test', 'confirmed-write' as const]]); + const v = classifyOriginSensitivity('https://private.accounts.bank.test/inbox', { learned }); + expect(v.sensitive).toBe(true); + expect(v.reason).toBe('confirmed-write'); + expect(v.origin).toBe('https://private.accounts.bank.test'); + }); + + test('a learned child does not poison its parent or a sibling', () => { + const learned = new Map([['attacker.shared.test', 'password-field' as const]]); + expect(classifyOriginSensitivity('https://shared.test', { learned }).sensitive).toBe(false); + expect(classifyOriginSensitivity('https://victim.shared.test', { learned }).sensitive).toBe(false); + }); + + test('hostname boundaries prevent suffix lookalikes from matching', () => { + const learned = new Map([['bank.test', 'password-field' as const]]); + expect(classifyOriginSensitivity('https://secure.bank.test', { learned }).sensitive).toBe(true); + expect(classifyOriginSensitivity('https://bank.test.evil.test', { learned }).sensitive).toBe(false); + expect(classifyOriginSensitivity('https://notbank.test', { learned }).sensitive).toBe(false); + }); + + test('the closest learned host supplies the reason', () => { + const learned = new Map([ + ['example.test', 'password-field' as const], + ['accounts.example.test', 'confirmed-write' as const], + ]); + const v = classifyOriginSensitivity('https://private.accounts.example.test', { learned }); + expect(v.reason).toBe('confirmed-write'); + }); +}); + +describe('learned cookie-host scope', () => { + test('normalization removes scheme, port, path, and host casing', () => { + expect(sensitivityHost('HTTP://Login.Bank.test:8080/path')).toBe('login.bank.test'); + }); + + test('coverage is one-way from parent to descendant', () => { + expect(learnedOriginCovers('https://example.test:8443', 'http://a.example.test:8080')).toBe(true); + expect(learnedOriginCovers('https://a.example.test', 'https://example.test')).toBe(false); + expect(learnedOriginCovers('https://a.example.test', 'https://b.example.test')).toBe(false); + }); }); describe('sensitivity — nothing to be signed in to', () => { @@ -130,6 +186,7 @@ describe('sameOrigin', () => { test('different origins are different', () => { expect(sameOrigin('https://github.com', 'https://evil.test')).toBe(false); expect(sameOrigin('https://github.com', 'http://github.com')).toBe(false); // scheme counts + expect(sameOrigin('https://github.com:8443', 'https://github.com:9443')).toBe(false); // port counts }); test('an unusable origin is never "the same as" anything, including another unusable one', () => { diff --git a/tests/red-team/scenarios/10-origin-retasking.ts b/tests/red-team/scenarios/10-origin-retasking.ts index 3eb35afe..d08b3e66 100644 --- a/tests/red-team/scenarios/10-origin-retasking.ts +++ b/tests/red-team/scenarios/10-origin-retasking.ts @@ -154,6 +154,53 @@ const CORPUS: Case[] = [ }; }, }, + { + vector: 'change scheme and port after a host is learned sensitive', + seeks: 'recover roaming authority through another spelling of the same cookie host', + defense: 'learned sensitivity is keyed by hostname rather than origin', + check: () => { + const verdict = classifyOriginSensitivity('http://bank.test:9443/transfer', { + learned: new Map([['bank.test', 'password-field']]), + }); + return { + denied: verdict.sensitive && verdict.reason === 'password-field' + && verdict.origin === 'http://bank.test:9443', + evidence: `sensitive=${verdict.sensitive} reason=${verdict.reason ?? 'none'} origin=${verdict.origin ?? 'none'}`, + }; + }, + }, + { + vector: 'move from a learned parent host onto a cookie-sharing descendant', + seeks: 'recover roaming authority where a Domain cookie may still authenticate the user', + defense: 'a learned parent hostname covers boundary-checked descendants', + check: () => { + const verdict = classifyOriginSensitivity('https://pay.bank.test/transfer', { + learned: new Map([['bank.test', 'confirmed-write']]), + }); + return { + denied: verdict.sensitive && verdict.reason === 'confirmed-write', + evidence: `sensitive=${verdict.sensitive} reason=${verdict.reason ?? 'none'}`, + }; + }, + }, + { + vector: 'learn a hostile child host, then visit its parent, sibling, or suffix lookalike', + seeks: 'poison unrelated account surfaces into persistent false handoffs', + defense: 'child marks do not widen upward or sideways and suffix matching is label-bound', + check: () => { + const learned = new Map([['login.bank.test', 'password-field' as const]]); + const candidates = [ + 'https://bank.test', + 'https://pay.bank.test', + 'https://login.bank.test.evil.test', + ]; + const verdicts = candidates.map((url) => classifyOriginSensitivity(url, { learned }).sensitive); + return { + denied: verdicts.every((sensitiveVerdict) => sensitiveVerdict === false), + evidence: `sensitive=${verdicts.join(',')}`, + }; + }, + }, { vector: 'numerically address a sensitive tab already owned by a legitimate site actor', seeks: 'erase the existing binding and its live origin lock during refusal', @@ -492,6 +539,8 @@ export const scenario: Scenario = { }); return summarize(probes, [ 'origin lock: roaming may not enter a credentialed origin', + 'learned sensitivity follows cookie host scope across scheme, port, and descendants', + 'learned child hosts cannot poison parents, siblings, or suffix lookalikes', 'numeric tab ids identify locations, not signed-in-site authority', 'numeric refusal preserves an existing actor binding and origin lock', 'origin lock: bound may not leave its owned origin', From 535740d3a73ea6817e05288bf51cf6e917b29b32 Mon Sep 17 00:00:00 2001 From: NotASithLord <48842926+NotASithLord@users.noreply.github.com> Date: Sun, 9 Aug 2026 06:07:44 +0200 Subject: [PATCH 2/2] add learned sites visual baselines --- scripts/cdp/GALLERY.md | 18 +++++++++++++----- .../linux-x64/options-learned-sites.dark.png | Bin 0 -> 73780 bytes .../linux-x64/options-learned-sites.light.png | Bin 0 -> 66335 bytes 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 scripts/cdp/baselines/linux-x64/options-learned-sites.dark.png create mode 100644 scripts/cdp/baselines/linux-x64/options-learned-sites.light.png diff --git a/scripts/cdp/GALLERY.md b/scripts/cdp/GALLERY.md index 374971a0..6a18d406 100644 --- a/scripts/cdp/GALLERY.md +++ b/scripts/cdp/GALLERY.md @@ -1,6 +1,6 @@ # peerd — visual gallery -**26 states · 52 screens · baselines `linux-x64`** +**27 states · 54 screens · baselines `linux-x64`** Every screen below is the live UI rendered through the E2E harness at a pinned Chrome build and viewport — the exact images the visual-regression gate compares @@ -189,7 +189,15 @@ against. | --- | --- | | options-dweb-stop-failed (light) | options-dweb-stop-failed (dark) | -### 23 · options-transfer +### 23 · options-learned-sites + +`options-learned-sites` + +| light | dark | +| --- | --- | +| options-learned-sites (light) | options-learned-sites (dark) | + +### 24 · options-transfer `options-transfer` @@ -197,7 +205,7 @@ against. | --- | --- | | options-transfer (light) | options-transfer (dark) | -### 24 · options-transfer-conflict +### 25 · options-transfer-conflict `options-transfer-conflict` @@ -205,7 +213,7 @@ against. | --- | --- | | options-transfer-conflict (light) | options-transfer-conflict (dark) | -### 25 · site-client-confirm +### 26 · site-client-confirm `site-client-confirm` @@ -213,7 +221,7 @@ against. | --- | --- | | site-client-confirm (light) | site-client-confirm (dark) | -### 26 · vm-tab-failed +### 27 · vm-tab-failed `vm-tab-failed` diff --git a/scripts/cdp/baselines/linux-x64/options-learned-sites.dark.png b/scripts/cdp/baselines/linux-x64/options-learned-sites.dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f79d406be5cafd054169f0a405b2ca79557f2511 GIT binary patch literal 73780 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 literal 0 HcmV?d00001 diff --git a/scripts/cdp/baselines/linux-x64/options-learned-sites.light.png b/scripts/cdp/baselines/linux-x64/options-learned-sites.light.png new file mode 100644 index 0000000000000000000000000000000000000000..5e52d65c5af4eb35f7be27e148367d8a9df648dd GIT binary patch literal 66335 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ⓈkG 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⁡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 >D9+5q4L&CL#rDU_qy6k#<`(CdS+?`|6yfT1kc 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