mirror of
https://github.com/googleworkspace/cli.git
synced 2026-09-14 16:47:13 +08:00
🔒 fix: secure atomic file creation with restrictive permissions
Use `0o600` permissions (read/write only by the owner) when creating temporary files in `atomic_write` and `atomic_write_async`. This prevents a race condition on Unix systems where a sensitive file (like credentials or tokens) could be created with default permissions (e.g., world-readable) before its permissions are corrected. The fix uses `OpenOptions::mode(0o600)` on Unix and also adds `sync_all()` for better data durability. A new test `test_atomic_write_permissions` verifies the fix on Unix. A changeset file is also included. Co-authored-by: jpoehnelt <3392975+jpoehnelt@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@googleworkspace/cli": patch
|
||||
---
|
||||
|
||||
🔒 Fix: Use restrictive permissions (0600) for temporary files created during atomic writes in fs_util.
|
||||
+60
-2
@@ -40,7 +40,21 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
.map(|p| p.join(&tmp_name))
|
||||
.unwrap_or_else(|| std::path::PathBuf::from(&tmp_name));
|
||||
|
||||
std::fs::write(&tmp_path, data)?;
|
||||
{
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.write(true).create(true).truncate(true);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
opts.mode(0o600);
|
||||
}
|
||||
let mut file = opts.open(&tmp_path)?;
|
||||
file.write_all(data)?;
|
||||
file.sync_all()?;
|
||||
}
|
||||
|
||||
std::fs::rename(&tmp_path, path)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -56,7 +70,21 @@ pub async fn atomic_write_async(path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
.map(|p| p.join(&tmp_name))
|
||||
.unwrap_or_else(|| std::path::PathBuf::from(&tmp_name));
|
||||
|
||||
tokio::fs::write(&tmp_path, data).await?;
|
||||
{
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.write(true).create(true).truncate(true);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::os::unix::fs::OpenOptionsExt;
|
||||
opts.mode(0o600);
|
||||
}
|
||||
let mut file = opts.open(&tmp_path).await?;
|
||||
file.write_all(data).await?;
|
||||
file.sync_all().await?;
|
||||
}
|
||||
|
||||
tokio::fs::rename(&tmp_path, path).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -99,4 +127,34 @@ mod tests {
|
||||
atomic_write_async(&path, b"async hello").await.unwrap();
|
||||
assert_eq!(fs::read(&path).unwrap(), b"async hello");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_atomic_write_permissions() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
// Sync
|
||||
let path_sync = dir.path().join("sync.txt");
|
||||
atomic_write(&path_sync, b"sync").unwrap();
|
||||
let meta_sync = fs::metadata(&path_sync).unwrap();
|
||||
assert_eq!(
|
||||
meta_sync.permissions().mode() & 0o777,
|
||||
0o600,
|
||||
"Sync atomic_write should create file with 0600 permissions"
|
||||
);
|
||||
|
||||
// Async
|
||||
let path_async = dir.path().join("async.txt");
|
||||
atomic_write_async(&path_async, b"async").await.unwrap();
|
||||
let meta_async = fs::metadata(&path_async).unwrap();
|
||||
assert_eq!(
|
||||
meta_async.permissions().mode() & 0o777,
|
||||
0o600,
|
||||
"Async atomic_write_async should create file with 0600 permissions"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user