Skip to content

fix(desktop): don't abort the login import on a cookie row that isn't a buffer - #6788

Merged
saddlepaddle merged 1 commit into
mainfrom
sentry/cookie-import-nonbuffer
Aug 23, 2026
Merged

saddlepaddle merged 1 commit into
mainfrom
sentry/cookie-import-nonbuffer

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Aug 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Someone opens Import settings from another browser, ticks logins, and presses Import. The mutation throws and they get a red Could not import from browser — Could not import browser cookies: encryptedValue.subarray is not a function toast. Nothing is imported, and retrying is futile: the same profile row fails every time. The reporting person pressed the button 6 times in 33 seconds and got nowhere. If they also ticked history, the history import runs first and succeeds, but the cookie throw happens before the success toast — so a partly-successful import is still reported to them as a total failure.

Root cause

decryptCookieValue documents that it "returns null for values in any other scheme", and has a try/catch implementing exactly that. But the "v10" scheme check sat above that try:

if (
  encryptedValue.length < 3 ||
  encryptedValue.subarray(0, 3).toString() !== "v10"
) return null;
try { /* decrypt */ } catch { return null; }

.length succeeds for anything with a length; .subarray only exists on a Buffer. The rows come from a raw better-sqlite3 .all() cast straight to ChromeCookieRow[], so encrypted_value: Buffer is an assumption about the profile database, not a guarantee — SQLite is dynamically typed, and a value stored in that column as TEXT comes back as a JavaScript string. On such a row the TypeError escaped decryptCookieValue, escaped mapCookieRow, aborted the for loop in readCookiesFromProfile, and surfaced as a 500 from the tRPC mutation.

Fix

Move the scheme check inside the existing try. One row that isn't a decryptable buffer is now skipped like any other undecryptable row — which is what the function already promised. Nothing else changes: the try body is untouched, and a successfully decrypted cookie produces exactly what it did before.

The only behaviour that changes is for input that previously threw. No row-shape validation, no per-field error handling, no new capture.

What still fails loudly

Deliberately unchanged, because these mean "we imported nothing", not "we skipped one cookie":

  • Profile database can't be opened (new Database(..., { fileMustExist: true })) — throws, reaches the tRPC catch, error toast.
  • Profile can't be copied (permissions / no Full Disk Access on the copyFileSync) — same.
  • The cookies table/columns aren't what we expect (SELECT fails) — same.
  • Profile no longer on disk — NOT_FOUND, "That browser profile is no longer available."

One correction on the framing worth stating plainly: a denied Keychain key never threw, before or after this change. readSafeStorageKey returns null on denial, readCookiesFromProfile returns [], and importCookiesIntoSession reports keyUnavailable: true, which the dialog renders as "logins skipped (Keychain access denied)". That's a distinct, visible signal rather than an error toast, and it is preserved untouched — it is not swallowed by this fix.

Typed causes

None added. Nothing reads a cause.kind on this path — the renderer branches on the keyUnavailable field, not on error shape — so the existing plain new TRPCError({ code, message }) at the router stays as it is.

Verification

bunx biome check on both changed files:

Checked 2 files in 15ms. No fixes applied.

cd apps/desktop && bun run typecheck:

$ tsr generate
$ tsc --noEmit
EXIT=0

Two tests added, using the shape the failure actually arrives in (a string where a Buffer was declared). Before the change, both fail with the exact error from the issue:

114 | 		encryptedValue.length < 3 ||
115 | 		encryptedValue.subarray(0, 3).toString() !== "v10"
                   ^
TypeError: encryptedValue.subarray is not a function.
  at decryptCookieValue (.../chrome-cookie-import.ts:115:18)
(fail) decryptCookieValue > returns null when the stored value is text rather than a buffer
  at decryptCookieValue (.../chrome-cookie-import.ts:115:18)
  at mapCookieRow (.../chrome-cookie-import.ts:193:5)
(fail) mapCookieRow > drops a row whose stored value is text rather than a buffer

 9 pass
 2 fail
Ran 11 tests across 1 file.

After, the file passes, and so does the whole src/main/lib/browser/ directory — including the pre-existing tests that assert real v10 buffers still decrypt, so the skip has not widened into swallowing valid cookies:

bun test src/main/lib/browser/chrome-cookie-import.test.ts
 11 pass
 0 fail
 22 expect() calls

bun test src/main/lib/browser/
 68 pass
 0 fail
 148 expect() calls
Ran 68 tests across 8 files.

Repo-wide bun run lint was not run (it hangs on cross-worktree bun locks); biome check on the changed files stands in for it.

Refs DESKTOP-11J

https://claude.ai/code/session_b1c13bee-c201-4c37-bf2a-d43d29438086


Summary by cubic

Skips non-buffer cookie rows during login import to prevent a hard failure. Previously, decryptCookieValue checked the "v10" scheme before its try/catch and threw on string rows, aborting the import; now the check runs inside the guard, so undecryptable or non-buffer rows return null and are skipped.

  • Notes for review
    • Only change: move the scheme guard inside the existing try/catch in decryptCookieValue. Valid "v10" buffers still decrypt unchanged.
    • Expected loud failures remain loud (profile open/copy/SELECT issues). Keychain denial still sets keyUnavailable: true.
    • Tests added for string-backed encrypted_value from better-sqlite3.
    • Addresses DESKTOP-11J by ensuring mixed-type cookie rows don’t abort login import.

Written for commit 8aa0bc4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved browser cookie import handling for invalid encrypted cookie values.
    • Invalid text-based cookie data is now safely rejected instead of causing errors or being processed incorrectly.
  • Tests

    • Added coverage confirming invalid cookie values return no result during decryption and import.

… a buffer

decryptCookieValue's "v10" scheme check ran before its own try/catch, so a
cookie row whose encrypted_value came back as a string threw
`encryptedValue.subarray is not a function` out of mapCookieRow, killed the
read loop, and failed the whole import. Move the check inside the guard so
such a row is skipped like any other undecryptable one.

Refs DESKTOP-11J
@coderabbitai

coderabbitai Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: deacd3bc-4899-4f9d-a526-707b5e65290e

📥 Commits

Reviewing files that changed from the base of the PR and between 5d87372 and 8aa0bc4.

📒 Files selected for processing (2)
  • apps/desktop/src/main/lib/browser/chrome-cookie-import.test.ts
  • apps/desktop/src/main/lib/browser/chrome-cookie-import.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The cookie import path now rejects text-valued encrypted fields by returning null. Tests cover both direct decryption and cookie-row mapping.

Changes

Cookie validation

Layer / File(s) Summary
Reject malformed encrypted values
apps/desktop/src/main/lib/browser/chrome-cookie-import.ts, apps/desktop/src/main/lib/browser/chrome-cookie-import.test.ts
decryptCookieValue catches invalid values without Buffer methods and returns null. Tests verify that string encrypted values are rejected during decryption and row mapping.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 8aa0b

The PR skips malformed cookie rows instead of aborting the entire login import while preserving successful decryption and existing loud failures; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: kitenite

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and clearly describes the fix for cookie rows that are not buffers.
Description check ✅ Passed The description explains the problem, root cause, fix, expected behavior, testing, and remaining failure cases in sufficient detail.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentry/cookie-import-nonbuffer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@saddlepaddle
saddlepaddle merged commit fd578b4 into main Aug 23, 2026
13 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle
saddlepaddle deleted the sentry/cookie-import-nonbuffer branch August 23, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant