diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index 0cd9a22ad..9bfabbba5 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -19,14 +19,16 @@ use thiserror::Error; fn write_secrets_file(path: &Path, content: &str) -> std::io::Result<()> { #[cfg(unix)] { - use std::os::unix::fs::OpenOptionsExt; + use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; let mut file = OpenOptions::new() .write(true) .create(true) - .truncate(true) + .truncate(false) .mode(0o600) .open(path)?; + file.set_permissions(std::fs::Permissions::from_mode(0o600))?; + file.set_len(0)?; file.write_all(content.as_bytes()) } @@ -2068,6 +2070,28 @@ mod tests { Ok(()) } + #[test] + #[cfg(unix)] + fn test_existing_secrets_file_permissions_tightened_on_write() -> Result<(), ConfigError> { + use std::os::unix::fs::PermissionsExt; + + let dir = TempDir::new().unwrap(); + let config_file = NamedTempFile::new().unwrap(); + let secrets_path = dir.path().join("secrets.yaml"); + std::fs::write(&secrets_path, "existing: old\n")?; + std::fs::set_permissions(&secrets_path, std::fs::Permissions::from_mode(0o644))?; + + let config = Config::new_with_file_secrets(config_file.path(), &secrets_path)?; + config.set_secret("key", &"value")?; + + let value: String = config.get_secret("key")?; + assert_eq!(value, "value"); + let mode = std::fs::metadata(&secrets_path)?.permissions().mode() & 0o777; + assert_eq!(mode, 0o600); + + Ok(()) + } + #[test] fn test_merge_config_values_basic_override() { let mut base = Mapping::new();