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:
starr-openai
2026-04-12 18:36:03 -07:00
committed by GitHub
parent 7c1e41c8b6
commit d626dc3895
52 changed files with 2313 additions and 895 deletions

View File

@@ -176,7 +176,16 @@ impl ToolHandler for ApplyPatchHandler {
));
};
let fs = environment.get_filesystem();
match codex_apply_patch::maybe_parse_apply_patch_verified(&command, &cwd, fs.as_ref()).await
let sandbox = environment
.is_remote()
.then(|| turn.file_system_sandbox_context(/*additional_permissions*/ None));
match codex_apply_patch::maybe_parse_apply_patch_verified(
&command,
&cwd,
fs.as_ref(),
sandbox.as_ref(),
)
.await
{
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
let (file_paths, effective_additional_permissions, file_system_sandbox_policy) =
@@ -273,7 +282,14 @@ pub(crate) async fn intercept_apply_patch(
call_id: &str,
tool_name: &str,
) -> Result<Option<FunctionToolOutput>, FunctionCallError> {
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd, fs).await {
let sandbox = turn
.environment
.as_ref()
.filter(|env| env.is_remote())
.map(|_| turn.file_system_sandbox_context(/*additional_permissions*/ None));
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd, fs, sandbox.as_ref())
.await
{
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
session
.record_model_warning(

View File

@@ -24,13 +24,17 @@ async fn approval_keys_include_move_destination() {
+new content
*** End Patch"#;
let argv = vec!["apply_patch".to_string(), patch.to_string()];
let action =
match codex_apply_patch::maybe_parse_apply_patch_verified(&argv, &cwd, LOCAL_FS.as_ref())
.await
{
MaybeApplyPatchVerified::Body(action) => action,
other => panic!("expected patch body, got: {other:?}"),
};
let action = match codex_apply_patch::maybe_parse_apply_patch_verified(
&argv,
&cwd,
LOCAL_FS.as_ref(),
/*sandbox*/ None,
)
.await
{
MaybeApplyPatchVerified::Body(action) => action,
other => panic!("expected patch body, got: {other:?}"),
};
let keys = file_paths_for_action(&action);
assert_eq!(keys.len(), 2);

View File

@@ -92,10 +92,13 @@ impl ToolHandler for ViewImageHandler {
"view_image is unavailable in this session".to_string(),
));
};
let sandbox = environment
.is_remote()
.then(|| turn.file_system_sandbox_context(/*additional_permissions*/ None));
let metadata = environment
.get_filesystem()
.get_metadata(&abs_path)
.get_metadata(&abs_path, sandbox.as_ref())
.await
.map_err(|error| {
FunctionCallError::RespondToModel(format!(
@@ -112,7 +115,7 @@ impl ToolHandler for ViewImageHandler {
}
let file_bytes = environment
.get_filesystem()
.read_file(&abs_path)
.read_file(&abs_path, sandbox.as_ref())
.await
.map_err(|error| {
FunctionCallError::RespondToModel(format!(

View File

@@ -218,6 +218,9 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
if let Some(environment) = ctx.turn.environment.as_ref().filter(|env| env.is_remote()) {
let started_at = Instant::now();
let fs = environment.get_filesystem();
let sandbox = ctx
.turn
.file_system_sandbox_context(req.additional_permissions.clone());
let mut stdout = Vec::new();
let mut stderr = Vec::new();
let result = codex_apply_patch::apply_patch(
@@ -226,6 +229,7 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
&mut stdout,
&mut stderr,
fs.as_ref(),
Some(&sandbox),
)
.await;
let stdout = String::from_utf8_lossy(&stdout).into_owned();