use std::io;
use std::path::Path;
pub fn restrict_to_owner(path: &Path) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
}
#[cfg(windows)]
{
if let Err(e) = restrict_windows(path) {
tracing::warn!(
error = %e,
path = %path.display(),
"could not restrict file to its owner; it keeps the directory's inherited ACL"
);
}
Ok(())
}
#[cfg(not(any(unix, windows)))]
{
let _ = path;
Ok(())
}
}
#[cfg(windows)]
fn restrict_windows(path: &Path) -> io::Result<()> {
use std::process::{Command, Stdio};
const OWNER_RIGHTS_SID: &str = "*S-1-3-4:(F)";
let output = Command::new("icacls")
.arg(path)
.args(["/inheritance:r", "/grant:r", OWNER_RIGHTS_SID])
.stdin(Stdio::null())
.output()?;
if output.status.success() {
return Ok(());
}
let detail = String::from_utf8_lossy(&output.stdout);
Err(io::Error::other(format!(
"icacls failed to restrict '{}' to its owner ({}): {}",
path.display(),
output.status,
detail.trim()
)))
}
#[cfg(test)]
mod tests {
use super::*;
fn write_secret(dir: &Path, name: &str) -> std::path::PathBuf {
let path = dir.join(name);
std::fs::write(&path, b"secret").unwrap();
path
}
#[test]
fn restricting_keeps_the_file_readable_by_this_process() {
let dir = tempfile::tempdir().unwrap();
let path = write_secret(dir.path(), "token");
restrict_to_owner(&path).unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"secret");
}
#[test]
#[cfg(unix)]
fn restricting_a_missing_file_is_an_error() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("nope");
assert!(restrict_to_owner(&missing).is_err());
}
#[test]
#[cfg(unix)]
fn unix_sets_mode_0600() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let path = write_secret(dir.path(), "token");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
restrict_to_owner(&path).unwrap();
let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "expected 0600, got {mode:o}");
}
#[test]
#[cfg(windows)]
fn windows_leaves_only_an_owner_ace() {
let dir = tempfile::tempdir().unwrap();
let path = write_secret(dir.path(), "token");
restrict_to_owner(&path).unwrap();
let out = std::process::Command::new("icacls")
.arg(&path)
.output()
.expect("icacls runs");
let acl = String::from_utf8_lossy(&out.stdout);
assert!(
acl.contains("OWNER RIGHTS") || acl.contains("S-1-3-4"),
"owner ACE missing from DACL: {acl}"
);
assert!(
!acl.contains("(I)"),
"inherited ACEs survived, secret may be readable by others: {acl}"
);
assert!(
!acl.contains("\\Users:") && !acl.contains("BUILTIN\\Users"),
"Users group still has access: {acl}"
);
}
}