vta_cli_common/
secure_file.rs1use std::path::Path;
31
32pub fn restrict_file_to_owner(path: &Path) -> std::io::Result<()> {
38 #[cfg(unix)]
39 {
40 use std::os::unix::fs::PermissionsExt;
41 let mut perm = std::fs::metadata(path)?.permissions();
42 perm.set_mode(0o600);
43 std::fs::set_permissions(path, perm)?;
44 }
45 #[cfg(windows)]
46 {
47 apply_windows_user_only_dacl(path)?;
48 }
49 #[cfg(not(any(unix, windows)))]
50 {
51 let _ = path;
52 }
53 Ok(())
54}
55
56pub fn restrict_dir_to_owner(path: &Path) -> std::io::Result<()> {
60 #[cfg(unix)]
61 {
62 use std::os::unix::fs::PermissionsExt;
63 let mut perm = std::fs::metadata(path)?.permissions();
64 perm.set_mode(0o700);
65 std::fs::set_permissions(path, perm)?;
66 }
67 #[cfg(windows)]
68 {
69 apply_windows_user_only_dacl(path)?;
70 }
71 #[cfg(not(any(unix, windows)))]
72 {
73 let _ = path;
74 }
75 Ok(())
76}
77
78#[cfg(windows)]
79fn apply_windows_user_only_dacl(path: &Path) -> std::io::Result<()> {
80 use std::process::Command;
81
82 let user = std::env::var("USERNAME").map_err(|_| {
90 std::io::Error::new(
91 std::io::ErrorKind::NotFound,
92 "USERNAME env var not set — cannot apply Windows user-only DACL",
93 )
94 })?;
95 let user_trimmed = user.trim();
96 if user_trimmed.is_empty() {
97 return Err(std::io::Error::new(
98 std::io::ErrorKind::InvalidData,
99 "USERNAME is empty — cannot apply Windows user-only DACL",
100 ));
101 }
102
103 let path_str = path.to_str().ok_or_else(|| {
104 std::io::Error::new(
105 std::io::ErrorKind::InvalidInput,
106 "path is not valid UTF-8 — cannot pass to icacls",
107 )
108 })?;
109
110 let output = Command::new("icacls")
115 .arg(path_str)
116 .arg("/inheritance:r")
117 .arg("/grant:r")
118 .arg(format!("{user_trimmed}:(F)"))
119 .output()?;
120
121 if !output.status.success() {
122 return Err(std::io::Error::other(format!(
123 "icacls failed ({}): {}",
124 output.status,
125 String::from_utf8_lossy(&output.stderr).trim()
126 )));
127 }
128 Ok(())
129}
130
131#[cfg(all(test, unix))]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn restrict_file_sets_0600_on_unix() {
137 use std::os::unix::fs::PermissionsExt;
138 let tmp = std::env::temp_dir().join(format!("vta-test-secure-{}", rand::random::<u32>()));
139 std::fs::create_dir_all(&tmp).unwrap();
140 let f = tmp.join("secret.bin");
141 std::fs::write(&f, b"sensitive").unwrap();
142
143 let mut perm = std::fs::metadata(&f).unwrap().permissions();
145 perm.set_mode(0o644);
146 std::fs::set_permissions(&f, perm).unwrap();
147
148 restrict_file_to_owner(&f).expect("restrict_file_to_owner succeeds");
149
150 let mode = std::fs::metadata(&f).unwrap().permissions().mode();
151 assert_eq!(mode & 0o777, 0o600);
152 let _ = std::fs::remove_dir_all(&tmp);
153 }
154
155 #[test]
156 fn restrict_dir_sets_0700_on_unix() {
157 use std::os::unix::fs::PermissionsExt;
158 let tmp = std::env::temp_dir().join(format!("vta-test-secure-{}", rand::random::<u32>()));
159 std::fs::create_dir_all(&tmp).unwrap();
160
161 let mut perm = std::fs::metadata(&tmp).unwrap().permissions();
162 perm.set_mode(0o755);
163 std::fs::set_permissions(&tmp, perm).unwrap();
164
165 restrict_dir_to_owner(&tmp).expect("restrict_dir_to_owner succeeds");
166
167 let mode = std::fs::metadata(&tmp).unwrap().permissions().mode();
168 assert_eq!(mode & 0o777, 0o700);
169 let _ = std::fs::remove_dir_all(&tmp);
170 }
171}