Skip to content

Run exec-server fs operations through sandbox helper#17294

Merged
starr-openai merged 23 commits intomainfrom
exec-server-fs-sandbox-arg0-20260409
Apr 13, 2026
Merged

Run exec-server fs operations through sandbox helper#17294
starr-openai merged 23 commits intomainfrom
exec-server-fs-sandbox-arg0-20260409

Conversation

@starr-openai
Copy link
Copy Markdown
Contributor

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)

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

Here are some automated review suggestions for this pull request.

Reviewed commit: 41f0e9c405

ℹ️ 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 thread codex-rs/exec-server/src/server.rs Outdated
starr-openai added a commit that referenced this pull request Apr 10, 2026
Discover exec-server arg0 helper paths from PATH for the default public server entrypoint, preserving sandboxed filesystem support when callers do not pass runtime paths explicitly.

Co-authored-by: Codex <noreply@openai.com>
Comment thread codex-rs/arg0/src/lib.rs Outdated
pub codex_self_exe: Option<PathBuf>,
/// Path to the `codex-fs` helper alias used by exec-server filesystem
/// sandboxing.
pub codex_fs_exe: Option<PathBuf>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isn't it the same as codex_self_exe ?

Comment thread codex-rs/arg0/src/lib.rs Outdated

#[cfg(windows)]
{
let arg1 = if *filename == CODEX_FS_HELPER_ARG0 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this seems wrong. we are not writing scripts for what's being invoked
we are writing scripts that allow us to invoke apply_patch

I'm also not sure we need a script for fs-helper at all.

Comment thread codex-rs/arg0/src/lib.rs Outdated
codex_fs_exe: {
#[cfg(windows)]
{
Some(path.join(format!("{CODEX_FS_HELPER_ARG0}.bat")))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we don't need for helper to be invocable via shell, we can create the correct command line directly. can we avoid the entire bat business?

Comment thread codex-rs/core/tests/common/lib.rs Outdated
// Allow a bit more time to accommodate async startup work (e.g. config IO, tool discovery)
let ev = timeout(wait_time.max(Duration::from_secs(10)), codex.next_event())
// Allow extra time to accommodate async startup work (e.g. config IO,
// tool discovery). Windows CI can take longer to launch helper
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this probably shouldn't be done in this pr.

Some(sandbox_policy) => {
self.run_sandboxed(
sandbox_policy,
enforce_read_access(&params.path, Some(sandbox_policy)),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why do we still have enforce_read_access?


#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "operation", rename_all = "camelCase")]
pub(crate) enum FsHelperRequest {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can we reuse the same requests and responses we use for trait?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

FsWriteFileParams/FsWriteFileResponse

sandbox_policy,
enforce_read_access(&params.path, Some(sandbox_policy)),
FsHelperRequest::ReadFile { path: params.path },
"readFile",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can we reuse op names?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fs/writeFile etc?

message.contains("is not permitted")
|| message.contains("Operation not permitted")
|| message.contains("Permission denied")
|| message.contains("No such file"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

no such file is a bit sus

Comment thread codex-rs/exec-server/src/server.rs Outdated

use crate::ExecServerRuntimePaths;

pub async fn run_main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we should probably drop all overloads that don't take runtime_paths to ensure they are always passed in correctly.

}
}

fn find_program_in_path(path_env: Option<&OsString>, program: &str) -> Option<PathBuf> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is wayy to much reimplementation that we don't do for any other commands.

}

fn enforce_read_access(
pub(crate) fn enforce_read_access(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

these all need to go

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated

pub(crate) async fn run(
&self,
sandbox_policy: &SandboxPolicy,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is SandboxPolicy enough? for apply patch we include a bunch of other permission

let req = ApplyPatchRequest {
action: apply.action,
file_paths,
changes,
exec_approval_requirement: apply.exec_approval_requirement,
additional_permissions: effective_additional_permissions
.additional_permissions,
permissions_preapproved: effective_additional_permissions
.permissions_preapproved,
timeout_ms: None,
};

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
&helper_sandbox_policy,
cwd.as_path(),
);
let network_policy = NetworkSandboxPolicy::from(&helper_sandbox_policy);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

network is always disabled

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
}

fn helper_program(&self) -> Result<PathBuf, JSONRPCErrorError> {
let helper = self.runtime_paths.codex_fs_exe.clone().ok_or_else(|| {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can codex_fs_exe be non optional?

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
.to_string(),
)
})?;
if !helper.is_absolute() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

and use absolute path type?

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
Ok(helper)
}

fn helper_read_permissions(&self, helper_path: &Path) -> PermissionProfile {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this logic must exist somwhere already, otherwise how would apply_patch work today?

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
file_system_policy,
network_policy,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why?

Copy link
Copy Markdown
Collaborator

@pakrym-oai pakrym-oai left a comment

Choose a reason for hiding this comment

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

Directionally correct. Few things:

  1. Can we reuse more of existing request/response types/ops
  2. Remove "manual" sandboxing
  3. Reduce amount of custom sandbox configuration logic and check whether we need more parameters to fully migrate something like apply_patch
  4. Remove .bat support and ability to start fs-helper directly via shell.

@starr-openai
Copy link
Copy Markdown
Contributor Author

Updated this PR to 60bb6343c0ab52127a744025592276a2c26c3daf.

Focused devbox validation passing:

  • //codex-rs/apply-patch:apply-patch-all-test
  • //codex-rs/apply-patch:apply-patch-unit-tests
  • //codex-rs/core:core-all-test --test_arg=suite::apply_patch_cli --test_arg=--test-threads=1
  • //codex-rs/core:core-all-test --test_arg=suite::view_image::view_image_tool --test_arg=--test-threads=1
  • //codex-rs/core:core-unit-tests --test_arg=--test-threads=1

I am digging into the remaining unfiltered core-all-test devbox failures separately; the focused apply_patch/view_image sandbox paths are green.

@starr-openai
Copy link
Copy Markdown
Contributor Author

Follow-up validation: the unfiltered core aggregate passes on the devbox when Bazel receives the correct test environment.

Passing command:

cd /tmp/codex-worktrees/exec-server-fs-sandbox-arg0-20260409/codex-rs && \
  export PATH=$HOME/code/openai/project/dotslash-gen/bin:$HOME/.local/bin:$PATH && \
  bazel test --bes_backend= --bes_results_url= --test_output=errors \
    --test_env=CODEX_JS_REPL_NODE_PATH=/home/dev-user/.nvm/versions/node/v25.9.0/bin/node \
    --test_env=PATH \
    //codex-rs/core:core-all-test --test_arg=--test-threads=1

Result: //codex-rs/core:core-all-test PASSED in 340.2s.

The earlier broad failure was caused by Bazel test actions resolving /usr/bin/node (v18.19.1), below the JS REPL minimum v22.22.0, and by the test action not exposing PATH to the shell snapshot assertions. The focused apply_patch/view_image sandbox paths remained green.

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "operation", content = "params")]
pub(crate) enum FsHelperRequest {
#[serde(rename = "fs/readFile")]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

super nit: reuse consts?

.map_or_else(|| self.cwd.clone(), |path| self.cwd.join(path))
}

pub(crate) fn file_system_sandbox_context(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

async fn effective_patch_permissions(
session: &Session,
looks much more complicated that this.

ok to punt until we convert the main apply patch path

"view_image is unavailable in this session".to_string(),
));
};
let sandbox = environment
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why only for remote? local too, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

eh, let's keep it remote only so we don't break existing usage.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

eh, let's keep it remote only so we don't break existing usage.

&self.runtime_paths.codex_self_exe
}

fn helper_permissions(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why doesn't apply_patch need to do this?

Comment thread codex-rs/exec-server/src/fs_sandbox.rs Outdated
.map_err(|err| invalid_request(format!("failed to prepare fs sandbox: {err}")))
}

fn helper_program(&self) -> &AbsolutePathBuf {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: inline

starr-openai added a commit that referenced this pull request Apr 10, 2026
Discover exec-server arg0 helper paths from PATH for the default public server entrypoint, preserving sandboxed filesystem support when callers do not pass runtime paths explicitly.

Co-authored-by: Codex <noreply@openai.com>
@starr-openai starr-openai force-pushed the exec-server-fs-sandbox-arg0-20260409 branch from 2a0e0ff to d0f6dce Compare April 10, 2026 23:03
@starr-openai starr-openai force-pushed the exec-server-fs-sandbox-arg0-20260409 branch from 96279d4 to 1a252f0 Compare April 11, 2026 00:32
starr-openai and others added 18 commits April 10, 2026 17:36
Route sandboxed exec-server filesystem operations through the codex-fs arg0 helper over stdin/stdout, while keeping direct local execution for policies that do not require platform sandboxing.

Co-authored-by: Codex <noreply@openai.com>
Discover exec-server arg0 helper paths from PATH for the default public server entrypoint, preserving sandboxed filesystem support when callers do not pass runtime paths explicitly.

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Windows CI can spend more than ten seconds launching helper processes inside the large core aggregate test binary, which makes apply-patch harness tests time out while the turn is still running. Keep the existing non-Windows floor and only raise the Windows floor.

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Collapse ExecutorFileSystem onto one optional-sandbox method per filesystem operation and update unsandboxed callers to pass an explicit None. Also replace helper response into_* demux helpers with expect_* variants that report the unexpected operation directly.

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
@starr-openai starr-openai force-pushed the exec-server-fs-sandbox-arg0-20260409 branch from 1a252f0 to 1059905 Compare April 11, 2026 00:38
Co-authored-by: Codex <noreply@openai.com>
starr-openai and others added 4 commits April 12, 2026 14:57
Strip the implicit cwd grant from request-side filesystem preflight so absolute fs API calls cannot escape explicit readable roots only because Bazel/TMPDIR place test tempdirs under the process cwd.

Add regression coverage for the cwd/TMPDIR overlap case that previously returned NotFound instead of an upfront sandbox denial.

Co-authored-by: Codex <noreply@openai.com>
Back out the request-side preflight split until the failing absolute-path regression is reworked around the actual AbsolutePathBuf semantics.

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
@starr-openai starr-openai merged commit d626dc3 into main Apr 13, 2026
26 of 30 checks passed
@starr-openai starr-openai deleted the exec-server-fs-sandbox-arg0-20260409 branch April 13, 2026 01:36
@github-actions github-actions bot locked and limited conversation to collaborators Apr 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants