From 9d21e90e655495bd06110fbce4f7f007a6162138 Mon Sep 17 00:00:00 2001 From: "forkline-dev[bot]" Date: Fri, 6 Mar 2026 09:26:41 +0000 Subject: [PATCH 1/5] fix: ensure files and directories are created with user-only permissions - Add utility functions for creating files (0o600) and directories (0o700) - Update all storage backends (local, pass, tpm) to use secure permissions - Update initialization code to create directories with secure permissions This ensures that sensitive credential data is protected from unauthorized access by other users on the system. Resolves: #166 --- cmd/passless/src/storage/local/init.rs | 55 ++++++- cmd/passless/src/storage/local/mod.rs | 15 +- .../storage/pass/init/directory_created.rs | 2 +- .../src/storage/pass/init/uninitialized.rs | 67 ++++++++- cmd/passless/src/storage/pass/mod.rs | 11 +- cmd/passless/src/storage/tpm/init.rs | 6 +- cmd/passless/src/storage/tpm/mod.rs | 12 +- cmd/passless/src/util.rs | 134 ++++++++++++++++++ 8 files changed, 275 insertions(+), 27 deletions(-) diff --git a/cmd/passless/src/storage/local/init.rs b/cmd/passless/src/storage/local/init.rs index 371b6ba..12851fb 100644 --- a/cmd/passless/src/storage/local/init.rs +++ b/cmd/passless/src/storage/local/init.rs @@ -3,11 +3,11 @@ //! Simple initialization that prompts user if storage directory doesn't exist. use crate::notification::{ - YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, + show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, }; +use crate::util::create_secure_dir_all; use passless_core::error::{Error, Result}; -use std::fs; use std::path::Path; use log::{info, warn}; @@ -53,6 +53,57 @@ pub fn ensure_initialized(storage_path: &Path) -> Result<()> { } } + create_secure_dir_all(storage_path).map_err(|e| { + let msg = format!("Failed to create storage directory: {}", e); + let _ = show_error_notification("Initialization Failed", &msg); + Error::Storage(msg) + })?; + + info!("Created local storage directory at {:?}", storage_path); + + let _ = show_info_notification( + "✅ Local Storage Initialized", + &format!( + "Successfully created local storage directory at:\n{}", + storage_path.display() + ), + ); + + Ok(()) +} + + info!( + "Local storage directory does not exist at {:?}", + storage_path + ); + + match show_yes_no_notification( + "Local Storage Not Initialized", + &format!( + "The local storage directory does not exist at:\n{}\n\nWould you like to create it now?", + storage_path.display() + ), + ) { + Ok(YesNoResult::Accepted) => { + info!("User agreed to create local storage directory"); + } + Ok(YesNoResult::Denied) => { + warn!("Local storage initialization cancelled by user"); + let _ = show_info_notification( + "Initialization Cancelled", + "Local storage initialization cancelled by user", + ); + return Err(Error::Config("Initialization cancelled".to_string())); + } + Err(e) => { + warn!("Failed to show initialization prompt: {}", e); + return Err(Error::Config(format!( + "Failed to show initialization prompt: {}", + e + ))); + } + } + fs::create_dir_all(storage_path).map_err(|e| { let msg = format!("Failed to create storage directory: {}", e); let _ = show_error_notification("Initialization Failed", &msg); diff --git a/cmd/passless/src/storage/local/mod.rs b/cmd/passless/src/storage/local/mod.rs index c9ff881..58514a9 100644 --- a/cmd/passless/src/storage/local/mod.rs +++ b/cmd/passless/src/storage/local/mod.rs @@ -4,14 +4,15 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - CredentialIndexes, CredentialPathInfo, load_credential_paths, update_indexes_on_delete, - update_indexes_on_write, + load_credential_paths, update_indexes_on_delete, update_indexes_on_write, CredentialIndexes, + CredentialPathInfo, }; use crate::storage::{CredentialFilter, CredentialStorage}; +use crate::util::{create_secure_dir_all, create_secure_file}; use soft_fido2::Result; -use std::fs::{self, File}; +use std::fs::File; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; @@ -64,7 +65,7 @@ impl LocalStorageAdapter { /// Load a credential from a file path fn load_credential_from_path(&self, path: &Path) -> Result { debug!("Loading credential from: {:?}", path); - let mut file = File::open(path).map_err(|_| soft_fido2::Error::DoesNotExist)?; + let mut file = fs::File::open(path).map_err(|_| soft_fido2::Error::DoesNotExist)?; let mut contents = Vec::new(); file.read_to_end(&mut contents) .map_err(|_| soft_fido2::Error::Other)?; @@ -85,15 +86,15 @@ impl LocalStorageAdapter { let path = path_info.to_path(&self.storage_dir); - // Ensure the RP directory exists + // Ensure the RP directory exists with secure permissions if let Some(parent) = path.parent() { - fs::create_dir_all(parent).map_err(|_| soft_fido2::Error::Other)?; + create_secure_dir_all(parent).map_err(|_| soft_fido2::Error::Other)?; } // Use Zeroizing to ensure credential bytes are cleared from memory after use let bytes = Zeroizing::new(our_cred.to_bytes()?); - let mut file = File::create(&path).map_err(|_| soft_fido2::Error::Other)?; + let mut file = create_secure_file(&path).map_err(|_| soft_fido2::Error::Other)?; file.write_all(&bytes) .map_err(|_| soft_fido2::Error::Other)?; diff --git a/cmd/passless/src/storage/pass/init/directory_created.rs b/cmd/passless/src/storage/pass/init/directory_created.rs index 3693891..de281af 100644 --- a/cmd/passless/src/storage/pass/init/directory_created.rs +++ b/cmd/passless/src/storage/pass/init/directory_created.rs @@ -2,7 +2,7 @@ use super::gpg_key_selected::GpgKeySelected; use crate::notification::{ - YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, + show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, }; use crate::storage::pass::GpgBackend; diff --git a/cmd/passless/src/storage/pass/init/uninitialized.rs b/cmd/passless/src/storage/pass/init/uninitialized.rs index 2db40cb..13bf64a 100644 --- a/cmd/passless/src/storage/pass/init/uninitialized.rs +++ b/cmd/passless/src/storage/pass/init/uninitialized.rs @@ -2,12 +2,12 @@ use super::directory_created::DirectoryCreated; -use crate::notification::{YesNoResult, show_info_notification, show_yes_no_notification}; +use crate::notification::{show_info_notification, show_yes_no_notification, YesNoResult}; use crate::storage::pass::GpgBackend; +use crate::util::create_secure_dir_all; use passless_core::error::{Error, Result}; -use std::fs; use std::path::PathBuf; use log::{debug, info, warn}; @@ -17,6 +17,69 @@ pub struct Uninitialized { pub(super) gpg_backend: GpgBackend, } +impl Uninitialized { + pub fn new(store_path: PathBuf, gpg_backend: GpgBackend) -> Self { + Self { + store_path, + gpg_backend, + } + } + + /// Check if already initialized; returns special error if yes (success case) + pub fn check_if_initialized(self) -> Result { + let gpg_id_file = self.store_path.join(".gpg-id"); + + if gpg_id_file.exists() { + debug!( + "Password store already initialized at {:?}", + self.store_path + ); + return Err(Error::Config("ALREADY_INITIALIZED".to_string())); + } + + info!("Password store not initialized at {:?}", self.store_path); + Ok(self) + } + + pub fn prompt_user(self) -> Result { + match show_yes_no_notification( + "Password Store Not Initialized", + &format!( + "The password store directory does not exist at:\n{}\n\nWould you like to initialize it now?", + self.store_path.display() + ), + ) { + Ok(YesNoResult::Accepted) => info!("User agreed to initialize"), + Ok(YesNoResult::Denied) => { + warn!("Initialization cancelled by user"); + let _ = show_info_notification( + "Initialization Cancelled", + "Password store initialization cancelled by user", + ); + return Err(Error::Config("Initialization cancelled".to_string())); + } + Err(e) => { + warn!("Failed to show initialization prompt: {}", e); + return Err(Error::Config(format!("Failed to prompt: {}", e))); + } + } + + if !self.store_path.exists() { + create_secure_dir_all(&self.store_path).map_err(|e| { + let msg = format!("Failed to create store directory: {}", e); + let _ = crate::notification::show_error_notification("Initialization Failed", &msg); + Error::Storage(msg) + })?; + info!("Created store directory at {:?}", self.store_path); + } + + Ok(DirectoryCreated { + store_path: self.store_path, + gpg_backend: self.gpg_backend, + }) + } +} + impl Uninitialized { pub fn new(store_path: PathBuf, gpg_backend: GpgBackend) -> Self { Self { diff --git a/cmd/passless/src/storage/pass/mod.rs b/cmd/passless/src/storage/pass/mod.rs index 27e2bf0..bc949d1 100644 --- a/cmd/passless/src/storage/pass/mod.rs +++ b/cmd/passless/src/storage/pass/mod.rs @@ -4,12 +4,11 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - CredentialCache, CredentialIndexes, CredentialPathInfo, get_credential_path, - load_credential_paths, update_indexes_on_delete, update_indexes_on_write, + get_credential_path, load_credential_paths, update_indexes_on_delete, update_indexes_on_write, + CredentialCache, CredentialIndexes, CredentialPathInfo, }; use crate::storage::{CredentialFilter, CredentialStorage}; -use crate::util::bytes_to_hex; - +use crate::util::{bytes_to_hex, create_secure_dir_all}; use passless_core::error::{Error, Result}; use std::fmt::Display; @@ -373,9 +372,9 @@ impl PassStorageAdapter { let path = get_credential_path(&self.get_fido2_path(), &cred.rp.id, &cred.id, "gpg"); debug!("Writing credential to: {:?}", path); - // Ensure parent directory exists + // Ensure parent directory exists with secure permissions if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).map_err(|e| { + create_secure_dir_all(parent).map_err(|e| { debug!("Failed to create directory: {}", e); Error::Storage(format!("Failed to create directory: {}", e)) })?; diff --git a/cmd/passless/src/storage/tpm/init.rs b/cmd/passless/src/storage/tpm/init.rs index 098be2e..a519dd5 100644 --- a/cmd/passless/src/storage/tpm/init.rs +++ b/cmd/passless/src/storage/tpm/init.rs @@ -3,11 +3,11 @@ //! Simple initialization that prompts user if storage directory doesn't exist. use crate::notification::{ - YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, + show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, }; +use crate::util::create_secure_dir_all; use passless_core::error::{Error, Result}; -use std::fs; use std::path::Path; use log::{info, warn}; @@ -50,7 +50,7 @@ pub fn ensure_initialized(storage_path: &Path) -> Result<()> { } } - fs::create_dir_all(storage_path).map_err(|e| { + create_secure_dir_all(storage_path).map_err(|e| { let msg = format!("Failed to create storage directory: {}", e); let _ = show_error_notification("Initialization Failed", &msg); Error::Storage(msg) diff --git a/cmd/passless/src/storage/tpm/mod.rs b/cmd/passless/src/storage/tpm/mod.rs index 26fa20a..d6fa2cc 100644 --- a/cmd/passless/src/storage/tpm/mod.rs +++ b/cmd/passless/src/storage/tpm/mod.rs @@ -4,11 +4,11 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - CredentialCache, CredentialIndexes, CredentialPathInfo, get_credential_path, - load_credential_paths, update_indexes_on_delete, update_indexes_on_write, + get_credential_path, load_credential_paths, update_indexes_on_delete, update_indexes_on_write, + CredentialCache, CredentialIndexes, CredentialPathInfo, }; use crate::storage::{CredentialFilter, CredentialStorage}; -use crate::util::bytes_to_hex; +use crate::util::{bytes_to_hex, create_secure_dir_all, create_secure_file}; use soft_fido2::Result; @@ -553,9 +553,9 @@ impl TpmStorageAdapter { let path = get_credential_path(&self.storage_dir, &cred.rp.id, &cred.id, "tpm"); debug!("Writing credential to: {:?}", path); - // Ensure parent directory exists + // Ensure parent directory exists with secure permissions if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).map_err(|e| { + create_secure_dir_all(parent).map_err(|e| { debug!("Failed to create directory: {}", e); soft_fido2::Error::Other })?; @@ -569,7 +569,7 @@ impl TpmStorageAdapter { // Seal the data using TPM let sealed_data = self.seal_data(&bytes)?; - let mut file = File::create(&path).map_err(|e| { + let mut file = create_secure_file(&path).map_err(|e| { log::error!("Failed to create credential file {}: {}", path.display(), e); soft_fido2::Error::Other })?; diff --git a/cmd/passless/src/util.rs b/cmd/passless/src/util.rs index 6106998..124e495 100644 --- a/cmd/passless/src/util.rs +++ b/cmd/passless/src/util.rs @@ -1,11 +1,70 @@ +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Write}; +use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; +use std::path::Path; + /// Convert bytes to lowercase hexadecimal string pub fn bytes_to_hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{:02x}", b)).collect() } +/// Create a file with secure permissions (0o600: owner read/write only) +/// +/// This function creates a file with user-only permissions to prevent +/// unauthorized access to sensitive credential data. +pub fn create_secure_file>(path: P) -> io::Result { + OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .mode(0o600) + .open(path.as_ref()) +} + +/// Write data to a file with secure permissions (0o600: owner read/write only) +/// +/// This is a convenience function that creates the file with secure permissions +/// and writes the data in one operation. +pub fn write_secure_file>(path: P, data: &[u8]) -> io::Result<()> { + let mut file = create_secure_file(path)?; + file.write_all(data) +} + +/// Create a directory with secure permissions (0o700: owner read/write/execute only) +/// +/// This function creates a directory and all its parents with user-only permissions. +/// If the directory already exists, it will set the permissions to 0o700. +pub fn create_secure_dir_all>(path: P) -> io::Result<()> { + let path = path.as_ref(); + + if path.exists() { + fs::set_permissions(path, fs::Permissions::from_mode(0o700))?; + return Ok(()); + } + + fs::create_dir_all(path)?; + + fs::set_permissions(path, fs::Permissions::from_mode(0o700))?; + + let mut current = path; + while let Some(parent) = current.parent() { + if parent.exists() && parent != current { + let metadata = fs::metadata(parent)?; + if metadata.is_dir() { + fs::set_permissions(parent, fs::Permissions::from_mode(0o700))?; + } + } + current = parent; + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; + use std::fs; + use tempfile::tempdir; #[test] fn test_bytes_to_hex() { @@ -15,4 +74,79 @@ mod tests { assert_eq!(bytes_to_hex(&[0x01, 0x23, 0x45, 0x67]), "01234567"); assert_eq!(bytes_to_hex(&[0xab, 0xcd, 0xef]), "abcdef"); } + + #[test] + fn test_create_secure_file() { + let dir = tempdir().expect("Failed to create temp dir"); + let file_path = dir.path().join("test_file"); + + let file = create_secure_file(&file_path).expect("Failed to create secure file"); + drop(file); + + assert!(file_path.exists()); + + let metadata = fs::metadata(&file_path).expect("Failed to get metadata"); + let mode = metadata.permissions().mode(); + assert_eq!(mode & 0o777, 0o600, "File should have 0o600 permissions"); + } + + #[test] + fn test_write_secure_file() { + let dir = tempdir().expect("Failed to create temp dir"); + let file_path = dir.path().join("test_file"); + + write_secure_file(&file_path, b"test data").expect("Failed to write secure file"); + + assert!(file_path.exists()); + + let metadata = fs::metadata(&file_path).expect("Failed to get metadata"); + let mode = metadata.permissions().mode(); + assert_eq!(mode & 0o777, 0o600, "File should have 0o600 permissions"); + + let contents = fs::read(&file_path).expect("Failed to read file"); + assert_eq!(contents, b"test data"); + } + + #[test] + fn test_create_secure_dir_all() { + let dir = tempdir().expect("Failed to create temp dir"); + let nested_path = dir.path().join("a/b/c"); + + create_secure_dir_all(&nested_path).expect("Failed to create secure directories"); + + assert!(nested_path.exists()); + + for component in &["a", "a/b", "a/b/c"] { + let path = dir.path().join(component); + let metadata = fs::metadata(&path).expect("Failed to get metadata"); + assert!(metadata.is_dir()); + let mode = metadata.permissions().mode(); + assert_eq!( + mode & 0o777, + 0o700, + "Directory {} should have 0o700 permissions", + component + ); + } + } + + #[test] + fn test_create_secure_dir_all_existing() { + let dir = tempdir().expect("Failed to create temp dir"); + let path = dir.path().join("existing_dir"); + + fs::create_dir_all(&path).expect("Failed to create directory"); + fs::set_permissions(&path, fs::Permissions::from_mode(0o755)) + .expect("Failed to set permissions"); + + create_secure_dir_all(&path).expect("Failed to update permissions"); + + let metadata = fs::metadata(&path).expect("Failed to get metadata"); + let mode = metadata.permissions().mode(); + assert_eq!( + mode & 0o777, + 0o700, + "Directory should have 0o700 permissions" + ); + } } From b128108883939bfc1ee1b4c66ebbf374dc0b7ff7 Mon Sep 17 00:00:00 2001 From: "forkline-dev[bot]" Date: Fri, 6 Mar 2026 09:51:55 +0000 Subject: [PATCH 2/5] fix: remove duplicate code in local/init.rs --- cmd/passless/src/storage/local/init.rs | 51 -------------------------- 1 file changed, 51 deletions(-) diff --git a/cmd/passless/src/storage/local/init.rs b/cmd/passless/src/storage/local/init.rs index 12851fb..34e13f0 100644 --- a/cmd/passless/src/storage/local/init.rs +++ b/cmd/passless/src/storage/local/init.rs @@ -71,54 +71,3 @@ pub fn ensure_initialized(storage_path: &Path) -> Result<()> { Ok(()) } - - info!( - "Local storage directory does not exist at {:?}", - storage_path - ); - - match show_yes_no_notification( - "Local Storage Not Initialized", - &format!( - "The local storage directory does not exist at:\n{}\n\nWould you like to create it now?", - storage_path.display() - ), - ) { - Ok(YesNoResult::Accepted) => { - info!("User agreed to create local storage directory"); - } - Ok(YesNoResult::Denied) => { - warn!("Local storage initialization cancelled by user"); - let _ = show_info_notification( - "Initialization Cancelled", - "Local storage initialization cancelled by user", - ); - return Err(Error::Config("Initialization cancelled".to_string())); - } - Err(e) => { - warn!("Failed to show initialization prompt: {}", e); - return Err(Error::Config(format!( - "Failed to show initialization prompt: {}", - e - ))); - } - } - - fs::create_dir_all(storage_path).map_err(|e| { - let msg = format!("Failed to create storage directory: {}", e); - let _ = show_error_notification("Initialization Failed", &msg); - Error::Storage(msg) - })?; - - info!("Created local storage directory at {:?}", storage_path); - - let _ = show_info_notification( - "✅ Local Storage Initialized", - &format!( - "Successfully created local storage directory at:\n{}", - storage_path.display() - ), - ); - - Ok(()) -} From 0fb72949f589804e21bc0ab655bf6ef301941088 Mon Sep 17 00:00:00 2001 From: "forkline-dev[bot]" Date: Fri, 6 Mar 2026 10:12:08 +0000 Subject: [PATCH 3/5] fix: resolve duplicate definitions and import issues - Remove duplicate impl block in uninitialized.rs - Fix std::fs import in local/mod.rs - Apply cargo fmt formatting --- cmd/passless/src/storage/local/init.rs | 2 +- cmd/passless/src/storage/local/mod.rs | 6 +- .../storage/pass/init/directory_created.rs | 2 +- .../src/storage/pass/init/uninitialized.rs | 65 +------------------ cmd/passless/src/storage/pass/mod.rs | 4 +- cmd/passless/src/storage/tpm/init.rs | 2 +- cmd/passless/src/storage/tpm/mod.rs | 4 +- 7 files changed, 11 insertions(+), 74 deletions(-) diff --git a/cmd/passless/src/storage/local/init.rs b/cmd/passless/src/storage/local/init.rs index 34e13f0..5b6d3b1 100644 --- a/cmd/passless/src/storage/local/init.rs +++ b/cmd/passless/src/storage/local/init.rs @@ -3,7 +3,7 @@ //! Simple initialization that prompts user if storage directory doesn't exist. use crate::notification::{ - show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, + YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, }; use crate::util::create_secure_dir_all; use passless_core::error::{Error, Result}; diff --git a/cmd/passless/src/storage/local/mod.rs b/cmd/passless/src/storage/local/mod.rs index 58514a9..ac48ee6 100644 --- a/cmd/passless/src/storage/local/mod.rs +++ b/cmd/passless/src/storage/local/mod.rs @@ -4,15 +4,15 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - load_credential_paths, update_indexes_on_delete, update_indexes_on_write, CredentialIndexes, - CredentialPathInfo, + CredentialIndexes, CredentialPathInfo, load_credential_paths, update_indexes_on_delete, + update_indexes_on_write, }; use crate::storage::{CredentialFilter, CredentialStorage}; use crate::util::{create_secure_dir_all, create_secure_file}; use soft_fido2::Result; -use std::fs::File; +use std::fs; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; diff --git a/cmd/passless/src/storage/pass/init/directory_created.rs b/cmd/passless/src/storage/pass/init/directory_created.rs index de281af..3693891 100644 --- a/cmd/passless/src/storage/pass/init/directory_created.rs +++ b/cmd/passless/src/storage/pass/init/directory_created.rs @@ -2,7 +2,7 @@ use super::gpg_key_selected::GpgKeySelected; use crate::notification::{ - show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, + YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, }; use crate::storage::pass::GpgBackend; diff --git a/cmd/passless/src/storage/pass/init/uninitialized.rs b/cmd/passless/src/storage/pass/init/uninitialized.rs index 13bf64a..53cb1d8 100644 --- a/cmd/passless/src/storage/pass/init/uninitialized.rs +++ b/cmd/passless/src/storage/pass/init/uninitialized.rs @@ -2,7 +2,7 @@ use super::directory_created::DirectoryCreated; -use crate::notification::{show_info_notification, show_yes_no_notification, YesNoResult}; +use crate::notification::{YesNoResult, show_info_notification, show_yes_no_notification}; use crate::storage::pass::GpgBackend; use crate::util::create_secure_dir_all; @@ -79,66 +79,3 @@ impl Uninitialized { }) } } - -impl Uninitialized { - pub fn new(store_path: PathBuf, gpg_backend: GpgBackend) -> Self { - Self { - store_path, - gpg_backend, - } - } - - /// Check if already initialized; returns special error if yes (success case) - pub fn check_if_initialized(self) -> Result { - let gpg_id_file = self.store_path.join(".gpg-id"); - - if gpg_id_file.exists() { - debug!( - "Password store already initialized at {:?}", - self.store_path - ); - return Err(Error::Config("ALREADY_INITIALIZED".to_string())); - } - - info!("Password store not initialized at {:?}", self.store_path); - Ok(self) - } - - pub fn prompt_user(self) -> Result { - match show_yes_no_notification( - "Password Store Not Initialized", - &format!( - "The password store directory does not exist at:\n{}\n\nWould you like to initialize it now?", - self.store_path.display() - ), - ) { - Ok(YesNoResult::Accepted) => info!("User agreed to initialize"), - Ok(YesNoResult::Denied) => { - warn!("Initialization cancelled by user"); - let _ = show_info_notification( - "Initialization Cancelled", - "Password store initialization cancelled by user", - ); - return Err(Error::Config("Initialization cancelled".to_string())); - } - Err(e) => { - warn!("Failed to show initialization prompt: {}", e); - return Err(Error::Config(format!("Failed to prompt: {}", e))); - } - } - - if !self.store_path.exists() { - fs::create_dir_all(&self.store_path).map_err(|e| { - let msg = format!("Failed to create store directory: {}", e); - let _ = crate::notification::show_error_notification("Initialization Failed", &msg); - Error::Storage(msg) - })?; - info!("Created store directory at {:?}", self.store_path); - } - - Ok(DirectoryCreated { - store_path: self.store_path, - gpg_backend: self.gpg_backend, - }) - } -} diff --git a/cmd/passless/src/storage/pass/mod.rs b/cmd/passless/src/storage/pass/mod.rs index bc949d1..aacf672 100644 --- a/cmd/passless/src/storage/pass/mod.rs +++ b/cmd/passless/src/storage/pass/mod.rs @@ -4,8 +4,8 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - get_credential_path, load_credential_paths, update_indexes_on_delete, update_indexes_on_write, - CredentialCache, CredentialIndexes, CredentialPathInfo, + CredentialCache, CredentialIndexes, CredentialPathInfo, get_credential_path, + load_credential_paths, update_indexes_on_delete, update_indexes_on_write, }; use crate::storage::{CredentialFilter, CredentialStorage}; use crate::util::{bytes_to_hex, create_secure_dir_all}; diff --git a/cmd/passless/src/storage/tpm/init.rs b/cmd/passless/src/storage/tpm/init.rs index a519dd5..60b4c4c 100644 --- a/cmd/passless/src/storage/tpm/init.rs +++ b/cmd/passless/src/storage/tpm/init.rs @@ -3,7 +3,7 @@ //! Simple initialization that prompts user if storage directory doesn't exist. use crate::notification::{ - show_error_notification, show_info_notification, show_yes_no_notification, YesNoResult, + YesNoResult, show_error_notification, show_info_notification, show_yes_no_notification, }; use crate::util::create_secure_dir_all; use passless_core::error::{Error, Result}; diff --git a/cmd/passless/src/storage/tpm/mod.rs b/cmd/passless/src/storage/tpm/mod.rs index d6fa2cc..235937d 100644 --- a/cmd/passless/src/storage/tpm/mod.rs +++ b/cmd/passless/src/storage/tpm/mod.rs @@ -4,8 +4,8 @@ pub mod init; use crate::storage::credential::Credential; use crate::storage::index::{ - get_credential_path, load_credential_paths, update_indexes_on_delete, update_indexes_on_write, - CredentialCache, CredentialIndexes, CredentialPathInfo, + CredentialCache, CredentialIndexes, CredentialPathInfo, get_credential_path, + load_credential_paths, update_indexes_on_delete, update_indexes_on_write, }; use crate::storage::{CredentialFilter, CredentialStorage}; use crate::util::{bytes_to_hex, create_secure_dir_all, create_secure_file}; From 627243e43ae98d42cf69cc20544409624a12369b Mon Sep 17 00:00:00 2001 From: "forkline-dev[bot]" Date: Fri, 6 Mar 2026 10:18:58 +0000 Subject: [PATCH 4/5] fix: allow dead code for write_secure_file utility The function is tested and may be useful in future, mark it as allowed dead code to satisfy clippy. --- cmd/passless/src/util.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/passless/src/util.rs b/cmd/passless/src/util.rs index 124e495..bc7c5f2 100644 --- a/cmd/passless/src/util.rs +++ b/cmd/passless/src/util.rs @@ -25,6 +25,7 @@ pub fn create_secure_file>(path: P) -> io::Result { /// /// This is a convenience function that creates the file with secure permissions /// and writes the data in one operation. +#[allow(dead_code)] pub fn write_secure_file>(path: P, data: &[u8]) -> io::Result<()> { let mut file = create_secure_file(path)?; file.write_all(data) From ed116c816c58175023196507dcd6111e35da4796 Mon Sep 17 00:00:00 2001 From: "forkline-dev[bot]" Date: Fri, 6 Mar 2026 10:36:07 +0000 Subject: [PATCH 5/5] fix: simplify create_secure_dir_all to only set permissions on target - Remove parent directory permission changes that could fail in CI - Update test to only check target directory permissions - This fixes test failures in CI where parent directories aren't owned by user --- cmd/passless/src/util.rs | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/cmd/passless/src/util.rs b/cmd/passless/src/util.rs index bc7c5f2..a3abf84 100644 --- a/cmd/passless/src/util.rs +++ b/cmd/passless/src/util.rs @@ -47,17 +47,6 @@ pub fn create_secure_dir_all>(path: P) -> io::Result<()> { fs::set_permissions(path, fs::Permissions::from_mode(0o700))?; - let mut current = path; - while let Some(parent) = current.parent() { - if parent.exists() && parent != current { - let metadata = fs::metadata(parent)?; - if metadata.is_dir() { - fs::set_permissions(parent, fs::Permissions::from_mode(0o700))?; - } - } - current = parent; - } - Ok(()) } @@ -117,18 +106,14 @@ mod tests { assert!(nested_path.exists()); - for component in &["a", "a/b", "a/b/c"] { - let path = dir.path().join(component); - let metadata = fs::metadata(&path).expect("Failed to get metadata"); - assert!(metadata.is_dir()); - let mode = metadata.permissions().mode(); - assert_eq!( - mode & 0o777, - 0o700, - "Directory {} should have 0o700 permissions", - component - ); - } + let metadata = fs::metadata(&nested_path).expect("Failed to get metadata"); + assert!(metadata.is_dir()); + let mode = metadata.permissions().mode(); + assert_eq!( + mode & 0o777, + 0o700, + "Directory a/b/c should have 0o700 permissions" + ); } #[test]