feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(desktop): import browser cookies into a profile - #7255

Open
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import
Open

feat(desktop): import browser cookies into a profile#7255
juliusmarminge wants to merge 60 commits into
shared-sqlite-clientfrom
browser-profile-import

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7272 (shared-sqlite-client).

Imports cookies from Helium on macOS into a T3 Code browser profile. Saved passwords and browsing history remain out of scope.

The app requests the browser key through the in-process Keychain API, so consent belongs to T3 Code. Denial stops the import; there is no permission bypass. Source databases are opened read-only and snapshotted consistently with SQLite before extraction.

The wizard chooses a source and destination, prevents profile edits during an import, and reports imported/skipped counts and affected sites. Plaintext and encrypted cookie records are handled separately; domain-bound records are validated. Partitioned cookies are skipped when their partition semantics cannot be preserved by Electron.

Validation includes synthetic decryption records, plaintext and legacy records, domain-binding failures, partitioned-cookie rejection, WAL snapshot consistency, interrupted writes, and wizard state transitions, plus scoped typechecks/lint. Earlier Helium imports were verified in the desktop app; this audit did not request another live Keychain read.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add BrowserImport service and wizard to import browser cookies

  • Introduces BrowserImport service and IPC handlers to read, decrypt, and write Chromium cookies into a target profile partition
  • Adds BrowserImportWizard UI to guide users through source and target selection, showing quit, blocked, and success screens
  • Replaces inline client-settings persistence with a serialized queue so concurrent updates execute in order and publish only after durable writes succeed
  • Adds @napi-rs/keyring dependency and build staging to package native keychain binaries for desktop artifacts
  • Risk: persistClientSettingsUpdate now delays in-memory snapshot publication until persistence completes and rejects on failure; optimistic patches made during an awaited update are reapplied

Macroscope summarized d22663d.


Note

High Risk
Reads third-party cookie databases and macOS Keychain secrets and writes session cookies into app partitions; correctness and hardening (path validation, partition alignment) directly affect auth/session security.

Overview
Adds browser cookie import from installed Chromium-family browsers (initially Helium on macOS) into T3 Code preview profiles. The desktop main process gains a BrowserImport service that lists sources, blocks import while the source browser is running, validates profile paths against traversal, reads cookies via a consistent SQLite snapshot and in-process Keychain access (@napi-rs/keyring), then writes into the same Electron partition the preview webview uses. New IPC/preload endpoints expose listBrowserImportSources and importBrowserCookies; contracts define sources, failure reasons, and results.

The Integrations browser profiles UI is reworked: Add profile opens a menu for a blank profile or Import from a detected browser, with a multi-step BrowserImportWizard (quit browser, pick source/target, progress, skipped domains). Default profile moves into per-profile row actions; the standalone default-profile setting row is removed. New profiles are persisted only after a successful import with cookies, using a new persistClientSettingsUpdate queue so registration does not race other settings writes.

Desktop release builds stage @napi-rs/keyring native binaries like existing passkey addons. Extensive unit tests cover decryption edge cases, lock detection, and wizard logic.

Reviewed by Cursor Bugbot for commit d22663d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitaiBot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf12449-bccc-4c49-a6df-572e284b0f18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Chromium cookie helper, and the IPC/layer wiring against the Effect service conventions.

Service shape, module layout (Context.Service tag + inline interface, make, layer), namespace imports, and layer composition in main.ts all look correct. The findings below are about the error model: the new failure type is unstructured (reason: Schema.String) and every construction discards the underlying cause, including one that erases a structured BrowserSession error.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
@github-actions

github-actionsBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB15.1 KiB
CodexThread snapshot wire6.9 KiB7.3 KiB
CodexLive turn WebSocket wire6.4 KiB7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB66.4 KiB
CodexLive turn messages921
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d22663d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency

One finding in the changed browser-profiles UI (apps/web/src/components/settings/IntegrationsSettings.tsx).

The menu/primitive usage itself looks consistent with the shared system: Menu/MenuPopup/MenuItem/MenuSub from components/ui/menu, MenuTrigger render={<Button …/>} matching the existing pattern in PreviewMoreMenu/ProjectScriptsControl, existing size="icon-sm" / variant="ghost-muted" Button variants, min-w-* on popups honouring MenuPopup's width-defaulting contract, and the bordered list container matching the convention already used in ConnectionsSettings. aria-labels are preserved on both the rename input and the new row trigger.

The issue is a state-display regression introduced by removing the default-profile Select while filtering Incognito out of the new list: a stored default of incognito (which the removed Select allowed a user to pick) now leaves the section with no "Default" badge on any row.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new desktop workflow that reads and decrypts browser authentication cookies through the OS keychain, then writes them into Electron profiles, along with native packaging and settings changes. The sensitive-data scope and unresolved runtime/UI risks warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

No code changes detected at b4a5e28. Prior analysis still applies.

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge changed the base branch from browser-profiles to shared-sqlite-clientAugust 16, 2026 22:54
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new BrowserImport service, its Sources/ChromiumCookies helpers, the IPC method, and the layer wiring against the Effect service conventions. The service module follows the canonical shape (errors → Context.Service with inline interface → makelayer), dependencies are acquired from the environment, and the failure translations now keep a real cause. One remaining gap on error context.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI Consistency — 1 finding

The two issues flagged on earlier commits are addressed: loadSources now clears sources before each refresh, and resolvedDefaultId resolves against the rendered rows.

One consistency gap remains: with BrowserDefaultProfileSetting removed and Incognito no longer rendered as a row, the section can badge Default on a profile that is not the effective default (see inline comment).

Minor (not blocking): lines 507–529 now carry three consecutive doc comments for a single component — the "Create, rename, and remove browser profiles" and "Per-profile cookie import" blocks are leftovers from the removed/renamed pieces and could be folded into one.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings in the reworked browser-profiles section. Both are about the settings UI now disagreeing with the runtime behaviour it configures, rather than styling.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
Comment threadapps/desktop/src/preview/BrowserImport/ChromiumCookies.ts Outdated
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx Outdated
juliusmarmingeand others added 26 commits September 2, 2026 11:51
Two review findings: the import menu only hid uninstalled sources, so a
platform that can never import from a browser (a macOS-only fork on Linux)
still listed it and clicking led to a dead-end blocked screen — those are now
left out too. And the "Default profile" search entry lost the word "browser"
when its settings row went away, which broke the "default browser profile"
query and read ambiguously beside its siblings; the search-only title says
"Default browser profile" again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key retry
Three review findings on the import flow:
- Profile discovery accepted any entry named `Cookies`, so a directory listed
as an importable profile and then failed the SQLite open; the fallback scan
and the installed check now require a regular file.
- A "new profile" target chosen before the profile cap was reached could still
be imported once it was, creating a profile past the limit; the Import
button now disables in that case.
- `keychainItemMissing` told the user to sign in and retry but offered no
retry — it is now retryable, since signing in is exactly what creates the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment threadapps/web/src/components/settings/IntegrationsSettings.tsx
Comment on lines +229 to +236
const targetMissing =
target.kind === "existing" &&
!targetProfiles.some((profile) => profile.id === target.profileId);
const targetFeedback =
targetError ??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new cap guard on the Import button (target.kind === "new" && !canCreateProfile) has no matching feedback, so it lands the user in a dead end: once canCreateProfile flips false the New profile tile is unrendered (line 270), nothing in Into looks selected, and Import is disabled with nothing said. The structurally identical case one line up — an existing target that disappeared — already renders targetFeedback telling the user to choose again, so the two now behave differently for the same problem.

Consider routing the cap case through the same message slot (and reusing targetUncreatable in the disabled expression below, so the condition isn't stated twice).

Suggested change
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: undefined);
consttargetMissing=
target.kind==="existing"&&
!targetProfiles.some((profile)=>profile.id===target.profileId);
// The "New profile" tile is unrendered once the cap is reached, so a target
// chosen before that leaves nothing selected in "Into".
consttargetUncreatable=target.kind==="new"&&!canCreateProfile;
consttargetFeedback=
targetError??
(targetMissing
? "That profile is no longer available. Choose where to import these cookies."
: targetUncreatable
? "You've reached the profile limit. Choose an existing profile to import into."
: undefined);

Posted via Macroscope — UI Consistency

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge