Implement audited findings
Some checks failed
Dependency security audit / rustsec (push) Failing after 3s

This commit is contained in:
2026-08-27 15:34:31 +02:00
parent 2feecae70d
commit c2dd826920
11 changed files with 773 additions and 220 deletions

View File

@@ -6,7 +6,7 @@ use std::{
error::Error,
fmt, fs,
fs::OpenOptions,
io::Write,
io::{self, Write},
path::{Component, Path, PathBuf},
time::Duration,
};
@@ -638,6 +638,13 @@ impl Config {
}
pub(crate) fn persist(&self) -> Result<(), ConfigError> {
self.persist_with_directory_sync(sync_config_directory)
}
fn persist_with_directory_sync(
&self,
sync_parent: impl FnOnce(&Dir) -> io::Result<()>,
) -> Result<(), ConfigError> {
let parent = self.source.parent().ok_or_else(|| ConfigError::Write {
path: self.source.clone(),
})?;
@@ -668,10 +675,20 @@ impl Config {
.and_then(|()| temporary.replace(name))
.map_err(|_| ConfigError::Write {
path: self.source.clone(),
})
})?;
sync_parent(&directory).map_err(|_| ConfigError::DurabilityUncertain {
path: self.source.clone(),
})
}
fn persist_new(&self) -> Result<(), ConfigError> {
self.persist_new_with_directory_sync(sync_config_directory)
}
fn persist_new_with_directory_sync(
&self,
sync_parent: impl FnOnce(&Dir) -> io::Result<()>,
) -> Result<(), ConfigError> {
let parent = self.source.parent().ok_or_else(|| ConfigError::Write {
path: self.source.clone(),
})?;
@@ -689,6 +706,10 @@ impl Config {
set_private_directory(parent).map_err(|_| ConfigError::Write {
path: self.source.clone(),
})?;
let directory =
Dir::open_ambient_dir(parent, ambient_authority()).map_err(|_| ConfigError::Write {
path: self.source.clone(),
})?;
let contents = toml::to_string_pretty(&self.document).map_err(|_| ConfigError::Write {
path: self.source.clone(),
})?;
@@ -725,7 +746,9 @@ impl Config {
drop(temporary_file);
let _ = fs::remove_file(&temporary_path);
match installed {
Ok(()) => Ok(()),
Ok(()) => sync_parent(&directory).map_err(|_| ConfigError::DurabilityUncertain {
path: self.source.clone(),
}),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
Err(ConfigError::AlreadyConfigured {
path: self.source.clone(),
@@ -738,6 +761,10 @@ impl Config {
}
}
fn sync_config_directory(directory: &Dir) -> io::Result<()> {
directory.open(".").and_then(|file| file.sync_all())
}
fn git_remote_document(remote: &GitRemote) -> Result<toml::Table, ConfigError> {
let mut configured = toml::Table::new();
configured.insert(
@@ -1412,6 +1439,7 @@ pub enum ConfigError {
VaultUnavailable { path: PathBuf },
VaultIsNotDirectory { path: PathBuf },
Write { path: PathBuf },
DurabilityUncertain { path: PathBuf },
AlreadyConfigured { path: PathBuf },
KeyMaterialNotFound { path: PathBuf },
InvalidKeyMaterial { path: PathBuf },
@@ -1483,6 +1511,11 @@ impl fmt::Display for ConfigError {
path.display()
)
}
Self::DurabilityUncertain { path } => write!(
formatter,
"configuration replacement completed but its directory sync failed: {}",
path.display()
),
Self::AlreadyConfigured { path } => write!(
formatter,
"configuration already exists and was not replaced: {}",
@@ -2315,7 +2348,7 @@ fn native_config_directory() -> Option<PathBuf> {
#[cfg(test)]
mod tests {
use std::fs;
use std::{fs, io};
use crate::mobile::MobileTab;
@@ -2401,4 +2434,48 @@ key_material = "keys"
assert!(contents.contains("vault = \"vault\""));
assert!(!contents.contains("OLD-CONTAINER"));
}
#[test]
fn post_install_directory_sync_failure_is_distinct_and_keeps_complete_config() {
let temporary = tempfile::tempdir().expect("temporary directory");
let vault = temporary.path().join("vault");
let keys = temporary.path().join("keys");
let source = temporary.path().join("config.toml");
fs::create_dir(&vault).expect("vault");
fs::create_dir(&keys).expect("keys");
let remote = GitRemote::https(
"origin",
"https://example.test/team/passwords.git",
"server-example",
"repository-example",
)
.expect("remote");
let config = Config::create_mobile_clone(source.clone(), &vault, &keys, "ALICE", &remote)
.expect("initial config");
let error = config
.persist_with_directory_sync(|_| Err(io::Error::other("simulated sync failure")))
.expect_err("replacement directory sync must fail");
assert_eq!(
error,
ConfigError::DurabilityUncertain {
path: source.clone()
}
);
Config::load(Some(&source)).expect("replacement is complete");
let created = temporary.path().join("created.toml");
let mut new_config = config;
new_config.source = created.clone();
let error = new_config
.persist_new_with_directory_sync(|_| Err(io::Error::other("simulated sync failure")))
.expect_err("creation directory sync must fail");
assert_eq!(
error,
ConfigError::DurabilityUncertain {
path: created.clone()
}
);
Config::load(Some(&created)).expect("creation is complete");
}
}