Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions apps/desktop-tauri/src-tauri/src/commands/browser_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn import_browser_cookies(
provider_id: String,
browser_type: String,
) -> Result<Vec<CookieInfoBridge>, String> {
use codexbar::browser::cookies::{CookieError, CookieExtractor};
use codexbar::browser::cookies::CookieExtractor;
use codexbar::browser::detection::BrowserDetector;

// Resolve the provider to get its cookie domain.
Expand All @@ -66,10 +66,8 @@ pub fn import_browser_cookies(
.ok_or_else(|| format!("Browser '{browser_type}' not found or not installed"))?;

// Extract the cookie header.
let cookies = CookieExtractor::extract_for_domain(&browser, domain).map_err(|e| match e {
CookieError::Dpapi(msg) => format!("DPAPI error: {msg}"),
other => other.to_string(),
})?;
let cookies = CookieExtractor::extract_for_domain(&browser, domain)
.map_err(|e| e.user_message_for_browser(browser.browser_type))?;

if cookies.is_empty() {
return Err(format!(
Expand Down
17 changes: 9 additions & 8 deletions docs/COOKIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

Win-CodexBar can extract browser cookies for providers that use web authentication (Claude, Cursor, Kimi, and others). This is the Windows rewrite of upstream cookie/Keychain concepts: **DPAPI + browser profiles**, not macOS Keychain prompts.


## Supported Browsers

| Browser | Encryption | Status |
|---------|-----------|--------|
| Chrome | DPAPI + AES-256-GCM | ✅ Automatic |
| Edge | DPAPI + AES-256-GCM | ✅ Automatic |
| Brave | DPAPI + AES-256-GCM | ✅ Automatic |
| Chrome | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound |
| Edge | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound |
| Brave | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound |
| Firefox | Unencrypted SQLite | ✅ Automatic |

Chromium App-Bound Encryption (ABE) binds protected cookie keys to the browser installation. Win-CodexBar does not bypass that protection. If the selected Chromium profile stores the provider cookies as App-Bound `v20` values, automatic import cannot decrypt them with the normal user DPAPI key. Use a manual Cookie header or Firefox instead. Chromium browser choices remain available because older or unmigrated profiles can still contain readable DPAPI/AES-GCM cookies.

## How It Works

1. CodexBar reads the browser's cookie database from its standard location
2. For Chromium-based browsers, cookies are encrypted with Windows DPAPI — CodexBar decrypts them using the current user's credentials
2. For Chromium-based browsers, CodexBar can decrypt legacy DPAPI/AES-GCM cookies using the current user's credentials; App-Bound `v20` cookies are intentionally not bypassed
3. Only cookies for enabled providers are extracted (e.g., `claude.ai`, `cursor.com`)
4. Cookies are stored in-memory and refreshed on each provider poll

Expand All @@ -28,7 +29,7 @@ Win-CodexBar can extract browser cookies for providers that use web authenticati

## Manual Cookies

If automatic extraction fails (e.g., browser is locked, profile is encrypted, or running in WSL):
If automatic extraction fails (for example, Chromium App-Bound Encryption is active, the browser database cannot be read, or CodexBar is running in WSL):

1. Open your browser and navigate to the provider's website (e.g., `claude.ai`)
2. Open DevTools (F12) → **Network** tab
Expand All @@ -38,7 +39,8 @@ If automatic extraction fails (e.g., browser is locked, profile is encrypted, or

## Troubleshooting

- **"Cookie decryption failed"**: Close the browser and retry — some browsers lock the cookie database while running
- **"Chromium App-Bound Encryption"**: Modern Chrome, Edge, Brave, and other Chromium-based profiles can protect cookies with ABE. Closing the browser does not remove ABE; use a manual Cookie header or Firefox for the same login
- **"Cookie decryption failed"**: Close the browser and retry if the cookie database itself is locked
- **Empty cookies**: Make sure you're logged into the provider's web interface in that browser
- **WSL**: Chromium DPAPI cookies cannot be decrypted from WSL. Use manual cookies or CLI-based auth instead

Expand All @@ -47,4 +49,3 @@ If automatic extraction fails (e.g., browser is locked, profile is encrypted, or
- [CONFIGURATION.md](./CONFIGURATION.md) — where manual cookies and settings live on disk
- [PROVIDERS.md](./PROVIDERS.md) — web vs cli vs oauth sources
- [WSL.md](./WSL.md) — why automatic Chromium decrypt fails in WSL

56 changes: 43 additions & 13 deletions rust/src/browser/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use base64::Engine;
use rusqlite::Connection;
use thiserror::Error;

use super::detection::{BrowserProfile, DetectedBrowser};
use super::detection::{BrowserProfile, BrowserType, DetectedBrowser};

/// Errors that can occur during cookie extraction
#[derive(Debug, Error)]
Expand Down Expand Up @@ -46,16 +46,32 @@ pub enum CookieError {

/// Chromium App-Bound Encryption (ABE) is protecting cookie values.
/// The user-level DPAPI key in Local State can no longer decrypt cookies encrypted
/// after the ABE migration. Modern Chrome and Edge write these cookies with a
/// `v20` prefix; older migrated profiles can also fail every AES-GCM decrypt while
/// exposing `app_bound_encrypted_key` in Local State.
/// after the ABE migration. Modern Chromium-based browsers can write these cookies
/// with a `v20` prefix; older migrated profiles can also fail every AES-GCM decrypt
/// while exposing `app_bound_encrypted_key` in Local State.
#[error(
"Chrome/Edge App-Bound Encryption is blocking automatic browser import. \
"Chromium App-Bound Encryption is blocking automatic browser import. \
Paste the Cookie header manually, or use Firefox if that browser has the same login."
)]
AppBoundEncryption,
}

impl CookieError {
/// Format a browser-specific message for UI surfaces that know which browser
/// the user selected. ABE is a Chromium capability boundary, so name the
/// concrete browser instead of implying the problem only affects Chrome/Edge.
pub fn user_message_for_browser(&self, browser_type: BrowserType) -> String {
match self {
Self::AppBoundEncryption => format!(
"{} is using Chromium App-Bound Encryption, which is blocking automatic cookie import for this profile. \
Paste the Cookie header manually, or use Firefox if that browser has the same login.",
browser_type.display_name()
),
_ => self.to_string(),
}
}
}

/// A browser cookie
#[derive(Debug, Clone)]
pub struct Cookie {
Expand Down Expand Up @@ -136,9 +152,9 @@ impl CookieExtractor {
}
}

/// Detect whether Chrome App-Bound Encryption (ABE, Chrome 127+) is active for
/// Detect whether Chromium App-Bound Encryption (ABE) is active for
/// this browser profile by checking for the `app_bound_encrypted_key` field in
/// the Local State JSON. The field is written by Chrome when it migrates the
/// the Local State JSON. Chromium browsers write this field when migrating the
/// cookie-encryption key to the ABE system; its presence means the user-level
/// DPAPI key stored in `encrypted_key` will no longer decrypt newly written
/// cookies.
Expand All @@ -154,7 +170,7 @@ impl CookieExtractor {
.and_then(|v| v.get("app_bound_encrypted_key"))
.is_some();
if present {
tracing::debug!("Chrome App-Bound Encryption detected in Local State");
tracing::debug!("Chromium App-Bound Encryption detected in Local State");
}
present
}
Expand Down Expand Up @@ -275,7 +291,7 @@ impl CookieExtractor {
let _cleanup = std::fs::remove_file(&temp_db);

// If every candidate cookie failed to decrypt and no cookies were recovered,
// check whether Chrome App-Bound Encryption (Chrome 127+) is the culprit.
// check whether Chromium App-Bound Encryption is the culprit.
// ABE replaces the user-level DPAPI cookie key with a system-level key that
// cannot be read by third-party tools, causing systematic AES-GCM auth failures.
if cookies.is_empty()
Expand Down Expand Up @@ -416,7 +432,7 @@ impl CookieExtractor {
let has_v20_prefix = encrypted_value.len() >= 3 && &encrypted_value[0..3] == b"v20";

if has_v20_prefix {
// Chrome/Edge 127+ on Windows use App-Bound Encryption for these
// Modern Chromium-based browsers on Windows use App-Bound Encryption for these
// cookies. Treating the blob as old DPAPI data produces a misleading
// DPAPI or "no cookies found" error even though the user is signed in.
return Err(CookieError::AppBoundEncryption);
Expand Down Expand Up @@ -672,8 +688,8 @@ pub fn get_cookies_for_domain(domain: &str) -> Result<Vec<Cookie>, CookieError>
}
Ok(_) => continue,
Err(CookieError::AppBoundEncryption) => {
// Chrome ABE is blocking this browser; log a warning and keep
// trying Edge / Firefox which are unaffected by ABE.
// Chromium ABE is blocking this browser; log a warning and keep
// trying the remaining browsers; Firefox does not use Chromium ABE.
tracing::warn!(
browser = %browser.browser_type.display_name(),
"App-Bound Encryption prevents automatic cookie import; \
Expand Down Expand Up @@ -784,7 +800,7 @@ mod tests {
"ABE error should mention App-Bound Encryption"
);
assert!(
msg.contains("Chrome/Edge"),
msg.contains("Chromium"),
"ABE error should identify Chromium browsers"
);
assert!(
Expand All @@ -793,6 +809,20 @@ mod tests {
);
}

#[test]
fn test_abe_error_names_selected_brave_browser() {
let msg = CookieError::AppBoundEncryption.user_message_for_browser(BrowserType::Brave);
assert!(msg.contains("Brave"), "ABE error should name Brave");
assert!(
msg.contains("Chromium App-Bound Encryption"),
"ABE error should explain the Chromium mechanism"
);
assert!(
msg.contains("Paste") || msg.contains("manual"),
"ABE error should preserve an actionable fallback"
);
}

/// Verify that modern Chromium `v20` cookies are recognized as App-Bound
/// Encryption instead of being misrouted through legacy DPAPI decryption.
#[test]
Expand Down