mirror of
https://github.com/openai/codex.git
synced 2026-10-21 03:35:45 +00:00
Add a symlink-safe reader for sensitive files (#39200)
## What changed - Export `read_sensitive_file_to_string` from `codex-exec-server`. - Require the opened path to be a regular disk file and avoid following its final symlink component on Unix or reparse point on Windows. - Read valid UTF-8 file contents asynchronously and return I/O errors for unsupported inputs. ## Testing Add tests covering regular files, directories, and symlinks. GitOrigin-RevId: 68809e94c0d3719e5685c064f9610a0455ffd8d7
This commit is contained in:
@@ -188,6 +188,7 @@ pub use protocol::TerminateResponse;
|
||||
pub use protocol::WriteParams;
|
||||
pub use protocol::WriteResponse;
|
||||
pub use protocol::WriteStatus;
|
||||
pub use regular_file::read_sensitive_file_to_string;
|
||||
pub use remote::RemoteEnvironmentConfig;
|
||||
pub use remote::run_remote_environment;
|
||||
pub use remote::run_remote_environment_until_shutdown;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
pub(crate) async fn open(path: &Path) -> io::Result<tokio::fs::File> {
|
||||
let mut options = tokio::fs::OpenOptions::new();
|
||||
@@ -16,6 +17,36 @@ pub(crate) async fn open(path: &Path) -> io::Result<tokio::fs::File> {
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
/// Reads a regular UTF-8 file without following a symlink at its final path component.
|
||||
pub async fn read_sensitive_file_to_string(path: &Path) -> io::Result<String> {
|
||||
let mut options = tokio::fs::OpenOptions::new();
|
||||
options.read(true);
|
||||
configure_open(&mut options);
|
||||
|
||||
#[cfg(unix)]
|
||||
options.custom_flags(libc::O_NONBLOCK | libc::O_NOFOLLOW);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
|
||||
|
||||
options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
}
|
||||
|
||||
let mut file = options.open(path).await?;
|
||||
let metadata = file.metadata().await?;
|
||||
if !is_disk_file(&file) || !metadata.is_file() || metadata.file_type().is_symlink() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("path `{}` is not a regular file", path.display()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn configure_open(options: &mut tokio::fs::OpenOptions) {
|
||||
options.custom_flags(libc::O_NONBLOCK);
|
||||
@@ -46,3 +77,7 @@ fn is_disk_file(file: &tokio::fs::File) -> bool {
|
||||
fn is_disk_file(_file: &tokio::fs::File) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "regular_file_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use super::read_sensitive_file_to_string;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_sensitive_file_reads_regular_file() {
|
||||
let directory = TempDir::new().expect("temporary directory");
|
||||
let path = directory.path().join("role.toml");
|
||||
tokio::fs::write(&path, "developer_instructions = 'stay focused'")
|
||||
.await
|
||||
.expect("write regular file");
|
||||
|
||||
assert_eq!(
|
||||
read_sensitive_file_to_string(&path)
|
||||
.await
|
||||
.expect("read regular file"),
|
||||
"developer_instructions = 'stay focused'",
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_sensitive_file_rejects_directory() {
|
||||
let directory = TempDir::new().expect("temporary directory");
|
||||
|
||||
assert!(
|
||||
read_sensitive_file_to_string(directory.path())
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(unix, windows))]
|
||||
#[tokio::test]
|
||||
async fn read_sensitive_file_rejects_symlink() {
|
||||
let directory = TempDir::new().expect("temporary directory");
|
||||
let target = directory.path().join("target.toml");
|
||||
let link = directory.path().join("role.toml");
|
||||
tokio::fs::write(&target, "model_provider = 'attacker'")
|
||||
.await
|
||||
.expect("write symlink target");
|
||||
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(&target, &link).expect("create symlink");
|
||||
#[cfg(windows)]
|
||||
std::os::windows::fs::symlink_file(&target, &link).expect("create symlink");
|
||||
|
||||
assert!(read_sensitive_file_to_string(&link).await.is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user