mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
[codex] expose sandbox ingress to host
This commit is contained in:
@@ -1374,6 +1374,7 @@ mod tests {
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
|
||||
@@ -1172,6 +1172,7 @@ mod tests {
|
||||
env: Default::default(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
@@ -1212,6 +1213,7 @@ mod tests {
|
||||
env: Default::default(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
|
||||
@@ -140,6 +140,7 @@ impl FileSystemSandboxRunner {
|
||||
enforce_managed_network: false,
|
||||
environment_id: None,
|
||||
network: None,
|
||||
ingress: None,
|
||||
sandbox_policy_cwd: &cwd.uri,
|
||||
codex_linux_sandbox_exe: self.runtime_paths.codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
|
||||
@@ -14,6 +14,10 @@ use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
use codex_protocol::shell_environment;
|
||||
use codex_sandboxing::SandboxType;
|
||||
#[cfg(unix)]
|
||||
use codex_sandboxing::ingress::IngressListener;
|
||||
#[cfg(unix)]
|
||||
use codex_sandboxing::ingress::IngressListenerError;
|
||||
use codex_sandboxing::is_likely_sandbox_denied;
|
||||
use codex_utils_pty::ExecCommandSession;
|
||||
use codex_utils_pty::ProcessSignal as PtyProcessSignal;
|
||||
@@ -234,8 +238,30 @@ impl LocalProcess {
|
||||
params: ExecParams,
|
||||
) -> Result<(ExecResponse, watch::Sender<u64>, ExecProcessEventLog), JSONRPCErrorError> {
|
||||
let process_id = params.process_id.clone();
|
||||
let prepared =
|
||||
prepare_exec_request(¶ms, child_env(¶ms), self.runtime_paths.as_ref())?;
|
||||
let mut env = child_env(¶ms);
|
||||
#[cfg(unix)]
|
||||
let ingress = match IngressListener::prepare(params.ingress) {
|
||||
Ok(ingress) => ingress,
|
||||
Err(error) => return Err(ingress_error(error)),
|
||||
};
|
||||
#[cfg(unix)]
|
||||
let inherited_fds = ingress
|
||||
.as_ref()
|
||||
.map(|ingress| vec![ingress.inherited_fd()])
|
||||
.unwrap_or_default();
|
||||
#[cfg(unix)]
|
||||
if let Some(ingress) = ingress.as_ref() {
|
||||
ingress.add_to_child_env(&mut env);
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
if params.ingress.is_some() {
|
||||
return Err(invalid_params(
|
||||
"ingress is only supported on Unix exec-server hosts".to_string(),
|
||||
));
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
let inherited_fds = Vec::new();
|
||||
let prepared = prepare_exec_request(¶ms, env, self.runtime_paths.as_ref())?;
|
||||
let (program, args) = prepared
|
||||
.command
|
||||
.split_first()
|
||||
@@ -256,31 +282,34 @@ impl LocalProcess {
|
||||
}
|
||||
|
||||
let spawned_result = if params.tty {
|
||||
codex_utils_pty::spawn_pty_process(
|
||||
codex_utils_pty::pty::spawn_process_with_inherited_fds(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
TerminalSize::default(),
|
||||
&inherited_fds,
|
||||
)
|
||||
.await
|
||||
} else if params.pipe_stdin {
|
||||
codex_utils_pty::spawn_pipe_process(
|
||||
codex_utils_pty::pipe::spawn_process_with_inherited_fds(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
&inherited_fds,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::spawn_pipe_process_no_stdin(
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin_with_inherited_fds(
|
||||
program,
|
||||
args,
|
||||
prepared.cwd.as_path(),
|
||||
&prepared.env,
|
||||
&prepared.arg0,
|
||||
&inherited_fds,
|
||||
)
|
||||
.await
|
||||
};
|
||||
@@ -297,6 +326,8 @@ impl LocalProcess {
|
||||
return Err(internal_error(err.to_string()));
|
||||
}
|
||||
};
|
||||
#[cfg(unix)]
|
||||
drop(ingress);
|
||||
|
||||
let output_notify = Arc::new(Notify::new());
|
||||
let (wake_tx, _wake_rx) = watch::channel(0);
|
||||
@@ -584,6 +615,21 @@ fn child_env(params: &ExecParams) -> HashMap<String, String> {
|
||||
env
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ingress_error(error: IngressListenerError) -> JSONRPCErrorError {
|
||||
match error {
|
||||
IngressListenerError::InvalidPort => {
|
||||
invalid_request("ingress port must be between 1 and 65535".to_string())
|
||||
}
|
||||
IngressListenerError::PortInUse(port) => invalid_request(format!(
|
||||
"ingress port {port} is already in use by another process"
|
||||
)),
|
||||
IngressListenerError::Io(error) => {
|
||||
internal_error(format!("failed to prepare ingress listener: {error}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shell_environment_policy(env_policy: &ExecEnvPolicy) -> ShellEnvironmentPolicy {
|
||||
ShellEnvironmentPolicy {
|
||||
inherit: env_policy.inherit.clone(),
|
||||
@@ -995,6 +1041,7 @@ mod tests {
|
||||
env,
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
|
||||
@@ -32,6 +32,11 @@ pub(crate) fn prepare_exec_request(
|
||||
runtime_paths: Option<&ExecServerRuntimePaths>,
|
||||
) -> Result<PreparedExecRequest, JSONRPCErrorError> {
|
||||
let Some(sandbox_context) = params.sandbox.as_ref() else {
|
||||
if params.ingress.is_some() {
|
||||
return Err(invalid_params(
|
||||
"ingress requires sandbox intent on exec-server".to_string(),
|
||||
));
|
||||
}
|
||||
return Ok(PreparedExecRequest {
|
||||
command: params.argv.clone(),
|
||||
cwd: native_path(¶ms.cwd, "cwd")?,
|
||||
@@ -125,6 +130,7 @@ pub(crate) fn prepare_exec_request(
|
||||
enforce_managed_network: params.enforce_managed_network,
|
||||
environment_id: None,
|
||||
network: None,
|
||||
ingress: params.ingress,
|
||||
sandbox_policy_cwd,
|
||||
codex_linux_sandbox_exe: runtime_paths.codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
|
||||
@@ -43,6 +43,7 @@ fn sandbox_request_wraps_native_argv_on_executor() {
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
@@ -81,6 +82,47 @@ fn sandbox_request_wraps_native_argv_on_executor() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn sandbox_request_forwards_ingress_to_linux_sandbox() {
|
||||
let cwd: AbsolutePathBuf = std::env::current_dir()
|
||||
.expect("current directory")
|
||||
.try_into()
|
||||
.expect("absolute cwd");
|
||||
let cwd_uri = PathUri::from_abs_path(&cwd);
|
||||
let self_exe = std::env::current_exe().expect("current executable");
|
||||
let runtime_paths =
|
||||
ExecServerRuntimePaths::new(self_exe.clone(), Some(self_exe)).expect("runtime paths");
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::workspace_write(),
|
||||
cwd_uri.clone(),
|
||||
);
|
||||
let params = ExecParams {
|
||||
process_id: ProcessId::from("ingress"),
|
||||
argv: vec!["/usr/bin/true".to_string()],
|
||||
cwd: cwd_uri,
|
||||
env_policy: None,
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: Some(4173),
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request(¶ms, HashMap::new(), Some(&runtime_paths))
|
||||
.expect("prepare ingress sandbox request");
|
||||
|
||||
assert!(
|
||||
prepared
|
||||
.command
|
||||
.windows(2)
|
||||
.any(|args| args[0] == "--ingress" && args[1] == "4173")
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn sandbox_request_allows_prepared_managed_proxy_port() {
|
||||
@@ -104,6 +146,7 @@ fn sandbox_request_allows_prepared_managed_proxy_port() {
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: true,
|
||||
@@ -140,6 +183,7 @@ fn native_request_preserves_native_launch_fields() {
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: Some("custom-arg0".to_string()),
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
|
||||
@@ -33,6 +33,7 @@ fn exec_params_with_argv(process_id: &str, argv: Vec<String>) -> ExecParams {
|
||||
env: inherited_path_env(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
|
||||
@@ -590,6 +590,7 @@ mod tests {
|
||||
env,
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
ingress: None,
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
|
||||
Reference in New Issue
Block a user