feat(desktop): import cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername
, '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 cookies from Safari - #7262

Open
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari
Open

feat(desktop): import cookies from Safari#7262
juliusmarminge wants to merge 25 commits into
browser-import-linux-windowsfrom
browser-import-safari

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7261 (browser-import-linux-windows).

Adds Safari cookie import on macOS and a Full Disk Access step. System Settings opens through a dedicated main-process action with a fixed destination. After granting access, the wizard returns to configuration or retries the selected import, depending on where permission was needed.

The Safari reader validates the binary cookie structure, translates Apple-epoch timestamps, and preserves source-path context on parse errors. TCC denial is reported without attempting a bypass.

This layer retains the Windows-user-tested detection contribution #7323: modern/legacy Chromium cookie paths and Firefox’s active Windows lock probe. Access-denied errors are not treated as proof that Firefox is running. Windows Chromium remains intentionally unsupported.

Validation: binary parser fixtures and malformed-record boundaries, filesystem/lock fixtures, fixed System Settings destination, and permission-flow state transitions. Earlier desktop testing by the maintainer is preserved; no new live Safari or Windows GUI test was run in this audit.

Original implementation: Claude Code and external contribution #7323. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add Safari cookie import with Full Disk Access detection and Linux Chromium key fix

  • Adds a Safari source to BROWSER_IMPORT_SOURCES on macOS and parses Safari's Cookies.binarycookies format, converting Apple-epoch timestamps and extracting domain, path, security flags, and SameSite fields in SafariCookies.ts
  • Introduces a needsFullDiskAccess unavailable reason; when an EPERM is hit opening the Safari jar, the import wizard in BrowserImportWizard.tsx shows a permission step that can open macOS System Settings and recheck access
  • Wires an openSystemSettings IPC channel through the desktop bridge, preload, and ElectronShell to open the Privacy & Security Full Disk Access pane
  • Fixes Linux Chromium key retrieval in ChromiumKeys.ts by adding the Chromium libsecret schema selector to secret-tool, so items with the same application attribute but a different schema are ignored
  • Firefox profile discovery in Sources.ts now excludes declared profiles without a cookie database and falls back to directory scanning when no declared profile has one
  • Risk: BrowserImportWizard callers must now provide a required openSystemSettings callback prop; the keychain-read audit log in BrowserImport.make is no longer emitted for non-macOS Chromium imports

Macroscope summarized d163c94.


Note

Medium Risk
Touches cookie import, macOS TCC/Full Disk Access handling, and new IPC surface; parser mistakes could drop cookies silently, but the diff adds explicit bounds checks and tests for malformed jars.

Overview
Adds Safari as a macOS browser-import source by reading Cookies.binarycookies, with strict bounds checking on the binary format and mapping TCC EPERM denials to a new needsFullDiskAccess reason (distinct from ordinary EACCES failures).

Pre-flight listing probes Safari jar access via safariAccessDenied so the import wizard can land on a Full Disk Access step before configuration. That step can open macOS Privacy & Security → Full Disk Access through a new openSystemSettings("full-disk-access") path (fixed deep link in main process, pane id over IPC—not arbitrary URLs), wired from preload through localApi into IntegrationsSettings.

Browser import elsewhere: Chromium cookie DB resolution prefers Network/Cookies (127+) with legacy fallback; Firefox profile discovery skips empty/stale profiles.ini entries and rescans when needed; Windows treats stale Firefox parent.lock as not running and keeps Chromium forks off win32 (App-Bound Encryption). Keychain audit logging for Chromium imports is limited to darwin.

Reviewed by Cursor Bugbot for commit d163c94. 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: 2ea8679c-247d-4066-a7c4-bc7ad8914eb6

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:L 100-499 changed lines (additions + deletions). labels Aug 16, 2026
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@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.6 KiB66.4 KiB
CodexLive turn messages1021
ClaudeTotal thread wire13.1 KiB15.1 KiB
ClaudeThread snapshot wire6.9 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.2 KiB7.8 KiB
ClaudeLive turn WebSocket decoded55.5 KiB66.4 KiB
ClaudeLive turn messages821

Baseline: unavailable · PR result: d163c94 · 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.

@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/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
@macroscopeapp

macroscopeappBot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Adds a substantial macOS Safari cookie-import capability with sensitive cookie-file access, a new Full Disk Access UI/IPC flow, and cross-platform browser-detection changes. The new test file also adds a file-level static-analysis suppression directive, so the change requires human review.

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

You can add or adjust custom eligibility rules. Learn more.

@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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.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.

Effect service conventions review: one finding on the new Safari cookie error. Everything else (subpath namespace imports, Effect.fn boundaries, tagged-error definition, pass-through of the already structured error in Effect.try, no hidden runtimes) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts
Comment threadapps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts Outdated
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts
Comment threadapps/desktop/src/preview/BrowserImport/SafariCookies.ts

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6918217. Configure here.

Comment threadapps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
juliusmarmingeand others added 25 commits September 2, 2026 16:00
Safari does not encrypt its cookies. It stores them in a proprietary
`Cookies.binarycookies` file whose protection is TCC rather than cryptography:
the file sits inside the app container, which only apps with Full Disk Access
may read. So the gate is a permission the user grants in System Settings, and
a denial is reported as exactly that rather than as a generic read failure.
Two details the format forces:
Timestamps count seconds from 2001-01-01, not the UNIX epoch, so every expiry
needs rebasing or cookies import as long expired.
The format predates SameSite and carries no equivalent field. Imported cookies
are marked Lax, the modern browser default — claiming "none" would widen the
scope of every cookie Safari ever set.
Safari keeps one jar for the whole app rather than per-profile, so it exposes
a single implicit profile, and it has no observable lock file since the jar is
written atomically.
The parser is covered by tests that build the binary format byte for byte,
including a multi-page file — Safari pages its jar, and a single-page reader
would silently return only the first slice. That coverage matters because the
real file cannot be read on this machine without the very permission the
feature asks for; the TCC path itself was verified against the live file,
which denies with EPERM and reports `needsFullDiskAccess`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every `nodeBuiltinImport:off` in the import module now says which builtin it
covers and why Effect has no equivalent, matching the neighbouring preload and
Playwright modules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of it
`Buffer.subarray` clamps rather than throwing, so every declared structure in
the binary format was taken on trust. An overlong page swallowed the following
page's bytes and pushed the cursor past the end, dropping every cookie after
the boundary from an import that still reported success. A record whose
declared size overran its page left its string offsets free to read the next
record's bytes as this cookie's value.
Pages, records, and string offsets are now bounds-checked against what the file
actually contains, and a mismatch fails the read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the Chromium and Firefox readers: the failure carries which jar it was
for, so a Full Disk Access refusal is traceable rather than anonymous. Optional
because the parser raises before a path is in hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Safari import with no Full Disk Access failed with the generic "cookie
database could not be read" instead of telling the user to grant access — and
no prompt appears, because macOS never prompts for Full Disk Access; the app is
added by hand.
The denial arrives as EPERM, which Effect tags `Unknown`, not
`PermissionDenied` (that is EACCES), so checking the tag alone never matched.
The underlying errno is checked too. Verified against the real jar: the reason
is now `needsFullDiskAccess`, which the renderer maps to the System Settings
instruction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Safari's cookies sit behind Full Disk Access, which no one has granted before
their first import — so it is a step in the flow, not a failure. When an import
comes back needing it, the wizard shows a screen that says what it's for, links
to the right System Settings pane, and — from an "I've turned it on" button —
runs the import itself, so the user never restarts from the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step's "Open System Settings" button did nothing: it routed the
`x-apple.systempreferences:` deep link through `openExternal`, whose
allowlist only passes http(s) and remote-editor schemes, so the link was
silently dropped.
Adds a dedicated `openSystemSettings(pane)` path instead. The renderer
sends a known pane identifier, not a URL, and the main process maps it to
an app-fixed deep link and opens it directly — so the general link
allowlist stays locked down while this one trusted destination gets
through. Uses the post-Ventura Full Disk Access anchor.
Also reworks the step's copy to say why the permission is needed and that
it can be revoked once the import is done.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings. Safari's parser interpolated a bare IPv6 host into the
cookie URL (`http://::1/`), which Electron rejects; hosts with a colon are now
bracketed, matching the Chromium reader's cookieScope. And the cookie-database
candidate probes accepted any stat-able entry, so a directory squatting on
`Network/Cookies` would be picked, fail the SQLite open, and shadow a valid
legacy `Cookies` file behind it; every candidate probe now requires a regular
file, in the listing, the counts, and the importer alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stale parent.lock test provided only HOME, but Firefox's win32 root hangs
off %APPDATA% — so the root was undefined, a `!` hid it, and the fixture wrote
a literal `undefined/Profiles/…` tree into the repository instead of the temp
directory. The context now carries an APPDATA under the temp home, keeping the
fixture (and the assertion) inside the sandbox.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cookieless ones
When every profile profiles.ini declared lacked a cookie database, listing returned empty without ever scanning the Profiles directory, so isSourceInstalled reported Firefox absent and hid cookies living in an undeclared profile. The declared path now falls through to the directory scan when it finds nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d route only TCC to Full Disk Access
Two review findings on the Safari reader. The parser validated each declared
page but never checked that the declared pages account for the file, so a jar
with extra undeclared pages imported "successfully" with those cookies
silently missing; it now requires the remainder to be exactly what Safari
writes after the pages — nothing, the 8-byte checksum, or checksum plus a
length-prefixed property list — and refuses anything else. And
`isPermissionDenied` sent ordinary EACCES failures (mode bits, ACLs) to the
Full Disk Access grant, which cannot fix them; only TCC's EPERM routes there
now, and the TCC test injects a real EPERM instead of relying on chmod's
EACCES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch other
A record offset was only checked against the page's end, so one pointing into the page header/offset table, or back into an already-parsed record, would read those bytes as a fabricated cookie. Offsets must now land past the offset table and records may not overlap any accepted so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The listing found Safari's jar with `stat`, which TCC permits without Full
Disk Access, so Safari always listed as ready and the wizard opened on the
configure step — the permission-first flow, the "still required" recheck and
its `resume: "configure"` path could never run, and a refresh could not tell
granted from denied. `unavailableReason` now probes the jar by opening it for
read; TCC's EPERM reports `needsFullDiskAccess`, while a readable jar or any
other failure does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 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.

2 participants

@juliusmarminge@UtkarshUsername