Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/crates/services/services-core/src/json_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,46 @@ impl JsonFileStore {
if let Err(source) = fs::write(&tmp_path, &bytes).await {
return Err(JsonFileStoreError::WriteTemp { source });
}
// Session artifacts carry full prompt/output content and must not be
// world-readable on multi-user hosts. The temp file inherits the
// process umask by default; force owner-only (0o600-equivalent) on
// unix before the rename publishes it. Best-effort: a
// set_permissions failure is logged, not fatal — the file is still
// written.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&tmp_path)
.await
.map(|metadata| metadata.permissions().mode());
match mode {
Ok(previous_mode) => {
// Preserve the owner read/write/execute bits and clear
// group/other access so the published file is
// 0o600-equivalent regardless of the process umask.
let restricted = previous_mode & 0o700;
if let Err(error) = fs::set_permissions(
&tmp_path,
std::fs::Permissions::from_mode(restricted),
)
.await
{
warn!(
"Failed to restrict permissions on temporary file {}: {} (continuing; the file may be readable by other local users)",
tmp_path.display(),
error
);
}
}
Err(error) => {
warn!(
"Failed to read permissions of temporary file {}: {} (continuing)",
tmp_path.display(),
error
);
}
}
}

let replacement = match policy {
AtomicWritePolicy::BestEffortReplace => {
Expand Down