Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
f40233f
initial sandbox implementation for mac app store builds
harr1424 Nov 18, 2025
aa42630
chromium importer working in sandbox
harr1424 Nov 19, 2025
4539589
add import-chrome-component.ts to working tree
harr1424 Nov 19, 2025
61b2624
move sandbox code into macos.rs
harr1424 Nov 20, 2025
18064ef
add/improve comments and logging
harr1424 Nov 20, 2025
83c01ba
added docs and removed debug output
harr1424 Nov 21, 2025
245171e
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Nov 21, 2025
4be25d9
remove additional debug output and revert popup-modal-style to match โ€ฆ
harr1424 Nov 21, 2025
5fc55c8
Merge branch 'PM-26250-Explore-options-to-enable-direct-importer-for-โ€ฆ
harr1424 Nov 21, 2025
f55e699
add dep to desktop_native cargo.toml
harr1424 Nov 21, 2025
61ace26
cargo fmt
harr1424 Nov 21, 2025
60eb309
avoid unwrap
harr1424 Nov 21, 2025
be94525
pass args directly in objc/build.rs
harr1424 Nov 21, 2025
46d24d3
cargo clippy
harr1424 Nov 21, 2025
f14a6f4
fix another clippy warning
harr1424 Nov 21, 2025
abf8991
remove unused dep
harr1424 Nov 21, 2025
9fbf45e
fix more clippy lints not caught locally
harr1424 Nov 21, 2025
f900acf
re-run cargo fmt after fixing clippy lints
harr1424 Nov 21, 2025
7a25b40
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Nov 21, 2025
7e11c22
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Nov 25, 2025
5625efb
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Nov 25, 2025
9719210
replace swift logic with objc
harr1424 Nov 25, 2025
2a30cf6
Merge branch 'PM-26250-Explore-options-to-enable-direct-importer-for-โ€ฆ
harr1424 Nov 25, 2025
3b329e2
rename files to avoid uppercase letters
harr1424 Nov 25, 2025
9df4928
rename references
harr1424 Nov 25, 2025
b68f685
use progress spinner for imports
harr1424 Nov 26, 2025
29080c8
improved error display
harr1424 Nov 26, 2025
be86360
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Nov 29, 2025
97223e6
respond to review comments:
harr1424 Nov 30, 2025
34c013e
pass new bool value on other platforms
harr1424 Nov 30, 2025
618d1dc
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 2, 2025
b58207e
remove sandbox feature flag and refactor to use bool
harr1424 Dec 2, 2025
de0d8e2
fix cargo fmt
harr1424 Dec 2, 2025
2ce203e
fix clippy error
harr1424 Dec 2, 2025
d0c0da3
fix unused variable outside of macos build
harr1424 Dec 2, 2025
d899048
fix lints on linux build
harr1424 Dec 2, 2025
084a291
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 2, 2025
dc9b05b
eval Claude's comments and address legitimate findings
harr1424 Dec 2, 2025
f773081
clippy lints
harr1424 Dec 2, 2025
84d588f
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 3, 2025
70b4993
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 4, 2025
71009f7
remove callout and revert to previous error display
harr1424 Dec 4, 2025
7beed6e
Merge branch 'PM-26250-Explore-options-to-enable-direct-importer-for-โ€ฆ
harr1424 Dec 4, 2025
16a0aad
adapt chromium_importer to match conventions established by autofill:
harr1424 Dec 4, 2025
1e0988f
clippy & fmt
harr1424 Dec 4, 2025
640efd1
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 4, 2025
d4ae112
remove ability for user to select dir different to browser
harr1424 Dec 4, 2025
5840c32
Merge branch 'PM-26250-Explore-options-to-enable-direct-importer-for-โ€ฆ
harr1424 Dec 4, 2025
02aa275
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 4, 2025
00689b7
use new function name introduced in merge
harr1424 Dec 4, 2025
e75dfe5
disable clippy warning on conditionally compiled code block
harr1424 Dec 4, 2025
13547d5
fix breaking changes caused by PM-27081
harr1424 Dec 5, 2025
673dc96
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 5, 2025
9ff43a6
fix more lints
harr1424 Dec 5, 2025
386cf03
rework error handling & presentation
harr1424 Dec 5, 2025
2f0ade2
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 5, 2025
9e52660
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 8, 2025
63af80c
use bit-spinner instead of bwi-spin
harr1424 Dec 8, 2025
3f4f33b
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 8, 2025
7d496df
remove comment related to sandbox flag (outdated)
harr1424 Dec 8, 2025
fb00a80
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 9, 2025
d38aa1a
address review comments
harr1424 Dec 9, 2025
9e71c89
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 9, 2025
7e270b6
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 12, 2025
1a018fb
revert breaking changes from review suggestions
harr1424 Dec 15, 2025
4e9ed04
fix syntax error
harr1424 Dec 15, 2025
cf12ef6
cargo fmt
harr1424 Dec 15, 2025
be7f066
safe changes related to review feedback (checkpoint)
harr1424 Dec 15, 2025
3899314
address remaining review comments
harr1424 Dec 15, 2025
fb860cd
address review comments
harr1424 Dec 16, 2025
9c4291d
address review comments
harr1424 Dec 19, 2025
82e78ab
resolve conflict with main branch
harr1424 Dec 19, 2025
7b897b4
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Dec 19, 2025
6be17c5
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Jan 12, 2026
ce77d2f
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Feb 4, 2026
a14ee3f
restore mas_build arg after napi split
harr1424 Feb 18, 2026
328129d
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Feb 18, 2026
f1075b0
merge latest main
harr1424 Feb 23, 2026
0baec16
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Feb 25, 2026
7f5c106
resolve merge conflicts
harr1424 Apr 22, 2026
843b66b
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Apr 29, 2026
af15ec9
resolve conflicts and ai review
harr1424 Apr 29, 2026
ddac378
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Apr 29, 2026
6086508
use sandboxed dir for path resolution
harr1424 Apr 30, 2026
d6cd57b
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Apr 30, 2026
e33dbdf
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Apr 30, 2026
d454a5d
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 Apr 30, 2026
0f342dc
remove empty clean file
harr1424 May 5, 2026
6244ba9
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 May 5, 2026
1633085
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-imโ€ฆ
harr1424 May 5, 2026
8fb1c1e
refactor per ai review
harr1424 May 5, 2026
17ac926
use i18n for NSOpenPanel dialog
harr1424 May 5, 2026
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ apps/browser/src/tools @bitwarden/team-tools-dev
apps/cli/src/tools @bitwarden/team-tools-dev
apps/desktop/desktop_native/bitwarden_chromium_import_helper @bitwarden/team-tools-dev
apps/desktop/desktop_native/chromium_importer @bitwarden/team-tools-dev
apps/desktop/desktop_native/objc/src/native/chromium_importer @bitwarden/team-tools-dev
apps/desktop/src/app/tools @bitwarden/team-tools-dev
apps/web/src/app/tools @bitwarden/team-tools-dev
libs/angular/src/tools @bitwarden/team-tools-dev
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/desktop_native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/chromium_importer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ sha1 = "=0.10.6"

[target.'cfg(target_os = "macos")'.dependencies]
cbc = { workspace = true, features = ["alloc"] }
desktop_objc = { path = "../objc" }
pbkdf2 = "=0.12.2"
security-framework = { workspace = true }
sha1 = "=0.10.6"
tokio = { workspace = true, features = ["rt"] }

[target.'cfg(target_os = "windows")'.dependencies]
aes-gcm = { workspace = true }
Expand Down
116 changes: 95 additions & 21 deletions apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,35 +51,104 @@ pub enum LoginImportResult {
}

pub trait InstalledBrowserRetriever {
fn get_installed_browsers() -> Result<Vec<String>>;
fn get_installed_browsers(mas_build: bool) -> Vec<String>;
}

pub struct DefaultInstalledBrowserRetriever {}

impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever {
fn get_installed_browsers() -> Result<Vec<String>> {
let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len());

for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
let data_dir = get_and_validate_data_dir(config);
if data_dir.is_ok() {
browsers.push((*browser).to_string());
}
fn get_installed_browsers(mas_build: bool) -> Vec<String> {
// Show all browsers for MAS builds, user will grant access when selected
if mas_build {
return SUPPORTED_BROWSER_MAP
.keys()
.map(|browser| (*browser).to_string())
.collect();
}
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.

Code quality: Potential optimization for MAS builds

When mas_build is true, the code shows all browsers regardless of whether they're installed. However, there's a check in sandbox.rs:request_only() (line 65) that verifies the browser is actually installed before showing the permission dialog.

Question: Should the initial browser list show only installed browsers even for MAS builds? This would improve UX by not showing options that will fail later.

Current flow:

  1. Show all browsers (MAS build)
  2. User selects browser
  3. Check if installed โ†’ fail with error if not

Suggested flow:

  1. Check which browsers are installed (using bundle ID check)
  2. Show only installed browsers
  3. User selects โ†’ show permission dialog

This would require calling the Objective-C check_browser_installed command during get_installed_browsers(). The trade-off is an additional Objective-C call vs better UX.


Ok(browsers)
// When not in sandbox, check file system directly
SUPPORTED_BROWSER_MAP
.iter()
.filter_map(|(browser, config)| {
get_and_validate_data_dir(config)
.ok()
.filter(|data_dir| data_dir.exists())
.map(|_| (*browser).to_string())
})
.collect()
}
}

pub fn get_available_profiles(browser_name: &String) -> Result<Vec<ProfileInfo>> {
#[allow(unused_variables, clippy::unused_async)]
pub async fn get_available_profiles(
browser_name: &str,
mas_build: bool,
) -> Result<Vec<ProfileInfo>> {
// MAS builds resolve the data dir from the security-scoped bookmark โ€” `dirs::home_dir()`
// returns the sandbox container path under the App Sandbox, not the user's real $HOME.
#[cfg(target_os = "macos")]
if mas_build {
let access = platform::sandbox::ScopedBrowserAccess::resume(browser_name).await?;
let read_result = load_local_state(access.path()).map(|s| get_profile_info(&s));
access.close().await?;
return read_result;
}

let (_, local_state) = load_local_state_for_browser(browser_name)?;
Ok(get_profile_info(&local_state))
}

/// Pre-translated picker dialog strings supplied by the renderer (which has the
/// i18n service). The native side concatenates these with the resolved browser
/// data path it computes via `getpwuid`.
#[cfg(target_os = "macos")]
pub struct PickerStrings {
pub message: String,
pub expected_location_label: String,
pub prompt: String,
}

/// Request access to browser directory (MAS builds only)
/// This shows the permission dialog and creates a security-scoped bookmark
#[cfg(target_os = "macos")]
pub async fn request_browser_access(
browser_name: &str,
picker_strings: PickerStrings,
mas_build: bool,
) -> Result<()> {
if mas_build {
platform::sandbox::ScopedBrowserAccess::request_only(browser_name, &picker_strings).await?;
}
Ok(())
}

#[allow(unused_variables)]
pub async fn import_logins(
browser_name: &String,
profile_id: &String,
browser_name: &str,
profile_id: &str,
mas_build: bool,
) -> Result<Vec<LoginImportResult>> {
// MAS builds resolve the data dir from the security-scoped bookmark โ€” `dirs::home_dir()`
// returns the sandbox container path under the App Sandbox, not the user's real $HOME.
// `ScopedBrowserAccess::close()` is awaited on the success path so the security scope
// is released before the function returns; `Drop` is a defensive backstop on the error path.
#[cfg(target_os = "macos")]
let access = if mas_build {
Some(platform::sandbox::ScopedBrowserAccess::resume(browser_name).await?)
} else {
None
};

#[cfg(target_os = "macos")]
let (data_dir, local_state) = match access.as_ref() {
Some(a) => {
let data_dir = a.path().to_path_buf();
let local_state = load_local_state(&data_dir)?;
(data_dir, local_state)
}
None => load_local_state_for_browser(browser_name)?,
};

#[cfg(not(target_os = "macos"))]
let (data_dir, local_state) = load_local_state_for_browser(browser_name)?;

let mut crypto_service = platform::get_crypto_service(browser_name, &local_state)
Expand All @@ -104,6 +173,11 @@ pub async fn import_logins(

let results = decrypt_logins(all_logins, &mut crypto_service).await;

#[cfg(target_os = "macos")]
if let Some(a) = access {
a.close().await?;
}

Ok(results)
}

Expand All @@ -115,6 +189,10 @@ pub async fn import_logins(
pub(crate) struct BrowserConfig {
pub name: &'static str,
pub data_dir: &'static [&'static str],
/// macOS application bundle identifier; used by sandbox builds to detect installation
/// without filesystem access. `None` on platforms where bundle IDs do not apply.
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub bundle_id: Option<&'static str>,
}

pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock<
Expand Down Expand Up @@ -177,9 +255,9 @@ struct OsCrypt {
app_bound_encrypted_key: Option<String>,
}

fn load_local_state_for_browser(browser_name: &String) -> Result<(PathBuf, LocalState)> {
fn load_local_state_for_browser(browser_name: &str) -> Result<(PathBuf, LocalState)> {
let config = SUPPORTED_BROWSER_MAP
.get(browser_name.as_str())
.get(browser_name)
.ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?;

let data_dir = get_and_validate_data_dir(config)?;
Expand Down Expand Up @@ -222,11 +300,7 @@ struct EncryptedLogin {
encrypted_note: Vec<u8>,
}

fn get_logins(
browser_dir: &Path,
profile_id: &String,
filename: &str,
) -> Result<Vec<EncryptedLogin>> {
fn get_logins(browser_dir: &Path, profile_id: &str, filename: &str) -> Result<Vec<EncryptedLogin>> {
let login_data_path = browser_dir.join(profile_id).join(filename);

// Sometimes database files are not present, so nothing to import
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,30 @@ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",
data_dir: &[".config/google-chrome", "snap/chromium/common/chromium"],
bundle_id: None,
},
BrowserConfig {
name: "Chromium",
data_dir: &["snap/chromium/common/chromium"],
bundle_id: None,
},
BrowserConfig {
name: "Brave",
data_dir: &[
"snap/brave/current/.config/BraveSoftware/Brave-Browser",
".config/BraveSoftware/Brave-Browser",
],
bundle_id: None,
},
BrowserConfig {
name: "Opera",
data_dir: &["snap/opera/current/.config/opera", ".config/opera"],
bundle_id: None,
},
];

pub(crate) fn get_crypto_service(
browser_name: &String,
browser_name: &str,
_local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {
let config = KEYRING_CONFIG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,42 @@ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",
data_dir: &["Library/Application Support/Google/Chrome"],
bundle_id: Some("com.google.Chrome"),
},
BrowserConfig {
name: "Chromium",
data_dir: &["Library/Application Support/Chromium"],
bundle_id: Some("org.chromium.Chromium"),
},
BrowserConfig {
name: "Microsoft Edge",
data_dir: &["Library/Application Support/Microsoft Edge"],
bundle_id: Some("com.microsoft.edgemac"),
},
BrowserConfig {
name: "Brave",
data_dir: &["Library/Application Support/BraveSoftware/Brave-Browser"],
bundle_id: Some("com.brave.Browser"),
},
BrowserConfig {
name: "Arc",
data_dir: &["Library/Application Support/Arc/User Data"],
bundle_id: Some("company.thebrowser.Browser"),
},
BrowserConfig {
name: "Opera",
data_dir: &["Library/Application Support/com.operasoftware.Opera"],
bundle_id: Some("com.operasoftware.Opera"),
},
BrowserConfig {
name: "Vivaldi",
data_dir: &["Library/Application Support/Vivaldi"],
bundle_id: Some("com.vivaldi.Vivaldi"),
},
];

pub(crate) fn get_crypto_service(
browser_name: &String,
browser_name: &str,
_local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {
let config = KEYCHAIN_CONFIG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#[cfg_attr(target_os = "macos", path = "macos.rs")]
mod native;

// Sandbox support (macOS only for MAS builds)
#[cfg(target_os = "macos")]
pub mod sandbox;

// Windows exposes public const
#[allow(unused_imports)]
pub use native::*;
Loading
Loading