Skip to content

fix: harden symlinked output and project config writes#15730

Open
viyatb-oai wants to merge 4 commits intomainfrom
codex/viyatb/harden-symlinked-output-and-project-config
Open

fix: harden symlinked output and project config writes#15730
viyatb-oai wants to merge 4 commits intomainfrom
codex/viyatb/harden-symlinked-output-and-project-config

Conversation

@viyatb-oai
Copy link
Copy Markdown
Collaborator

@viyatb-oai viyatb-oai commented Mar 25, 2026

Summary

  • Reject symlinked --output-last-message paths in codex exec with O_NOFOLLOW
  • Explicitly protect .codex/config.toml as a read-only leaf in split filesystem policies
  • Reject symlinked project config loads before parsing

Root Cause

Bubblewrap protects .codex/ as a read-only directory, but writes to .codex/config.toml can still follow a symlink to a writable target outside the protected subtree. Separately, --output-last-message used plain std::fs::write, which follows symlinks when callers place the output path in a sandbox-writable location.

Validation

  • just fmt
  • cargo test -p codex-exec last_message
  • cargo test -p codex-protocol
  • cargo test -p codex-linux-sandbox

@viyatb-oai viyatb-oai marked this pull request as ready for review March 27, 2026 18:18
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if !tokio::fs::metadata(&dot_codex)
.await
.map(|meta| meta.is_dir())
.unwrap_or(false)

P1 Badge Reject symlinked .codex directories before loading config

load_project_layers treats .codex as valid when metadata(...).is_dir() is true, but metadata follows symlinks. A symlinked .codex directory still passes and allows loading config.toml from outside the project, bypassing the new leaf-only symlink guard and undermining the hardening intent.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +799 to 827
match tokio::fs::symlink_metadata(&config_file).await {
Ok(metadata) => {
if metadata.file_type().is_symlink() {
let config_file_display = config_file.as_path().display();
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Project config file {config_file_display} must not be a symlink"),
));
}
}
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
layers.push(project_layer_entry(
trust_context,
&dot_codex_abs,
&layer_dir,
TomlValue::Table(toml::map::Map::new()),
/*config_toml_exists*/ false,
));
continue;
}
let config_file_display = config_file.as_path().display();
return Err(io::Error::new(
err.kind(),
format!("Failed to inspect project config file {config_file_display}: {err}"),
));
}
}
match tokio::fs::read_to_string(&config_file).await {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Eliminate TOCTOU between symlink check and file read

The code checks config.toml with symlink_metadata and then reads it in a later async call. This check-then-use gap allows replacing the file with a symlink between operations, so a symlinked config can still be read despite the new rejection logic.

Useful? React with 👍 / 👎.

@viyatb-oai viyatb-oai changed the title [codex] harden symlinked output and project config writes fix: harden symlinked output and project config writes Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant