codex - 💡(How to fix) Fix rollout: session JSONL files are created world-readable (mode 0644) on Unix

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Code Example

fs::create_dir_all(parent)?;
std::fs::OpenOptions::new()
    .append(true)
    .create(true)
    .open(path)

---

# 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.
RAW_BUFFERClick to expand / collapse

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:

The proposed change adds:

  1. DirBuilderExt::mode(0o700) on parent-directory creation,
  2. OpenOptionsExt::mode(0o600) on file creation,
  3. 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.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

codex - 💡(How to fix) Fix rollout: session JSONL files are created world-readable (mode 0644) on Unix