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) {