use anyhow::{anyhow, Context, Result};
use colored::Colorize;
use rusqlite::{Connection, OpenFlags};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BrowserType {
Chrome,
Edge,
Firefox,
Brave,
Vivaldi,
Opera,
}
impl BrowserType {
pub fn name(&self) -> &'static str {
match self {
BrowserType::Chrome => "Chrome",
BrowserType::Edge => "Edge",
BrowserType::Firefox => "Firefox",
BrowserType::Brave => "Brave",
BrowserType::Vivaldi => "Vivaldi",
BrowserType::Opera => "Opera",
}
}
#[cfg(windows)]
pub fn profile_path(&self) -> Option<PathBuf> {
let local_app_data = dirs::data_local_dir()?;
let roaming_app_data = dirs::data_dir()?;
let path = match self {
BrowserType::Chrome => local_app_data.join("Google/Chrome/User Data/Default"),
BrowserType::Edge => local_app_data.join("Microsoft/Edge/User Data/Default"),
BrowserType::Brave => {
local_app_data.join("BraveSoftware/Brave-Browser/User Data/Default")
}
BrowserType::Vivaldi => local_app_data.join("Vivaldi/User Data/Default"),
BrowserType::Opera => roaming_app_data.join("Opera Software/Opera Stable"),
BrowserType::Firefox => {
let profiles_dir = roaming_app_data.join("Mozilla/Firefox/Profiles");
if profiles_dir.exists() {
if let Ok(entries) = fs::read_dir(&profiles_dir) {
let mut best_profile: Option<(PathBuf, u64)> = None;
for entry in entries.flatten() {
let profile_path = entry.path();
let cookies_path = profile_path.join("cookies.sqlite");
if cookies_path.exists() {
if let Ok(metadata) = fs::metadata(&cookies_path) {
let size = metadata.len();
if best_profile.as_ref().map_or(true, |(_, s)| size > *s) {
best_profile = Some((profile_path, size));
}
}
}
}
if let Some((path, _)) = best_profile {
return Some(path);
}
}
}
return None;
}
};
if path.exists() {
Some(path)
} else {
None
}
}
#[cfg(not(windows))]
pub fn profile_path(&self) -> Option<PathBuf> {
let home = dirs::home_dir()?;
let path = match self {
BrowserType::Chrome => {
#[cfg(target_os = "macos")]
{
home.join("Library/Application Support/Google/Chrome/Default")
}
#[cfg(target_os = "linux")]
{
home.join(".config/google-chrome/Default")
}
}
BrowserType::Edge => {
#[cfg(target_os = "macos")]
{
home.join("Library/Application Support/Microsoft Edge/Default")
}
#[cfg(target_os = "linux")]
{
home.join(".config/microsoft-edge/Default")
}
}
BrowserType::Firefox => {
#[cfg(target_os = "macos")]
let profiles_dir = home.join("Library/Application Support/Firefox/Profiles");
#[cfg(target_os = "linux")]
let profiles_dir = home.join(".mozilla/firefox");
if profiles_dir.exists() {
if let Ok(entries) = fs::read_dir(&profiles_dir) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.ends_with(".default-release") || name.ends_with(".default") {
return Some(entry.path());
}
}
}
}
return None;
}
BrowserType::Brave => {
#[cfg(target_os = "macos")]
{
home.join("Library/Application Support/BraveSoftware/Brave-Browser/Default")
}
#[cfg(target_os = "linux")]
{
home.join(".config/BraveSoftware/Brave-Browser/Default")
}
}
_ => return None,
};
if path.exists() {
Some(path)
} else {
None
}
}
pub fn cookies_path(&self) -> Option<PathBuf> {
let profile = self.profile_path()?;
match self {
BrowserType::Firefox => {
let path = profile.join("cookies.sqlite");
if path.exists() {
Some(path)
} else {
None
}
}
_ => {
let network_path = profile.join("Network/Cookies");
if network_path.exists() {
return Some(network_path);
}
let old_path = profile.join("Cookies");
if old_path.exists() {
Some(old_path)
} else {
None
}
}
}
}
#[cfg(windows)]
#[allow(dead_code)]
pub fn local_state_path(&self) -> Option<PathBuf> {
let local_app_data = dirs::data_local_dir()?;
let roaming_app_data = dirs::data_dir()?;
let path = match self {
BrowserType::Chrome => local_app_data.join("Google/Chrome/User Data/Local State"),
BrowserType::Edge => local_app_data.join("Microsoft/Edge/User Data/Local State"),
BrowserType::Brave => {
local_app_data.join("BraveSoftware/Brave-Browser/User Data/Local State")
}
BrowserType::Vivaldi => local_app_data.join("Vivaldi/User Data/Local State"),
BrowserType::Opera => roaming_app_data.join("Opera Software/Opera Stable/Local State"),
BrowserType::Firefox => return None, };
if path.exists() {
Some(path)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct ProviderAuth {
pub name: &'static str,
pub domain: &'static str,
pub auth_cookie_names: &'static [&'static str],
#[allow(dead_code)]
pub description: &'static str,
}
pub const WEB_LLM_PROVIDERS: &[ProviderAuth] = &[
ProviderAuth {
name: "ChatGPT",
domain: "chatgpt.com", auth_cookie_names: &[
"__Secure-next-auth.session-token",
"_puid",
"__cf_bm",
"cf_clearance",
],
description: "OpenAI ChatGPT",
},
ProviderAuth {
name: "Claude",
domain: "claude.ai",
auth_cookie_names: &["sessionKey", "__cf_bm"],
description: "Anthropic Claude",
},
ProviderAuth {
name: "Gemini",
domain: "gemini.google.com",
auth_cookie_names: &["SID", "HSID", "SSID"],
description: "Google Gemini",
},
ProviderAuth {
name: "Perplexity",
domain: "perplexity.ai",
auth_cookie_names: &["pplx.visitor-id", "__Secure-next-auth.session-token"],
description: "Perplexity AI",
},
ProviderAuth {
name: "DeepSeek",
domain: "chat.deepseek.com",
auth_cookie_names: &["token", "sessionid"],
description: "DeepSeek Chat",
},
ProviderAuth {
name: "Poe",
domain: "poe.com",
auth_cookie_names: &["p-b", "p-lat"],
description: "Quora Poe",
},
ProviderAuth {
name: "HuggingChat",
domain: "huggingface.co",
auth_cookie_names: &["token", "hf-chat"],
description: "HuggingFace Chat",
},
ProviderAuth {
name: "Copilot",
domain: "copilot.microsoft.com",
auth_cookie_names: &["_U", "MUID"],
description: "Microsoft Copilot",
},
ProviderAuth {
name: "Mistral",
domain: "chat.mistral.ai",
auth_cookie_names: &["__Secure-next-auth.session-token"],
description: "Mistral Le Chat",
},
ProviderAuth {
name: "Cohere",
domain: "coral.cohere.com",
auth_cookie_names: &["session", "auth_token"],
description: "Cohere Coral",
},
ProviderAuth {
name: "Groq",
domain: "groq.com",
auth_cookie_names: &["__Secure-next-auth.session-token"],
description: "Groq Cloud",
},
ProviderAuth {
name: "Phind",
domain: "phind.com",
auth_cookie_names: &["__Secure-next-auth.session-token", "phind-session"],
description: "Phind AI",
},
ProviderAuth {
name: "Character.AI",
domain: "character.ai",
auth_cookie_names: &["token", "web-next-auth.session-token"],
description: "Character.AI",
},
ProviderAuth {
name: "You.com",
domain: "you.com",
auth_cookie_names: &["stytch_session", "youchat_session"],
description: "You.com AI",
},
ProviderAuth {
name: "Pi",
domain: "pi.ai",
auth_cookie_names: &["__Secure-next-auth.session-token"],
description: "Inflection Pi",
},
];
#[derive(Debug, Clone)]
pub struct BrowserAuthResult {
pub browser: BrowserType,
pub provider: String,
pub authenticated: bool,
#[allow(dead_code)]
pub cookies_found: Vec<String>,
}
pub fn scan_browser_auth() -> Vec<BrowserAuthResult> {
scan_browser_auth_internal(false)
}
pub fn scan_browser_auth_verbose() -> Vec<BrowserAuthResult> {
scan_browser_auth_internal(true)
}
fn scan_browser_auth_internal(verbose: bool) -> Vec<BrowserAuthResult> {
let mut results = Vec::new();
let browsers = [
BrowserType::Edge,
BrowserType::Chrome,
BrowserType::Brave,
BrowserType::Firefox,
BrowserType::Vivaldi,
BrowserType::Opera,
];
for browser in browsers {
if let Some(cookies_path) = browser.cookies_path() {
if verbose {
println!(
" {} {} cookies: {}",
"->".dimmed(),
browser.name(),
cookies_path.display()
);
}
match scan_browser_cookies_internal(&browser, &cookies_path, verbose) {
Ok(browser_results) => results.extend(browser_results),
Err(e) => {
if verbose {
println!(" {} Direct access failed: {}", "!".yellow(), e);
println!(" {} Trying copy method...", "->".dimmed());
}
match scan_browser_cookies_with_copy_internal(&browser, &cookies_path, verbose)
{
Ok(browser_results) => results.extend(browser_results),
Err(e2) => {
if verbose {
println!(" {} Copy method also failed: {}", "x".red(), e2);
}
}
}
}
}
}
}
results
}
pub fn get_installed_browsers() -> Vec<BrowserType> {
let browsers = [
BrowserType::Edge,
BrowserType::Chrome,
BrowserType::Brave,
BrowserType::Firefox,
BrowserType::Vivaldi,
BrowserType::Opera,
];
browsers
.into_iter()
.filter(|b| b.profile_path().is_some())
.collect()
}
#[allow(dead_code)]
fn scan_browser_cookies(
browser: &BrowserType,
cookies_path: &PathBuf,
) -> Result<Vec<BrowserAuthResult>> {
scan_browser_cookies_internal(browser, cookies_path, false)
}
fn scan_browser_cookies_internal(
browser: &BrowserType,
cookies_path: &PathBuf,
verbose: bool,
) -> Result<Vec<BrowserAuthResult>> {
let mut results = Vec::new();
let conn = Connection::open_with_flags(
cookies_path,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
)
.context("Failed to open cookie database")?;
let cookies = match *browser {
BrowserType::Firefox => get_firefox_cookies(&conn)?,
_ => get_chromium_cookies(&conn)?,
};
if verbose {
println!(
" {} Found {} domains with cookies",
"->".dimmed(),
cookies.len()
);
let llm_domains: Vec<_> = cookies
.keys()
.filter(|d| {
let dl = d.to_lowercase();
dl.contains("openai")
|| dl.contains("claude")
|| dl.contains("anthropic")
|| dl.contains("google")
|| dl.contains("perplexity")
|| dl.contains("deepseek")
|| dl.contains("poe")
|| dl.contains("huggingface")
|| dl.contains("microsoft")
|| dl.contains("copilot")
|| dl.contains("mistral")
|| dl.contains("cohere")
|| dl.contains("groq")
|| dl.contains("phind")
|| dl.contains("character")
})
.collect();
if !llm_domains.is_empty() {
println!(" {} LLM-related domains found:", "->".dimmed());
for domain in &llm_domains {
let cookie_names = cookies
.get(*domain)
.map(|v| v.join(", "))
.unwrap_or_default();
println!(
" {} {} -> [{}]",
"*".dimmed(),
domain,
cookie_names.dimmed()
);
}
}
}
for provider in WEB_LLM_PROVIDERS {
let domain_cookies: Vec<&String> = cookies
.iter()
.filter(|(domain, _)| {
let domain_clean = domain.trim_start_matches('.');
let provider_domain = provider.domain.trim_start_matches('.');
domain_clean.ends_with(provider_domain) || provider_domain.ends_with(domain_clean)
})
.flat_map(|(_, names)| names)
.collect();
let found_auth_cookies: Vec<String> = provider
.auth_cookie_names
.iter()
.filter(|name| {
domain_cookies
.iter()
.any(|c| c == *name || c.contains(*name))
})
.map(|s| s.to_string())
.collect();
let authenticated = !found_auth_cookies.is_empty();
if verbose && !domain_cookies.is_empty() {
println!(
" {} {}: domain cookies={:?}, auth cookies={:?}, authenticated={}",
"->".dimmed(),
provider.name,
domain_cookies.iter().take(5).collect::<Vec<_>>(),
found_auth_cookies,
authenticated
);
}
results.push(BrowserAuthResult {
browser: *browser,
provider: provider.name.to_string(),
authenticated,
cookies_found: found_auth_cookies,
});
}
Ok(results)
}
#[allow(dead_code)]
fn scan_browser_cookies_with_copy(
browser: &BrowserType,
cookies_path: &PathBuf,
) -> Result<Vec<BrowserAuthResult>> {
scan_browser_cookies_with_copy_internal(browser, cookies_path, false)
}
fn scan_browser_cookies_with_copy_internal(
browser: &BrowserType,
cookies_path: &PathBuf,
verbose: bool,
) -> Result<Vec<BrowserAuthResult>> {
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join(format!("csm_cookies_{}.db", uuid::Uuid::new_v4()));
fs::copy(cookies_path, &temp_path).context("Failed to copy cookie database")?;
let wal_path = cookies_path.with_extension("db-wal");
if wal_path.exists() {
let _ = fs::copy(&wal_path, temp_path.with_extension("db-wal"));
}
let shm_path = cookies_path.with_extension("db-shm");
if shm_path.exists() {
let _ = fs::copy(&shm_path, temp_path.with_extension("db-shm"));
}
let ff_wal = cookies_path.with_file_name(format!(
"{}-wal",
cookies_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
));
if ff_wal.exists() {
let _ = fs::copy(
&ff_wal,
temp_dir.join(format!("csm_cookies_{}.db-wal", uuid::Uuid::new_v4())),
);
}
if verbose {
println!(
" {} Copied to temp: {}",
"->".dimmed(),
temp_path.display()
);
}
let result = scan_browser_cookies_internal(browser, &temp_path, verbose);
let _ = fs::remove_file(&temp_path);
let _ = fs::remove_file(temp_path.with_extension("db-wal"));
let _ = fs::remove_file(temp_path.with_extension("db-shm"));
result
}
fn get_chromium_cookies(conn: &Connection) -> Result<HashMap<String, Vec<String>>> {
let mut cookies: HashMap<String, Vec<String>> = HashMap::new();
let mut stmt = conn.prepare(
"SELECT host_key, name FROM cookies WHERE host_key LIKE '%.%' GROUP BY host_key, name",
)?;
let rows = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
for row in rows.flatten() {
let (host, name) = row;
cookies.entry(host).or_default().push(name);
}
Ok(cookies)
}
fn get_firefox_cookies(conn: &Connection) -> Result<HashMap<String, Vec<String>>> {
let mut cookies: HashMap<String, Vec<String>> = HashMap::new();
let mut stmt = conn
.prepare("SELECT host, name FROM moz_cookies WHERE host LIKE '%.%' GROUP BY host, name")?;
let rows = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
for row in rows.flatten() {
let (host, name) = row;
cookies.entry(host).or_default().push(name);
}
Ok(cookies)
}
#[allow(dead_code)]
#[derive(Debug, Default)]
pub struct AuthSummary {
pub browsers_checked: Vec<BrowserType>,
pub authenticated_providers: HashMap<String, Vec<BrowserType>>,
pub total_providers_authenticated: usize,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ExtractedCookie {
pub name: String,
pub value: String,
pub domain: String,
pub browser: BrowserType,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct ProviderCredentials {
pub provider: String,
pub session_token: Option<String>,
pub cookies: HashMap<String, String>,
pub browser: Option<BrowserType>,
}
pub fn extract_provider_cookies(provider_name: &str) -> Option<ProviderCredentials> {
let provider_auth = WEB_LLM_PROVIDERS
.iter()
.find(|p| p.name.eq_ignore_ascii_case(provider_name))?;
let domains_to_try: Vec<&str> = if provider_name.eq_ignore_ascii_case("chatgpt") {
vec!["chatgpt.com", "openai.com", "chat.openai.com"]
} else {
vec![provider_auth.domain]
};
let browsers = [
BrowserType::Edge,
BrowserType::Chrome,
BrowserType::Brave,
BrowserType::Firefox,
BrowserType::Vivaldi,
BrowserType::Opera,
];
for browser in browsers {
if let Some(cookies_path) = browser.cookies_path() {
for domain in &domains_to_try {
if let Ok(cookies) = extract_cookies_for_domain(&browser, &cookies_path, domain) {
if !cookies.is_empty() {
let mut creds = ProviderCredentials {
provider: provider_name.to_string(),
session_token: None,
cookies: HashMap::new(),
browser: Some(browser),
};
for cookie in &cookies {
if provider_auth
.auth_cookie_names
.iter()
.any(|name| cookie.name.contains(name))
&& (cookie.name.contains("session")
|| cookie.name.contains("token"))
{
creds.session_token = Some(cookie.value.clone());
}
creds
.cookies
.insert(cookie.name.clone(), cookie.value.clone());
}
if creds.session_token.is_some() || !creds.cookies.is_empty() {
return Some(creds);
}
}
}
}
}
}
None
}
fn extract_cookies_for_domain(
browser: &BrowserType,
cookies_path: &PathBuf,
domain: &str,
) -> Result<Vec<ExtractedCookie>> {
match extract_cookies_internal(browser, cookies_path, domain) {
Ok(cookies) => Ok(cookies),
Err(_) => {
extract_cookies_with_copy(browser, cookies_path, domain)
}
}
}
fn extract_cookies_internal(
browser: &BrowserType,
cookies_path: &PathBuf,
domain: &str,
) -> Result<Vec<ExtractedCookie>> {
let conn = Connection::open_with_flags(
cookies_path,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
)
.context("Failed to open cookie database")?;
match browser {
BrowserType::Firefox => extract_firefox_cookie_values(&conn, domain, browser),
_ => extract_chromium_cookie_values(&conn, domain, browser),
}
}
fn extract_cookies_with_copy(
browser: &BrowserType,
cookies_path: &PathBuf,
domain: &str,
) -> Result<Vec<ExtractedCookie>> {
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join(format!("csm_cookies_extract_{}.db", uuid::Uuid::new_v4()));
fs::copy(cookies_path, &temp_path).context("Failed to copy cookie database")?;
let wal_path = cookies_path.with_extension("db-wal");
if wal_path.exists() {
let _ = fs::copy(&wal_path, temp_path.with_extension("db-wal"));
}
let shm_path = cookies_path.with_extension("db-shm");
if shm_path.exists() {
let _ = fs::copy(&shm_path, temp_path.with_extension("db-shm"));
}
let result = extract_cookies_internal(browser, &temp_path, domain);
let _ = fs::remove_file(&temp_path);
let _ = fs::remove_file(temp_path.with_extension("db-wal"));
let _ = fs::remove_file(temp_path.with_extension("db-shm"));
result
}
fn extract_firefox_cookie_values(
conn: &Connection,
domain: &str,
browser: &BrowserType,
) -> Result<Vec<ExtractedCookie>> {
let mut cookies = Vec::new();
let mut stmt =
conn.prepare("SELECT name, value, host FROM moz_cookies WHERE host LIKE ? OR host LIKE ?")?;
let domain_pattern = format!("%{}", domain);
let dot_domain_pattern = format!("%.{}", domain);
let rows = stmt.query_map([&domain_pattern, &dot_domain_pattern], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
))
})?;
for row in rows.flatten() {
let (name, value, host) = row;
if !value.is_empty() {
cookies.push(ExtractedCookie {
name,
value,
domain: host,
browser: *browser,
});
}
}
Ok(cookies)
}
fn extract_chromium_cookie_values(
conn: &Connection,
domain: &str,
browser: &BrowserType,
) -> Result<Vec<ExtractedCookie>> {
let mut cookies = Vec::new();
let mut stmt = conn.prepare(
"SELECT name, value, encrypted_value, host_key FROM cookies WHERE host_key LIKE ? OR host_key LIKE ?"
)?;
let domain_pattern = format!("%{}", domain);
let dot_domain_pattern = format!("%.{}", domain);
let rows = stmt.query_map([&domain_pattern, &dot_domain_pattern], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Vec<u8>>(2)?,
row.get::<_, String>(3)?,
))
})?;
for row in rows.flatten() {
let (name, value, encrypted_value, host) = row;
let cookie_value = if !value.is_empty() {
value
} else if !encrypted_value.is_empty() {
match decrypt_chromium_cookie(&encrypted_value, browser) {
Ok(decrypted) => decrypted,
Err(_) => continue, }
} else {
continue;
};
if !cookie_value.is_empty() {
cookies.push(ExtractedCookie {
name,
value: cookie_value,
domain: host,
browser: *browser,
});
}
}
Ok(cookies)
}
#[cfg(windows)]
fn decrypt_chromium_cookie(encrypted_value: &[u8], browser: &BrowserType) -> Result<String> {
use windows::Win32::Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB};
if encrypted_value.len() > 3 && &encrypted_value[0..3] == b"v10"
|| &encrypted_value[0..3] == b"v20"
{
if let Some(key) = get_chromium_encryption_key(browser) {
return decrypt_aes_gcm(&encrypted_value[3..], &key);
}
}
unsafe {
#[allow(unused_mut)]
let input = CRYPT_INTEGER_BLOB {
cbData: encrypted_value.len() as u32,
pbData: encrypted_value.as_ptr() as *mut u8,
};
let mut output = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: std::ptr::null_mut(),
};
let result = CryptUnprotectData(&input, None, None, None, None, 0, &mut output);
if result.is_ok() && !output.pbData.is_null() {
let slice = std::slice::from_raw_parts(output.pbData, output.cbData as usize);
let decrypted = String::from_utf8_lossy(slice).to_string();
return Ok(decrypted);
}
}
Err(anyhow!("Failed to decrypt cookie"))
}
#[cfg(windows)]
fn get_chromium_encryption_key(browser: &BrowserType) -> Option<Vec<u8>> {
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use windows::Win32::Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB};
let local_state_path = browser.local_state_path()?;
let local_state_content = fs::read_to_string(&local_state_path).ok()?;
let local_state: serde_json::Value = serde_json::from_str(&local_state_content).ok()?;
let encrypted_key_b64 = local_state
.get("os_crypt")?
.get("encrypted_key")?
.as_str()?;
let encrypted_key = BASE64.decode(encrypted_key_b64).ok()?;
if encrypted_key.len() <= 5 || &encrypted_key[0..5] != b"DPAPI" {
return None;
}
let encrypted_key = &encrypted_key[5..];
unsafe {
#[allow(unused_mut)]
let input = CRYPT_INTEGER_BLOB {
cbData: encrypted_key.len() as u32,
pbData: encrypted_key.as_ptr() as *mut u8,
};
let mut output = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: std::ptr::null_mut(),
};
let result = CryptUnprotectData(&input, None, None, None, None, 0, &mut output);
if result.is_ok() && !output.pbData.is_null() {
let key = std::slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
return Some(key);
}
}
None
}
#[cfg(windows)]
fn decrypt_aes_gcm(encrypted_data: &[u8], key: &[u8]) -> Result<String> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
if encrypted_data.len() < 12 + 16 {
return Err(anyhow!("Encrypted data too short"));
}
let nonce = Nonce::from_slice(&encrypted_data[0..12]);
let ciphertext = &encrypted_data[12..];
let cipher =
Aes256Gcm::new_from_slice(key).map_err(|e| anyhow!("Failed to create cipher: {}", e))?;
let plaintext = cipher
.decrypt(nonce, ciphertext)
.map_err(|e| anyhow!("Decryption failed: {}", e))?;
String::from_utf8(plaintext).map_err(|e| anyhow!("Invalid UTF-8 in decrypted cookie: {}", e))
}
#[cfg(not(windows))]
fn decrypt_chromium_cookie(encrypted_value: &[u8], _browser: &BrowserType) -> Result<String> {
if let Ok(s) = String::from_utf8(encrypted_value.to_vec()) {
if s.chars()
.all(|c| c.is_ascii_graphic() || c.is_ascii_whitespace())
{
return Ok(s);
}
}
Err(anyhow!(
"Cookie decryption not implemented for this platform"
))
}
#[allow(dead_code)]
pub fn get_auth_summary() -> AuthSummary {
let results = scan_browser_auth();
let mut summary = AuthSummary::default();
let mut browsers_seen = std::collections::HashSet::new();
for result in results {
browsers_seen.insert(result.browser);
if result.authenticated {
summary
.authenticated_providers
.entry(result.provider)
.or_default()
.push(result.browser);
}
}
summary.browsers_checked = browsers_seen.into_iter().collect();
summary.total_providers_authenticated = summary.authenticated_providers.len();
summary
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_browser_type_name() {
assert_eq!(BrowserType::Chrome.name(), "Chrome");
assert_eq!(BrowserType::Edge.name(), "Edge");
assert_eq!(BrowserType::Firefox.name(), "Firefox");
}
#[test]
fn test_get_installed_browsers() {
let browsers = get_installed_browsers();
assert!(browsers.len() <= 6);
}
#[test]
fn test_provider_auth_domains() {
for provider in WEB_LLM_PROVIDERS {
assert!(!provider.domain.is_empty());
assert!(provider.domain.contains('.'));
assert!(!provider.auth_cookie_names.is_empty());
}
}
#[test]
fn test_auth_summary_default() {
let summary = AuthSummary::default();
assert!(summary.browsers_checked.is_empty());
assert!(summary.authenticated_providers.is_empty());
assert_eq!(summary.total_providers_authenticated, 0);
}
}