From 555d42983c07077ace2b1189d3fcfda02b2b9dfa Mon Sep 17 00:00:00 2001 From: Alberto Martinez Date: Sat, 28 Mar 2026 21:47:31 -0700 Subject: [PATCH] security: track cookie-imported domains and scope cookie imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cookie origin tracking to BrowserManager (trackCookieImportDomains, getCookieImportedDomains, hasCookieImports). Every cookie import path now records which domains were imported. - cookie-import-browser direct mode already required --domain; this adds --all as the explicit opt-in for importing all cookies. Without either flag, the interactive picker UI opens instead. - cookie-import (JSON file) now tracks imported domains on BrowserManager. - The --all flag works but emits a warning recommending --domain for tighter scoping. This is the foundation for origin-pinned JS execution (separate PR) — the BrowserManager now knows which domains have imported cookies, so downstream commands can restrict operations to those origins. Made-with: Cursor --- browse/src/browser-manager.ts | 16 ++++++++++++++++ browse/src/write-commands.ts | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index a6eda991b..4784c5431 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -57,6 +57,9 @@ export class BrowserManager { private dialogAutoAccept: boolean = true; private dialogPromptText: string | null = null; + // ─── Cookie Origin Tracking ──────────────────────────────── + private cookieImportedDomains: Set = new Set(); + // ─── Handoff State ───────────────────────────────────────── private isHeaded: boolean = false; private consecutiveFailures: number = 0; @@ -521,6 +524,19 @@ export class BrowserManager { return this.dialogPromptText; } + // ─── Cookie Origin Tracking ──────────────────────────────── + trackCookieImportDomains(domains: string[]): void { + for (const d of domains) this.cookieImportedDomains.add(d); + } + + getCookieImportedDomains(): ReadonlySet { + return this.cookieImportedDomains; + } + + hasCookieImports(): boolean { + return this.cookieImportedDomains.size > 0; + } + // ─── Viewport ────────────────────────────────────────────── async setViewport(width: number, height: number) { await this.getPage().setViewportSize({ width, height }); diff --git a/browse/src/write-commands.ts b/browse/src/write-commands.ts index 02413daf8..3194eb487 100644 --- a/browse/src/write-commands.ts +++ b/browse/src/write-commands.ts @@ -314,32 +314,58 @@ export async function handleWriteCommand( } await page.context().addCookies(cookies); + const importedDomains = [...new Set(cookies.map((c: any) => c.domain).filter(Boolean))]; + if (importedDomains.length > 0) bm.trackCookieImportDomains(importedDomains); return `Loaded ${cookies.length} cookies from ${filePath}`; } case 'cookie-import-browser': { // Two modes: // 1. Direct CLI import: cookie-import-browser --domain [--profile ] - // 2. Open picker UI: cookie-import-browser [browser] + // Requires --domain (or --all to explicitly import everything). + // 2. Open picker UI: cookie-import-browser [browser] (interactive domain selection) const browserArg = args[0]; const domainIdx = args.indexOf('--domain'); const profileIdx = args.indexOf('--profile'); + const hasAll = args.includes('--all'); const profile = (profileIdx !== -1 && profileIdx + 1 < args.length) ? args[profileIdx + 1] : 'Default'; if (domainIdx !== -1 && domainIdx + 1 < args.length) { - // Direct import mode — no UI + // Direct import mode — scoped to specific domain const domain = args[domainIdx + 1]; const browser = browserArg || 'comet'; const result = await importCookies(browser, [domain], profile); if (result.cookies.length > 0) { await page.context().addCookies(result.cookies); + bm.trackCookieImportDomains([domain]); } const msg = [`Imported ${result.count} cookies for ${domain} from ${browser}`]; if (result.failed > 0) msg.push(`(${result.failed} failed to decrypt)`); return msg.join(' '); } - // Picker UI mode — open in user's browser + if (hasAll) { + // Explicit all-cookies import — requires --all flag as a deliberate opt-in. + // Imports every non-expired cookie domain from the browser. + const browser = browserArg || 'comet'; + const { listDomains } = await import('./cookie-import-browser'); + const { domains } = listDomains(browser, profile); + const allDomainNames = domains.map(d => d.domain); + if (allDomainNames.length === 0) { + return `No cookies found in ${browser} (profile: ${profile})`; + } + const result = await importCookies(browser, allDomainNames, profile); + if (result.cookies.length > 0) { + await page.context().addCookies(result.cookies); + bm.trackCookieImportDomains(allDomainNames); + } + const msg = [`Imported ${result.count} cookies across ${Object.keys(result.domainCounts).length} domains from ${browser}`]; + msg.push('(used --all: all browser cookies imported — consider --domain for tighter scoping)'); + if (result.failed > 0) msg.push(`(${result.failed} failed to decrypt)`); + return msg.join(' '); + } + + // Picker UI mode — open in user's browser for interactive domain selection const port = bm.serverPort; if (!port) throw new Error('Server port not available'); @@ -355,7 +381,7 @@ export async function handleWriteCommand( // open may fail silently — URL is in the message below } - return `Cookie picker opened at ${pickerUrl}\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`; + return `Cookie picker opened at ${pickerUrl}\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.\n\nTip: For scripted imports, use --domain to scope cookies to a single domain.`; } default: