rollout: session JSONL files are created world-readable (mode 0644) on Unix
What version of Codex CLI is running?
Reproducible on main @ commit 163eac9306 (current HEAD as of 2026-05-07). The defect predates this commit; both open_log_file and the parent-directory creation have always lacked an explicit Unix mode.
What subscription do you have?
N/A — bug is in local file-handling code, independent of subscription.
Which model were you using?
N/A — bug occurs at session-create time, before any model interaction.
What platform is your computer?
Darwin 25.5.0 arm64 verified locally. Reproducible on any Unix host with the standard default umask of 022 (macOS and most Linux distros).
What terminal emulator and version are you using?
N/A — defect is in the core rollout recorder, terminal-independent.
What issue are you seeing?
codex-rs/rollout/src/recorder.rs::open_log_file (L1406-L1418) creates rollout JSONL files using
fs::create_dir_all(parent)?;
std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(path)
Neither call sets a Unix mode, so both fall back to 0o666 & ~umask and 0o777 & ~umask. With the standard umask of 022, this yields:
- session JSONL files at
0o644(world-readable) - session directories at
0o755(world-traversable)
~/.codex itself is 0o755 on macOS by default (verified locally). Result: any other unprivileged UID on the same host can cat the victim's complete Codex session history — user prompts, model responses, every tool I/O including file contents read by the agent and full command stdout/stderr.
The codebase already implements the correct pattern for ~/.codex/history.jsonl in codex-rs/message-history/src/lib.rs (L136-L147): OpenOptions::mode(0o600) plus an ensure_owner_only_permissions() post-hoc chmod. The same hardening was simply never applied to the rollout recorder.
What steps can reproduce the bug?
# As the victim:
codex
> any prompt that lands in the rollout
exit
ls -l ~/.codex/sessions/*/*/*/rollout-*.jsonl | head -1
# -rw-r--r-- ← bug. expected -rw-------
# Compare with the correctly-restricted message-history file:
ls -l ~/.codex/history.jsonl
# -rw------- ← correct
# As another local UID on the same host:
$ id
uid=502(attacker) gid=20(staff)
$ cat /Users/victim/.codex/sessions/*/*/*/rollout-*.jsonl | head
# Full transcript readable, no privilege escalation needed.
What is the expected behavior?
Rollout JSONL files and their newly-created parent directories should be owner-only on Unix (0o600 for files, 0o700 for directories), matching the pattern already used by codex-rs/message-history.
Additional information
Threat model. Local-only: another unprivileged UID on the same host. Practically affects:
- shared developer servers / jumphosts
- CI runners that host multiple build accounts
- JupyterHub-style multi-tenant notebook hosts
- multi-user macOS hosts (Family Sharing, fast-user-switching)
Not remotely exploitable; not a privilege-escalation vector. Consistent in spirit with the existing security-relevant decisions in message-history.
Per docs/contributing.md I have not opened a pull request. As a high-level outline of a potential fix, I prepared a single-file branch on my fork that mirrors the message-history pattern:
- branch: https://github.com/ioleksiuk/codex/tree/rollout-file-perms-0600
- commit (single-file diff): https://github.com/ioleksiuk/codex/commit/984e15a
The proposed change adds:
DirBuilderExt::mode(0o700)on parent-directory creation,OpenOptionsExt::mode(0o600)on file creation,- a
set_permissions(0o600)post-open belt-and-suspenders so files created by older Codex versions get tightened on first reopen.
Existing parent directories are intentionally left alone (DirBuilder::mode only applies on creation); operators on shared hosts may want to chmod 0700 ~/.codex once after upgrade. A possible follow-up is extending the post-hoc tightening to directory metadata as well, but that felt out of scope for the minimal fix.
Happy to provide further analysis, additional reproduction detail, or revise the proposed approach per team feedback. I also reported this via Bugcrowd; the program closed it as Not Applicable on the basis that multi-user threat models fall under their "compromised host OS / root-or-admin assumptions" exclusion. Sharing here in case the team's view of the risk differs given the in-tree precedent.