security: restrict secret files to owner-only (0600)

This commit is contained in:
zhom
2026-07-07 22:44:31 +04:00
parent 862831764d
commit 15a7647e74
5 changed files with 39 additions and 14 deletions
+19
View File
@@ -190,6 +190,25 @@ pub fn set_test_cache_dir(dir: PathBuf) -> TestDirGuard {
}
}
/// Restrict a just-written file to owner-only read/write (`0600`) on Unix so
/// other local users/processes can't read secret material (tokens, E2E
/// password, encrypted vault files). Best-effort: the write already succeeded,
/// so a permission failure is logged, not propagated. On Windows the per-user
/// profile ACL already restricts access, so this is a no-op there.
pub fn restrict_to_owner(path: &std::path::Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)) {
log::warn!("Failed to restrict permissions on {}: {e}", path.display());
}
}
#[cfg(not(unix))]
{
let _ = path;
}
}
#[cfg(test)]
mod tests {
use super::*;
+3 -1
View File
@@ -289,6 +289,7 @@ impl CloudAuthManager {
file_data.extend_from_slice(&ciphertext);
fs::write(file_path, file_data).map_err(|e| format!("Failed to write file: {e}"))?;
crate::app_dirs::restrict_to_owner(file_path);
Ok(())
}
@@ -409,7 +410,8 @@ impl CloudAuthManager {
}
let json =
serde_json::to_string_pretty(state).map_err(|e| format!("Failed to serialize: {e}"))?;
fs::write(path, json).map_err(|e| format!("Failed to write auth state: {e}"))?;
fs::write(&path, json).map_err(|e| format!("Failed to write auth state: {e}"))?;
crate::app_dirs::restrict_to_owner(&path);
Ok(())
}
+6 -3
View File
@@ -266,7 +266,8 @@ impl SettingsManager {
file_data.extend_from_slice(&(ciphertext.len() as u32).to_le_bytes());
file_data.extend_from_slice(&ciphertext);
std::fs::write(token_file, file_data)?;
std::fs::write(&token_file, file_data)?;
crate::app_dirs::restrict_to_owner(std::path::Path::new(&token_file));
Ok(())
}
@@ -434,7 +435,8 @@ impl SettingsManager {
file_data.extend_from_slice(&(ciphertext.len() as u32).to_le_bytes());
file_data.extend_from_slice(&ciphertext);
std::fs::write(token_file, file_data)?;
std::fs::write(&token_file, file_data)?;
crate::app_dirs::restrict_to_owner(std::path::Path::new(&token_file));
Ok(())
}
@@ -573,7 +575,8 @@ impl SettingsManager {
file_data.extend_from_slice(&(ciphertext.len() as u32).to_le_bytes());
file_data.extend_from_slice(&ciphertext);
std::fs::write(token_file, file_data)?;
std::fs::write(&token_file, file_data)?;
crate::app_dirs::restrict_to_owner(std::path::Path::new(&token_file));
Ok(())
}
+1
View File
@@ -87,6 +87,7 @@ pub fn store_e2e_password(password: &str) -> Result<(), String> {
std::fs::write(&file_path, file_data)
.map_err(|e| format!("Failed to write e2e password file: {e}"))?;
crate::app_dirs::restrict_to_owner(std::path::Path::new(&file_path));
Ok(())
}
+10 -10
View File
@@ -69,6 +69,12 @@ impl VpnStorage {
let storage_path = dir.join("vpn_configs.json");
let key_path = dir.join(".vpn_key");
// Persist a freshly generated key with owner-only (0600) permissions so the
// WireGuard-key-encrypting key isn't left world-readable.
let write_key = |key: &[u8; 32]| {
let _ = fs::write(&key_path, key);
crate::app_dirs::restrict_to_owner(&key_path);
};
let encryption_key = if key_path.exists() {
if let Ok(key_data) = fs::read(&key_path) {
if key_data.len() == 32 {
@@ -77,17 +83,17 @@ impl VpnStorage {
key
} else {
let key: [u8; 32] = rand::rng().random();
let _ = fs::write(&key_path, key);
write_key(&key);
key
}
} else {
let key: [u8; 32] = rand::rng().random();
let _ = fs::write(&key_path, key);
write_key(&key);
key
}
} else {
let key: [u8; 32] = rand::rng().random();
let _ = fs::write(&key_path, key);
write_key(&key);
key
};
@@ -124,13 +130,7 @@ impl VpnStorage {
// Generate a new key
let key: [u8; 32] = rand::rng().random();
let _ = fs::write(&key_path, key);
// Set restrictive permissions on Unix
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(&key_path, fs::Permissions::from_mode(0o600));
}
crate::app_dirs::restrict_to_owner(&key_path);
key
}