From eed11acba29044072953b6269337b076d499fbda Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Mon, 20 Apr 2026 20:37:49 -0400 Subject: [PATCH] feat(ext/popup): snapshot activeTab at popup-open for fill_credentials (audit M5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend PopupState with {capturedTabId, capturedUrl} populated via chrome.tabs.query({active: true, currentWindow: true}) in init(). These are later passed with fill_credentials so the SW can verify the captured tab's hostname hasn't changed out from under the user before forwarding credentials. Combined with expectedHost in the forwarded payload + content-side re-check in fill.ts, this closes the TOCTOU window on the popup → SW → content fill path. popup.ts stays under @ts-nocheck (Slice 6 removes it alongside the item-* rewrites). Co-Authored-By: Claude Opus 4.7 (1M context) --- extension/src/popup/popup.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/extension/src/popup/popup.ts b/extension/src/popup/popup.ts index 360cb2e..a808007 100644 --- a/extension/src/popup/popup.ts +++ b/extension/src/popup/popup.ts @@ -33,6 +33,12 @@ export interface PopupState { activeGroup: string | null; error: string | null; loading: boolean; + // Captured tab snapshot taken at popup-open. Used by fill_credentials + // to guard against TOCTOU navigation — the SW re-checks this URL's + // hostname against the tab's live URL before forwarding fill_credentials + // to the content script. See router/popup-only.ts#handleFillCredentials. + capturedTabId: number | null; + capturedUrl: string; } let currentState: PopupState = { @@ -45,6 +51,8 @@ let currentState: PopupState = { activeGroup: null, error: null, loading: false, + capturedTabId: null, + capturedUrl: '', }; export function getState(): PopupState { @@ -103,6 +111,13 @@ function render(): void { // --- Init --- async function init(): Promise { + // Snapshot the active tab at popup-open — the fill path uses this + // tabId/url pair so the SW can verify the tab hasn't navigated before + // forwarding credentials (audit M5 + TOCTOU close via expectedHost). + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + currentState.capturedTabId = tab?.id ?? null; + currentState.capturedUrl = tab?.url ?? ''; + // Check if extension is configured. const setupResp = await sendMessage({ type: 'get_setup_state' }); if (setupResp.ok) {