mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Run exec-server fs operations through sandbox helper (#17294)
## Summary - run exec-server filesystem RPCs requiring sandboxing through a `codex-fs` arg0 helper over stdin/stdout - keep direct local filesystem execution for `DangerFullAccess` and external sandbox policies - remove the standalone exec-server binary path in favor of top-level arg0 dispatch/runtime paths - add sandbox escape regression coverage for local and remote filesystem paths ## Validation - `just fmt` - `git diff --check` - remote devbox: `cd codex-rs && bazel test --bes_backend= --bes_results_url= //codex-rs/exec-server:all` (6/6 passed) --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
7c1e41c8b6
commit
d626dc3895
@@ -135,6 +135,7 @@ pub async fn maybe_parse_apply_patch_verified(
|
||||
argv: &[String],
|
||||
cwd: &AbsolutePathBuf,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&codex_exec_server::FileSystemSandboxContext>,
|
||||
) -> MaybeApplyPatchVerified {
|
||||
// Detect a raw patch body passed directly as the command or as the body of a shell
|
||||
// script. In these cases, report an explicit error rather than applying the patch.
|
||||
@@ -170,7 +171,7 @@ pub async fn maybe_parse_apply_patch_verified(
|
||||
);
|
||||
}
|
||||
Hunk::DeleteFile { .. } => {
|
||||
let content = match fs.read_file_text(&path).await {
|
||||
let content = match fs.read_file_text(&path, sandbox).await {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
return MaybeApplyPatchVerified::CorrectnessError(
|
||||
@@ -192,7 +193,7 @@ pub async fn maybe_parse_apply_patch_verified(
|
||||
let ApplyPatchFileUpdate {
|
||||
unified_diff,
|
||||
content: contents,
|
||||
} = match unified_diff_from_chunks(&path, &chunks, fs).await {
|
||||
} = match unified_diff_from_chunks(&path, &chunks, fs, sandbox).await {
|
||||
Ok(diff) => diff,
|
||||
Err(e) => {
|
||||
return MaybeApplyPatchVerified::CorrectnessError(e);
|
||||
@@ -467,7 +468,8 @@ mod tests {
|
||||
maybe_parse_apply_patch_verified(
|
||||
&args,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref()
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await,
|
||||
MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation)
|
||||
@@ -483,7 +485,8 @@ mod tests {
|
||||
maybe_parse_apply_patch_verified(
|
||||
&args,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref()
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await,
|
||||
MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation)
|
||||
@@ -693,9 +696,10 @@ PATCH"#,
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -2,2 +2,2 @@
|
||||
bar
|
||||
-baz
|
||||
@@ -731,9 +735,10 @@ PATCH"#,
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -3 +3,2 @@
|
||||
baz
|
||||
+quux
|
||||
@@ -770,6 +775,7 @@ PATCH"#,
|
||||
&argv,
|
||||
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -823,6 +829,7 @@ PATCH"#,
|
||||
&argv,
|
||||
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
let action = match result {
|
||||
|
||||
@@ -12,6 +12,7 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub use parser::Hunk;
|
||||
@@ -184,6 +185,7 @@ pub async fn apply_patch(
|
||||
stdout: &mut impl std::io::Write,
|
||||
stderr: &mut impl std::io::Write,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> Result<(), ApplyPatchError> {
|
||||
let hunks = match parse_patch(patch) {
|
||||
Ok(source) => source.hunks,
|
||||
@@ -207,7 +209,7 @@ pub async fn apply_patch(
|
||||
}
|
||||
};
|
||||
|
||||
apply_hunks(&hunks, cwd, stdout, stderr, fs).await?;
|
||||
apply_hunks(&hunks, cwd, stdout, stderr, fs, sandbox).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -219,9 +221,10 @@ pub async fn apply_hunks(
|
||||
stdout: &mut impl std::io::Write,
|
||||
stderr: &mut impl std::io::Write,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> Result<(), ApplyPatchError> {
|
||||
// Delegate to a helper that applies each hunk to the filesystem.
|
||||
match apply_hunks_to_files(hunks, cwd, fs).await {
|
||||
match apply_hunks_to_files(hunks, cwd, fs, sandbox).await {
|
||||
Ok(affected) => {
|
||||
print_summary(&affected, stdout).map_err(ApplyPatchError::from)?;
|
||||
Ok(())
|
||||
@@ -257,6 +260,7 @@ async fn apply_hunks_to_files(
|
||||
hunks: &[Hunk],
|
||||
cwd: &AbsolutePathBuf,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> anyhow::Result<AffectedPaths> {
|
||||
if hunks.is_empty() {
|
||||
anyhow::bail!("No files were modified.");
|
||||
@@ -271,23 +275,27 @@ async fn apply_hunks_to_files(
|
||||
match hunk {
|
||||
Hunk::AddFile { contents, .. } => {
|
||||
if let Some(parent_abs) = path_abs.parent() {
|
||||
fs.create_directory(&parent_abs, CreateDirectoryOptions { recursive: true })
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to create parent directories for {}",
|
||||
path_abs.display()
|
||||
)
|
||||
})?;
|
||||
fs.create_directory(
|
||||
&parent_abs,
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
sandbox,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to create parent directories for {}",
|
||||
path_abs.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
fs.write_file(&path_abs, contents.clone().into_bytes())
|
||||
fs.write_file(&path_abs, contents.clone().into_bytes(), sandbox)
|
||||
.await
|
||||
.with_context(|| format!("Failed to write file {}", path_abs.display()))?;
|
||||
added.push(affected_path);
|
||||
}
|
||||
Hunk::DeleteFile { .. } => {
|
||||
let result: io::Result<()> = async {
|
||||
let metadata = fs.get_metadata(&path_abs).await?;
|
||||
let metadata = fs.get_metadata(&path_abs, sandbox).await?;
|
||||
if metadata.is_directory {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
@@ -300,6 +308,7 @@ async fn apply_hunks_to_files(
|
||||
recursive: false,
|
||||
force: false,
|
||||
},
|
||||
sandbox,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -311,13 +320,14 @@ async fn apply_hunks_to_files(
|
||||
move_path, chunks, ..
|
||||
} => {
|
||||
let AppliedPatch { new_contents, .. } =
|
||||
derive_new_contents_from_chunks(&path_abs, chunks, fs).await?;
|
||||
derive_new_contents_from_chunks(&path_abs, chunks, fs, sandbox).await?;
|
||||
if let Some(dest) = move_path {
|
||||
let dest_abs = AbsolutePathBuf::resolve_path_against_base(dest, cwd);
|
||||
if let Some(parent_abs) = dest_abs.parent() {
|
||||
fs.create_directory(
|
||||
&parent_abs,
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
sandbox,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
@@ -327,11 +337,11 @@ async fn apply_hunks_to_files(
|
||||
)
|
||||
})?;
|
||||
}
|
||||
fs.write_file(&dest_abs, new_contents.into_bytes())
|
||||
fs.write_file(&dest_abs, new_contents.into_bytes(), sandbox)
|
||||
.await
|
||||
.with_context(|| format!("Failed to write file {}", dest_abs.display()))?;
|
||||
let result: io::Result<()> = async {
|
||||
let metadata = fs.get_metadata(&path_abs).await?;
|
||||
let metadata = fs.get_metadata(&path_abs, sandbox).await?;
|
||||
if metadata.is_directory {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
@@ -344,6 +354,7 @@ async fn apply_hunks_to_files(
|
||||
recursive: false,
|
||||
force: false,
|
||||
},
|
||||
sandbox,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -353,7 +364,7 @@ async fn apply_hunks_to_files(
|
||||
})?;
|
||||
modified.push(affected_path);
|
||||
} else {
|
||||
fs.write_file(&path_abs, new_contents.into_bytes())
|
||||
fs.write_file(&path_abs, new_contents.into_bytes(), sandbox)
|
||||
.await
|
||||
.with_context(|| format!("Failed to write file {}", path_abs.display()))?;
|
||||
modified.push(affected_path);
|
||||
@@ -379,8 +390,9 @@ async fn derive_new_contents_from_chunks(
|
||||
path_abs: &AbsolutePathBuf,
|
||||
chunks: &[UpdateFileChunk],
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> std::result::Result<AppliedPatch, ApplyPatchError> {
|
||||
let original_contents = fs.read_file_text(path_abs).await.map_err(|err| {
|
||||
let original_contents = fs.read_file_text(path_abs, sandbox).await.map_err(|err| {
|
||||
ApplyPatchError::IoError(IoError {
|
||||
context: format!("Failed to read file to update {}", path_abs.display()),
|
||||
source: err,
|
||||
@@ -540,8 +552,9 @@ pub async fn unified_diff_from_chunks(
|
||||
path_abs: &AbsolutePathBuf,
|
||||
chunks: &[UpdateFileChunk],
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
|
||||
unified_diff_from_chunks_with_context(path_abs, chunks, /*context*/ 1, fs).await
|
||||
unified_diff_from_chunks_with_context(path_abs, chunks, /*context*/ 1, fs, sandbox).await
|
||||
}
|
||||
|
||||
pub async fn unified_diff_from_chunks_with_context(
|
||||
@@ -549,11 +562,12 @@ pub async fn unified_diff_from_chunks_with_context(
|
||||
chunks: &[UpdateFileChunk],
|
||||
context: usize,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
|
||||
let AppliedPatch {
|
||||
original_contents,
|
||||
new_contents,
|
||||
} = derive_new_contents_from_chunks(path_abs, chunks, fs).await?;
|
||||
} = derive_new_contents_from_chunks(path_abs, chunks, fs, sandbox).await?;
|
||||
let text_diff = TextDiff::from_lines(&original_contents, &new_contents);
|
||||
let unified_diff = text_diff.unified_diff().context_radius(context).to_string();
|
||||
Ok(ApplyPatchFileUpdate {
|
||||
@@ -614,6 +628,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -667,9 +682,16 @@ mod tests {
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
|
||||
apply_patch(&patch, &cwd, &mut stdout, &mut stderr, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
apply_patch(
|
||||
&patch,
|
||||
&cwd,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(fs::read_to_string(&relative_add).unwrap(), "relative add\n");
|
||||
assert_eq!(fs::read_to_string(&absolute_add).unwrap(), "absolute add\n");
|
||||
@@ -709,6 +731,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -744,6 +767,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -783,6 +807,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -831,6 +856,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -888,6 +914,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -931,6 +958,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -973,6 +1001,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1019,9 +1048,14 @@ mod tests {
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
};
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, update_file_chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff = unified_diff_from_chunks(
|
||||
&path_abs,
|
||||
update_file_chunks,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -1,4 +1,4 @@
|
||||
foo
|
||||
-bar
|
||||
@@ -1061,9 +1095,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -1,2 +1,2 @@
|
||||
-foo
|
||||
+FOO
|
||||
@@ -1101,9 +1136,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -2,2 +2,2 @@
|
||||
bar
|
||||
-baz
|
||||
@@ -1139,9 +1175,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let expected_diff = r#"@@ -3 +3,2 @@
|
||||
baz
|
||||
+quux
|
||||
@@ -1188,9 +1225,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let path_abs = path.as_path().abs();
|
||||
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let diff =
|
||||
unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref(), /*sandbox*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let expected_diff = r#"@@ -1,6 +1,7 @@
|
||||
a
|
||||
@@ -1219,6 +1257,7 @@ mod tests {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1258,6 +1297,7 @@ g
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
|
||||
@@ -71,6 +71,7 @@ pub fn run_main() -> i32 {
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
codex_exec_server::LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
)) {
|
||||
Ok(()) => {
|
||||
// Flush to ensure output ordering when used in pipelines.
|
||||
|
||||
Reference in New Issue
Block a user