From 73011d154281ada72285fc96521592577bdafec6 Mon Sep 17 00:00:00 2001 From: JunghwanNA <70629228+shaun0927@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:29:02 +0900 Subject: [PATCH] fix(profile): guard needsSync against overwriting headless-acquired cookies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a headless session acquires Google login cookies and writes them to the persistent profile, needsSync() was returning true after 30 minutes (or when the real Chrome Cookies file changed), causing syncProfileData() to overwrite those cookies with the real Chrome profile's cookies. Add a guard that checks the persistent profile's Cookies mtime against the last sync timestamp. If the persistent Cookies were modified after the last sync, a headless session must have written them — skip the overwrite to preserve the acquired session (Root Cause B, issue #606). Co-Authored-By: Claude Sonnet 4.6 --- src/chrome/profile-manager.ts | 19 ++++++++++++++++ tests/chrome/persistent-profile.test.ts | 29 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/chrome/profile-manager.ts b/src/chrome/profile-manager.ts index 1f8bb56..3c8a5f1 100644 --- a/src/chrome/profile-manager.ts +++ b/src/chrome/profile-manager.ts @@ -197,6 +197,25 @@ export class ProfileManager { return false; } + // Guard: if the persistent profile's Cookies file has been modified after + // the last sync, a headless session wrote cookies — do not overwrite them. + const persistentCookiesPath = path.join( + ProfileManager.PERSISTENT_PROFILE_DIR, + profileSubdir, + 'Cookies' + ); + try { + const persistentStat = fs.statSync(persistentCookiesPath); + if (persistentStat.mtimeMs > metadata.lastSyncTimestamp) { + console.error( + '[ProfileManager] Persistent profile cookies modified after last sync — skipping overwrite to preserve headless-acquired session' + ); + return false; + } + } catch { + // Persistent Cookies file doesn't exist yet — sync is needed + } + if (currentHash !== metadata.sourceProfileHash) { return true; // Source has changed } diff --git a/tests/chrome/persistent-profile.test.ts b/tests/chrome/persistent-profile.test.ts index 8954c21..fcf7c5f 100644 --- a/tests/chrome/persistent-profile.test.ts +++ b/tests/chrome/persistent-profile.test.ts @@ -180,6 +180,35 @@ describe('ProfileManager', () => { const manager = new ProfileManager(); expect(manager.needsSync(sourceDir)).toBe(false); }); + + it('should return false when persistent profile Cookies were modified after last sync (headless guard)', () => { + // Set up source Cookies file and compute its hash + const cookiesPath = path.join(sourceDir, 'Default', 'Cookies'); + fs.writeFileSync(cookiesPath, 'source-cookie-data'); + const stat = fs.statSync(cookiesPath); + const hash = `${stat.mtimeMs}:${stat.size}`; + + // Write metadata with an old timestamp (simulating last sync happened in the past) + const oldTimestamp = Date.now() - (40 * 60 * 1000); // 40 minutes ago + const metadata: SyncMetadata = { + lastSyncTimestamp: oldTimestamp, + sourceProfileHash: hash, + syncCount: 1, + sourceProfileDir: sourceDir, + }; + fs.writeFileSync(ProfileManager.SYNC_METADATA_PATH, JSON.stringify(metadata)); + + // Create the persistent profile's Cookies file with a recent mtime + // (simulating a headless session that wrote cookies after the last sync) + const persistentCookiesDir = path.join(ProfileManager.PERSISTENT_PROFILE_DIR, 'Default'); + fs.mkdirSync(persistentCookiesDir, { recursive: true }); + fs.writeFileSync(path.join(persistentCookiesDir, 'Cookies'), 'headless-acquired-cookies'); + // The persistent Cookies file was just written — its mtime is newer than oldTimestamp + + const manager = new ProfileManager(); + // Should NOT sync: persistent cookies were written after the last sync by a headless session + expect(manager.needsSync(sourceDir)).toBe(false); + }); }); // =========================================================================