Skip to content

Commit 984e15a

Browse files
committed
rollout: chmod session JSONL files to 0o600
`open_log_file` previously created session rollout files via `OpenOptions::new().append(true).create(true).open(path)` and parent directories via `fs::create_dir_all`, neither of which set a Unix mode. With the standard macOS / Linux default umask of 022, this yielded files at 0o644 (world-readable) and directories at 0o755 (world-traversable). Rollout JSONL holds the full conversation transcript: user prompts, model responses, and every tool I/O — file contents read by the agent, command stdout/stderr, and reasoning traces. On any host with more than one local UID (shared dev boxes, CI runners, JupyterHub nodes, multi-user macOS, build farms), other unprivileged users could `cat` the victim's complete Codex history without privilege escalation. The sibling `codex-rs/message-history` crate already implements the correct pattern for `~/.codex/history.jsonl`: `OpenOptions::mode(0o600)` plus an `ensure_owner_only_permissions` post-hoc chmod. This change mirrors that pattern in the rollout recorder: * `DirBuilder::mode(0o700)` so newly-created session directories are owner-only. * `OpenOptions::mode(0o600)` so newly-created rollout files are owner-only. * A post-open `set_permissions` belt-and-suspenders so files created by older Codex versions at 0o644 get tightened on next reopen. Existing parent directories are left alone (`DirBuilder::mode` only applies on creation); operators on shared hosts may want to `chmod 0700 ~/.codex` once after upgrade. Future work could extend the post-hoc tightening to directory metadata as well.
1 parent 163eac9 commit 984e15a

1 file changed

Lines changed: 38 additions & 4 deletions

File tree

codex-rs/rollout/src/recorder.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ use std::collections::HashSet;
44
use std::fs;
55
use std::fs::File;
66
use std::io::Error as IoError;
7+
#[cfg(unix)]
8+
use std::os::unix::fs::DirBuilderExt;
9+
#[cfg(unix)]
10+
use std::os::unix::fs::OpenOptionsExt;
11+
#[cfg(unix)]
12+
use std::os::unix::fs::PermissionsExt;
713
use std::path::Path;
814
use std::path::PathBuf;
915
use std::sync::Arc;
@@ -1410,11 +1416,39 @@ fn open_log_file(path: &Path) -> std::io::Result<File> {
14101416
path.display()
14111417
)));
14121418
};
1419+
1420+
// Create the parent chain owner-only on Unix so newly-introduced session
1421+
// dirs do not become world-traversable under the default umask. Existing
1422+
// directories are left alone (DirBuilder::mode only applies on creation).
1423+
#[cfg(unix)]
1424+
std::fs::DirBuilder::new()
1425+
.recursive(true)
1426+
.mode(0o700)
1427+
.create(parent)?;
1428+
#[cfg(not(unix))]
14131429
fs::create_dir_all(parent)?;
1414-
std::fs::OpenOptions::new()
1415-
.append(true)
1416-
.create(true)
1417-
.open(path)
1430+
1431+
let mut opts = std::fs::OpenOptions::new();
1432+
opts.append(true).create(true);
1433+
#[cfg(unix)]
1434+
opts.mode(0o600);
1435+
let file = opts.open(path)?;
1436+
1437+
// Belt-and-suspenders: if the rollout file already existed at a looser
1438+
// mode (e.g. created by an older Codex version under default umask),
1439+
// tighten it to 0o600 on first reopen. Mirrors the
1440+
// `ensure_owner_only_permissions` helper in `codex-rs/message-history`.
1441+
#[cfg(unix)]
1442+
{
1443+
let metadata = file.metadata()?;
1444+
if metadata.permissions().mode() & 0o777 != 0o600 {
1445+
let mut perms = metadata.permissions();
1446+
perms.set_mode(0o600);
1447+
std::fs::set_permissions(path, perms)?;
1448+
}
1449+
}
1450+
1451+
Ok(file)
14181452
}
14191453

14201454
/// Mutable state owned by the background rollout writer.

0 commit comments

Comments
 (0)