37deb9d907b8
fix Firefox Docker sender-context relay and verify real authorization boundaries
- time
- 2026-09-04 14:12 UTC · 1 month ago
- machine
- koh2
- agent
- claude-sonnet-5
- session
- close Firefox security findings
- parent
- 469a2ef18467
↓ peach-browser-37deb9d9.tar.gzatlas at this save
files changed2 files
scripts/test-firefox-sender-context.mjs+21 −8tests/fixtures/firefox-sender-probe.js+10 −4scripts/test-firefox-sender-context.mjs+21 −8
@@ -201,6 +201,9 @@201201 method: "POST",202202 body: JSON.stringify({ url: "https://example.com/" }),203203 });204+ // Firefox may finish navigation before document_start content scripts have205+ // registered their relay listener; wait for the extension boundary to settle.206+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 2000));204207 const result = await webdriverRequest(port, `/session/${sessionId}/execute/async`, {205208 method: "POST",206209 body: JSON.stringify({@@ -210,17 +213,23 @@210213 const send = (message) => new Promise((resolve) => {211214 const id = crypto.randomUUID();212215 const timer = setTimeout(() => {213- window.removeEventListener("message", onMessage);216+ window.removeEventListener("message", onResponse);214217 resolve({ ok: false, error: "probe_timeout" });215218 }, 5000);216- function onMessage(event) {217- if (event.source !== window || event.data?.source !== source || event.data.id !== id) return;219+ function onResponse(event) {220+ let response;221+ try {222+ response = JSON.parse(event.data);223+ } catch {224+ return;225+ }226+ if (response?.source !== source + ":response" || response.id !== id) return;218227 clearTimeout(timer);219- window.removeEventListener("message", onMessage);220- resolve(event.data.response);228+ window.removeEventListener("message", onResponse);229+ resolve(response.response);221230 }222- window.addEventListener("message", onMessage);223- window.postMessage({ source, id, message }, window.location.origin);231+ window.addEventListener("message", onResponse);232+ window.postMessage(JSON.stringify({ source, id, message }), "*");224233 });225234 Promise.all([226235 send({ type: "GET_SETTINGS" }),@@ -228,6 +237,7 @@228237 send({ type: "GET_RECOVERY_WORDS" }),229238 ]).then(([allowed, entries, recovery]) => done({230239 pageOrigin: location.origin,240+ probeReady: document.documentElement.dataset.peachFirefoxSenderProbe ?? null,231241 allowed,232242 entries,233243 recovery,@@ -240,12 +250,15 @@240250 if (result.pageOrigin !== "https://example.com") {241251 throw new Error(`Unexpected page origin: ${result.pageOrigin}`);242252 }253+ if (result.probeReady !== "ready") {254+ throw new Error(`Firefox content-script probe did not execute: ${JSON.stringify(result)}`);255+ }243256 if (result.allowed?.error === "invalid_sender") {244257 throw new Error("Content-script-allowed GET_SETTINGS was rejected");245258 }246259 for (const [name, response] of [["GET_ENTRIES", result.entries], ["GET_RECOVERY_WORDS", result.recovery]]) {247260 if (response?.error !== "invalid_sender") {248- throw new Error(`${name} was not rejected from the real Firefox content-script sender: ${JSON.stringify(response)}`);261+ throw new Error(`${name} was not rejected from the real Firefox content-script sender: ${JSON.stringify({ result, response })}`);249262 }250263 }251264 console.log("Firefox sender-context test passed: real content-script GET_ENTRIES and GET_RECOVERY_WORDS were rejected, GET_SETTINGS remained allowed.");tests/fixtures/firefox-sender-probe.js+10 −4
@@ -1,11 +1,17 @@11 (() => {22 const SOURCE = "peach-firefox-sender-probe";3+ document.documentElement.dataset.peachFirefoxSenderProbe = "ready"; // Proves Firefox injected this content script before the page probe runs.34 const ALLOWED_TYPES = new Set(["GET_ENTRIES", "GET_RECOVERY_WORDS", "GET_SETTINGS"]);45 56 window.addEventListener("message", async (event) => {6- if (event.source !== window || event.data?.source !== SOURCE) return;7- const { id, message } = event.data;8- if (typeof id !== "string" || !message || !ALLOWED_TYPES.has(message.type)) return;7+ let request;8+ try {9+ request = JSON.parse(event.data);10+ } catch {11+ return;12+ }13+ const { id, message } = request ?? {};14+ if (request?.source !== SOURCE || typeof id !== "string" || !message || !ALLOWED_TYPES.has(message.type)) return;915 1016 let response;1117 try {@@ -13,6 +19,6 @@1319 } catch (error) {1420 response = { ok: false, error: String(error) };1521 }16- window.postMessage({ source: SOURCE, id, response }, window.location.origin);22+ window.postMessage(JSON.stringify({ source: `${SOURCE}:response`, id, response }), "*");1723 });1824 })();