diff --git a/.jules/sentinel.md b/.jules/sentinel.md index b58d736..986d7ff 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** The CLI application creates sensitive configuration files and directories (like wallets and snapshot data) using standard `fs::create_dir_all` and `fs::write` in Rust. These standard functions create files/directories using the system's default umask, which typically allows other users on the same Unix-like system to read the sensitive files. **Learning:** This could lead to a local privilege escalation or exposure of sensitive user data if the user runs the CLI on a shared machine. Relying on default system configurations for sensitive files is unsafe. **Prevention:** Always use `std::os::unix::fs::DirBuilderExt` and `std::os::unix::fs::OpenOptionsExt` to explicitly set file permissions (e.g., `0o700` for directories and `0o600` for files) when creating sensitive data on disk. + +## 2024-03-24 - Secure File Writing Regression Prevention +**Vulnerability:** The `maybe_write_text` utility function was using `std::fs::write`, which resulted in sensitive data (like PSBT files and offers) being saved with insecure default file permissions, making them readable by other users on a shared system. +**Learning:** Even generic utility functions used for saving user-requested command outputs must use secure file permissions (`0o600`) if the data they handle (like PSBTs and offers) is sensitive. +**Prevention:** Always use `crate::paths::write_secure_file` instead of `std::fs::write` for all file writing operations that might contain sensitive material in this codebase. diff --git a/src/utils.rs b/src/utils.rs index dd14d17..8e3996f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -93,7 +93,7 @@ pub(crate) fn best_match<'a>(needle: &str, candidates: &'a [&'a str]) -> Option< pub fn maybe_write_text(path: Option<&str>, text: &str) -> Result<(), crate::error::AppError> { if let Some(path) = path { - std::fs::write(path, text) + crate::paths::write_secure_file(path, text.as_bytes()) .map_err(|e| crate::error::AppError::Io(format!("failed to write to {path}: {e}"))) } else { Ok(())