Skip to content

Commit 9fc14fb

Browse files
🛡️ Sentinel: [HIGH] Enforce secure file and directory permissions (#1)
* 🛡️ Sentinel: [HIGH] Enforce secure file and directory permissions Severity: HIGH Vulnerability: The application creates sensitive files (wallets, snapshots) using `fs::create_dir_all` and `fs::write` which default to the system's `umask`, potentially exposing them to other local users. Impact: Local privilege escalation or exposure of wallet secrets on shared Unix-like systems. Fix: Created secure wrappers using `DirBuilderExt` and `OpenOptionsExt` to enforce `0o700` and `0o600` permissions respectively. Updated usages in `src/paths.rs`. Documented the learning in `.jules/sentinel.md`. Verification: Compile and verify permissions on Unix systems. Co-authored-by: bitcoiner-dev <75873427+bitcoiner-dev@users.noreply.github.com> * 🛡️ Sentinel: [HIGH] Enforce secure file and directory permissions Severity: HIGH Vulnerability: The application creates sensitive files (wallets, snapshots) using `fs::create_dir_all` and `fs::write` which default to the system's `umask`, potentially exposing them to other local users. Impact: Local privilege escalation or exposure of wallet secrets on shared Unix-like systems. Fix: Created secure wrappers using `DirBuilderExt` and `OpenOptionsExt` to enforce `0o700` and `0o600` permissions respectively. Updated usages in `src/paths.rs`. Documented the learning in `.jules/sentinel.md`. Verification: Compile and verify permissions on Unix systems. Co-authored-by: bitcoiner-dev <75873427+bitcoiner-dev@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 30a4660 commit 9fc14fb

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

.jules/sentinel.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## 2024-03-24 - Insecure Default File Permissions
2+
**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.
3+
**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.
4+
**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.

src/paths.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ use std::path::{Path, PathBuf};
55
use crate::lock::now_unix;
66
use std::process;
77

8+
pub fn create_secure_dir_all<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
9+
let path = path.as_ref();
10+
#[cfg(unix)]
11+
{
12+
use std::os::unix::fs::DirBuilderExt;
13+
let mut builder = fs::DirBuilder::new();
14+
builder.recursive(true);
15+
builder.mode(0o700);
16+
builder.create(path)
17+
}
18+
#[cfg(not(unix))]
19+
{
20+
fs::create_dir_all(path)
21+
}
22+
}
23+
24+
pub fn write_secure_file<P: AsRef<Path>>(path: P, contents: &[u8]) -> std::io::Result<()> {
25+
let path = path.as_ref();
26+
#[cfg(unix)]
27+
{
28+
use std::os::unix::fs::OpenOptionsExt;
29+
use std::io::Write;
30+
let mut options = fs::OpenOptions::new();
31+
options.write(true).create(true).truncate(true).mode(0o600);
32+
let mut file = options.open(path)?;
33+
file.write_all(contents)?;
34+
file.sync_all()
35+
}
36+
#[cfg(not(unix))]
37+
{
38+
fs::write(path, contents)
39+
}
40+
}
41+
842
pub fn data_dir(config: &crate::config::ServiceConfig<'_>) -> std::path::PathBuf {
943
if let Some(path) = config.data_dir {
1044
path.to_path_buf()
@@ -25,7 +59,7 @@ pub fn profile_path(config: &crate::config::ServiceConfig<'_>) -> Result<PathBuf
2559
let root = data_dir(config);
2660
let profiles = root.join("profiles");
2761
if !profiles.exists() {
28-
fs::create_dir_all(&profiles)
62+
create_secure_dir_all(&profiles)
2963
.map_err(|e| AppError::Config(format!("failed to create profiles dir: {e}")))?;
3064
}
3165
Ok(profiles.join(format!("{}.json", config.profile)))
@@ -38,14 +72,14 @@ pub fn profile_lock_path(config: &crate::config::ServiceConfig<'_>) -> Result<Pa
3872
pub fn snapshot_dir(config: &crate::config::ServiceConfig<'_>) -> Result<PathBuf, AppError> {
3973
let root = data_dir(config);
4074
let directory = root.join("snapshots").join(config.profile);
41-
fs::create_dir_all(&directory)
75+
create_secure_dir_all(&directory)
4276
.map_err(|e| AppError::Config(format!("failed to create snapshot dir: {e}")))?;
4377
Ok(directory)
4478
}
4579

4680
pub fn write_bytes_atomic(path: &Path, bytes: &[u8], label: &str) -> Result<(), AppError> {
4781
if let Some(parent) = path.parent() {
48-
fs::create_dir_all(parent)
82+
create_secure_dir_all(parent)
4983
.map_err(|e| AppError::Config(format!("failed to create dir for {label}: {e}")))?;
5084
}
5185
let tmp_name = format!(
@@ -56,7 +90,7 @@ pub fn write_bytes_atomic(path: &Path, bytes: &[u8], label: &str) -> Result<(),
5690
);
5791
let tmp_path = path.with_file_name(tmp_name);
5892

59-
fs::write(&tmp_path, bytes)
93+
write_secure_file(&tmp_path, bytes)
6094
.map_err(|e| AppError::Config(format!("failed to write temp {label}: {e}")))?;
6195
if let Err(e) = fs::rename(&tmp_path, path) {
6296
let _ = fs::remove_file(&tmp_path);

0 commit comments

Comments
 (0)