Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Bolin
4cfc116d7f refactor: unify zsh fork shell tool with fd-based bridge 2026-02-22 20:01:03 -08:00
12 changed files with 174 additions and 271 deletions

17
codex-rs/Cargo.lock generated
View File

@@ -1653,6 +1653,7 @@ dependencies = [
"codex-rmcp-client",
"codex-secrets",
"codex-shell-command",
"codex-shell-exec-bridge",
"codex-skills",
"codex-state",
"codex-utils-absolute-path",
@@ -1786,6 +1787,7 @@ dependencies = [
"codex-execpolicy",
"codex-protocol",
"codex-shell-command",
"codex-shell-exec-bridge",
"codex-utils-cargo-bin",
"core_test_support",
"exec_server_test_support",
@@ -2201,6 +2203,21 @@ dependencies = [
"which",
]
[[package]]
name = "codex-shell-exec-bridge"
version = "0.0.0"
dependencies = [
"anyhow",
"libc",
"pretty_assertions",
"serde",
"serde_json",
"socket2 0.6.2",
"tempfile",
"tokio",
"tracing",
]
[[package]]
name = "codex-skills"
version = "0.0.0"

View File

@@ -21,6 +21,7 @@ members = [
"core",
"hooks",
"secrets",
"shell-exec-bridge",
"exec",
"exec-server",
"execpolicy",
@@ -113,6 +114,7 @@ codex-responses-api-proxy = { path = "responses-api-proxy" }
codex-rmcp-client = { path = "rmcp-client" }
codex-secrets = { path = "secrets" }
codex-shell-command = { path = "shell-command" }
codex-shell-exec-bridge = { path = "shell-exec-bridge" }
codex-skills = { path = "skills" }
codex-state = { path = "state" }
codex-stdio-to-uds = { path = "stdio-to-uds" }

View File

@@ -26,7 +26,7 @@ fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
// Run wrapper mode only after arg0 dispatch so `codex-linux-sandbox`
// invocations don't get misclassified as zsh exec-wrapper calls.
if codex_core::maybe_run_zsh_exec_wrapper_mode()? {
if codex_core::maybe_run_zsh_exec_wrapper_mode().await? {
return Ok(());
}
let args = AppServerArgs::parse();

View File

@@ -546,7 +546,7 @@ fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
// Run wrapper mode only after arg0 dispatch so `codex-linux-sandbox`
// invocations don't get misclassified as zsh exec-wrapper calls.
if codex_core::maybe_run_zsh_exec_wrapper_mode()? {
if codex_core::maybe_run_zsh_exec_wrapper_mode().await? {
return Ok(());
}
cli_main(codex_linux_sandbox_exe).await?;

View File

@@ -43,6 +43,7 @@ codex-network-proxy = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
codex-rmcp-client = { workspace = true }
codex-shell-exec-bridge = { workspace = true }
codex-state = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-home-dir = { workspace = true }

View File

@@ -163,19 +163,13 @@ impl SandboxManager {
SandboxType::MacosSeatbelt => {
let mut seatbelt_env = HashMap::new();
seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let zsh_exec_bridge_wrapper_socket = env
.get(crate::zsh_exec_bridge::ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR)
.map(PathBuf::from);
let zsh_exec_bridge_allowed_unix_sockets = zsh_exec_bridge_wrapper_socket
.as_ref()
.map_or_else(Vec::new, |path| vec![path.clone()]);
let mut args = create_seatbelt_command_args(
command.clone(),
policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
&zsh_exec_bridge_allowed_unix_sockets,
&[],
);
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());

View File

@@ -26,7 +26,6 @@ use crate::tools::sandboxing::ToolCtx;
use crate::tools::sandboxing::ToolError;
use crate::tools::sandboxing::ToolRuntime;
use crate::tools::sandboxing::with_cached_approval;
use crate::zsh_exec_bridge::ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR;
use codex_network_proxy::NetworkProxy;
use codex_protocol::protocol::ReviewDecision;
use futures::future::BoxFuture;
@@ -182,20 +181,10 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
};
if ctx.session.features().enabled(Feature::ShellZshFork) {
let wrapper_socket_path = ctx
.session
.services
.zsh_exec_bridge
.next_wrapper_socket_path();
let mut zsh_fork_env = req.env.clone();
zsh_fork_env.insert(
ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR.to_string(),
wrapper_socket_path.to_string_lossy().to_string(),
);
let spec = build_command_spec(
&command,
&req.cwd,
&zsh_fork_env,
&req.env,
req.timeout_ms.into(),
req.sandbox_permissions,
req.justification.clone(),

View File

@@ -1,6 +1,7 @@
use crate::exec::ExecToolCallOutput;
use crate::tools::sandboxing::ToolError;
use std::path::PathBuf;
use std::time::Instant;
use tokio::sync::Mutex;
use uuid::Uuid;
@@ -23,27 +24,16 @@ use codex_protocol::approvals::ExecPolicyAmendment;
#[cfg(unix)]
use codex_utils_pty::process_group::kill_child_process_group;
#[cfg(unix)]
use serde::Deserialize;
#[cfg(unix)]
use serde::Serialize;
#[cfg(unix)]
use std::io::Read;
#[cfg(unix)]
use std::io::Write;
#[cfg(unix)]
use std::time::Instant;
#[cfg(unix)]
use tokio::io::AsyncReadExt;
#[cfg(unix)]
use tokio::net::UnixListener;
#[cfg(unix)]
use tokio::net::UnixStream;
pub(crate) const ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR: &str =
"CODEX_ZSH_EXEC_BRIDGE_WRAPPER_SOCKET";
pub(crate) const ZSH_EXEC_WRAPPER_MODE_ENV_VAR: &str = "CODEX_ZSH_EXEC_WRAPPER_MODE";
#[cfg(unix)]
pub(crate) const EXEC_WRAPPER_ENV_VAR: &str = "EXEC_WRAPPER";
use codex_shell_exec_bridge::AsyncSocket;
use codex_shell_exec_bridge::EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::WrapperExecAction;
use codex_shell_exec_bridge::WrapperIpcRequest;
use codex_shell_exec_bridge::WrapperIpcResponse;
use codex_shell_exec_bridge::ZSH_EXEC_BRIDGE_SOCKET_ENV_VAR;
use codex_shell_exec_bridge::ZSH_EXEC_WRAPPER_MODE_ENV_VAR;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct ZshExecBridgeSessionState {
@@ -56,37 +46,6 @@ pub(crate) struct ZshExecBridge {
state: Mutex<ZshExecBridgeSessionState>,
}
#[cfg(unix)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum WrapperIpcRequest {
ExecRequest {
request_id: String,
file: String,
argv: Vec<String>,
cwd: String,
},
}
#[cfg(unix)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum WrapperIpcResponse {
ExecResponse {
request_id: String,
action: WrapperExecAction,
reason: Option<String>,
},
}
#[cfg(unix)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum WrapperExecAction {
Run,
Deny,
}
impl ZshExecBridge {
pub(crate) fn new(zsh_path: Option<PathBuf>, _codex_home: PathBuf) -> Self {
Self {
@@ -105,13 +64,6 @@ impl ZshExecBridge {
state.initialized_session_id = None;
}
pub(crate) fn next_wrapper_socket_path(&self) -> PathBuf {
let socket_id = Uuid::new_v4().as_simple().to_string();
let temp_dir = std::env::temp_dir();
let canonical_temp_dir = temp_dir.canonicalize().unwrap_or(temp_dir);
canonical_temp_dir.join(format!("czs-{}.sock", &socket_id[..12]))
}
#[cfg(not(unix))]
pub(crate) async fn execute_shell_request(
&self,
@@ -145,21 +97,13 @@ impl ZshExecBridge {
return Err(ToolError::Rejected("command args are empty".to_string()));
}
let wrapper_socket_path = req
.env
.get(ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR)
.map(PathBuf::from)
.unwrap_or_else(|| self.next_wrapper_socket_path());
let listener = {
let _ = std::fs::remove_file(&wrapper_socket_path);
UnixListener::bind(&wrapper_socket_path).map_err(|err| {
ToolError::Rejected(format!(
"bind wrapper socket at {}: {err}",
wrapper_socket_path.display()
))
})?
};
let (server_socket, client_socket) = AsyncSocket::pair().map_err(|err| {
ToolError::Rejected(format!("failed to create zsh wrapper socket pair: {err}"))
})?;
client_socket.set_cloexec(false).map_err(|err| {
ToolError::Rejected(format!("disable cloexec on wrapper socket: {err}"))
})?;
let fd = client_socket.as_raw_fd().to_string();
let wrapper_path = std::env::current_exe().map_err(|err| {
ToolError::Rejected(format!("resolve current executable path: {err}"))
@@ -180,10 +124,7 @@ impl ZshExecBridge {
cmd.kill_on_drop(true);
cmd.env_clear();
cmd.envs(&req.env);
cmd.env(
ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR,
wrapper_socket_path.to_string_lossy().to_string(),
);
cmd.env(ZSH_EXEC_BRIDGE_SOCKET_ENV_VAR, fd);
cmd.env(EXEC_WRAPPER_ENV_VAR, &wrapper_path);
cmd.env(ZSH_EXEC_WRAPPER_MODE_ENV_VAR, "1");
@@ -194,6 +135,7 @@ impl ZshExecBridge {
zsh_path.display()
))
})?;
drop(client_socket);
let (stream_tx, mut stream_rx) =
tokio::sync::mpsc::unbounded_channel::<(ExecOutputStream, Vec<u8>)>();
@@ -249,7 +191,13 @@ impl ZshExecBridge {
while child_exit.is_none() || stream_open {
tokio::select! {
result = child.wait(), if child_exit.is_none() => {
child_exit = Some(result.map_err(|err| ToolError::Rejected(format!("wait for zsh fork command exit: {err}")))?);
child_exit = Some(
result.map_err(|err| {
ToolError::Rejected(format!(
"wait for zsh fork command exit: {err}"
))
})?
);
}
stream = stream_rx.recv(), if stream_open => {
if let Some((output_stream, chunk)) = stream {
@@ -271,12 +219,25 @@ impl ZshExecBridge {
stream_open = false;
}
}
accept_result = listener.accept(), if child_exit.is_none() => {
let (stream, _) = accept_result.map_err(|err| {
ToolError::Rejected(format!("failed to accept wrapper request: {err}"))
result = server_socket.receive_with_fds::<WrapperIpcRequest>(), if child_exit.is_none() => {
let (request, fds) = result.map_err(|err| {
ToolError::Rejected(format!("failed to receive wrapper request: {err}"))
})?;
if !fds.is_empty() {
return Err(ToolError::Rejected(format!(
"unexpected fds in wrapper request: {}",
fds.len()
)));
}
if self
.handle_wrapper_request(stream, req.justification.clone(), session, turn, call_id)
.handle_wrapper_request(
request,
req.justification.clone(),
session,
turn,
call_id,
&server_socket,
)
.await?
{
user_rejected = true;
@@ -294,8 +255,6 @@ impl ZshExecBridge {
}
}
let _ = std::fs::remove_file(&wrapper_socket_path);
let status = child_exit.ok_or_else(|| {
ToolError::Rejected("zsh fork command did not return exit status".to_string())
})?;
@@ -323,21 +282,13 @@ impl ZshExecBridge {
#[cfg(unix)]
async fn handle_wrapper_request(
&self,
mut stream: UnixStream,
request: WrapperIpcRequest,
approval_reason: Option<String>,
session: &crate::codex::Session,
turn: &crate::codex::TurnContext,
call_id: &str,
socket: &AsyncSocket,
) -> Result<bool, ToolError> {
let mut request_buf = Vec::new();
stream.read_to_end(&mut request_buf).await.map_err(|err| {
ToolError::Rejected(format!("read wrapper request from socket: {err}"))
})?;
let request_line = String::from_utf8(request_buf).map_err(|err| {
ToolError::Rejected(format!("decode wrapper request as utf-8: {err}"))
})?;
let request = parse_wrapper_request_line(request_line.trim())?;
let (request_id, file, argv, cwd) = match request {
WrapperIpcRequest::ExecRequest {
request_id,
@@ -367,35 +318,37 @@ impl ZshExecBridge {
)
.await;
let (action, reason, user_rejected) = match decision {
let (action, reason) = match decision {
ReviewDecision::Approved
| ReviewDecision::ApprovedForSession
| ReviewDecision::ApprovedExecpolicyAmendment { .. } => {
(WrapperExecAction::Run, None, false)
}
| ReviewDecision::ApprovedExecpolicyAmendment { .. } => (WrapperExecAction::Run, None),
ReviewDecision::Denied => (
WrapperExecAction::Deny,
Some("command denied by host approval policy".to_string()),
true,
),
ReviewDecision::Abort => (
WrapperExecAction::Deny,
Some("command aborted by host approval policy".to_string()),
true,
),
};
write_json_line(
&mut stream,
&WrapperIpcResponse::ExecResponse {
request_id,
action,
reason,
},
)
.await?;
let response = WrapperIpcResponse::ExecResponse {
request_id,
action,
reason,
};
socket
.send(response.clone())
.await
.map_err(|err| ToolError::Rejected(format!("send wrapper response failed: {err}")))?;
Ok(user_rejected)
Ok(matches!(
response,
WrapperIpcResponse::ExecResponse {
action: WrapperExecAction::Deny,
..
}
))
}
#[cfg(unix)]
@@ -420,138 +373,79 @@ impl ZshExecBridge {
}
}
pub fn maybe_run_zsh_exec_wrapper_mode() -> anyhow::Result<bool> {
pub async fn maybe_run_zsh_exec_wrapper_mode() -> anyhow::Result<bool> {
if std::env::var_os(ZSH_EXEC_WRAPPER_MODE_ENV_VAR).is_none() {
return Ok(false);
}
run_exec_wrapper_mode()?;
run_exec_wrapper_mode().await?;
Ok(true)
}
fn run_exec_wrapper_mode() -> anyhow::Result<()> {
#[cfg(not(unix))]
{
anyhow::bail!("zsh exec wrapper mode is only supported on unix");
#[cfg(unix)]
async fn run_exec_wrapper_mode() -> anyhow::Result<()> {
let raw_fd = std::env::var(ZSH_EXEC_BRIDGE_SOCKET_ENV_VAR)?
.parse::<i32>()
.context("invalid wrapper socket fd")?;
if raw_fd < 0 {
anyhow::bail!("wrapper socket fd must be non-negative");
}
#[cfg(unix)]
{
use std::os::unix::net::UnixStream as StdUnixStream;
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
anyhow::bail!("exec wrapper mode requires target executable path");
}
let file = args[1].clone();
let argv = if args.len() > 2 {
args[2..].to_vec()
} else {
vec![file.clone()]
};
let cwd = std::env::current_dir()?.to_string_lossy().to_string();
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
anyhow::bail!("exec wrapper mode requires target executable path");
}
let file = args[1].clone();
let argv = if args.len() > 2 {
args[2..].to_vec()
let socket = {
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
unsafe { AsyncSocket::from_fd(OwnedFd::from_raw_fd(raw_fd))? }
};
let request_id = Uuid::new_v4().to_string();
let request = WrapperIpcRequest::ExecRequest {
request_id: request_id.clone(),
file: file.clone(),
argv,
cwd,
};
socket.send(request).await?;
let response = socket.receive::<WrapperIpcResponse>().await?;
let (response_request_id, action, reason) = match response {
WrapperIpcResponse::ExecResponse {
request_id,
action,
reason,
} => (request_id, action, reason),
};
if response_request_id != request_id {
anyhow::bail!(
"wrapper response request_id mismatch: expected {request_id}, got {response_request_id}"
);
}
if action == WrapperExecAction::Deny {
if let Some(reason) = reason {
tracing::warn!("execution denied: {reason}");
} else {
vec![file.clone()]
};
let cwd = std::env::current_dir()
.context("resolve wrapper cwd")?
.to_string_lossy()
.to_string();
let socket_path = std::env::var(ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR)
.context("missing wrapper socket path env var")?;
let request_id = Uuid::new_v4().to_string();
let request = WrapperIpcRequest::ExecRequest {
request_id: request_id.clone(),
file: file.clone(),
argv: argv.clone(),
cwd,
};
let mut stream = StdUnixStream::connect(&socket_path)
.with_context(|| format!("connect to wrapper socket at {socket_path}"))?;
let encoded = serde_json::to_string(&request).context("serialize wrapper request")?;
stream
.write_all(encoded.as_bytes())
.context("write wrapper request")?;
stream
.write_all(b"\n")
.context("write wrapper request newline")?;
stream
.shutdown(std::net::Shutdown::Write)
.context("shutdown wrapper write")?;
let mut response_buf = String::new();
stream
.read_to_string(&mut response_buf)
.context("read wrapper response")?;
let response: WrapperIpcResponse =
serde_json::from_str(response_buf.trim()).context("parse wrapper response")?;
let (response_request_id, action, reason) = match response {
WrapperIpcResponse::ExecResponse {
request_id,
action,
reason,
} => (request_id, action, reason),
};
if response_request_id != request_id {
anyhow::bail!(
"wrapper response request_id mismatch: expected {request_id}, got {response_request_id}"
);
tracing::warn!("execution denied");
}
if action == WrapperExecAction::Deny {
if let Some(reason) = reason {
tracing::warn!("execution denied: {reason}");
} else {
tracing::warn!("execution denied");
}
std::process::exit(1);
}
let mut command = std::process::Command::new(&file);
if argv.len() > 1 {
command.args(&argv[1..]);
}
command.env_remove(ZSH_EXEC_WRAPPER_MODE_ENV_VAR);
command.env_remove(ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR);
command.env_remove(EXEC_WRAPPER_ENV_VAR);
let status = command.status().context("spawn wrapped executable")?;
std::process::exit(status.code().unwrap_or(1));
std::process::exit(1);
}
}
#[cfg(unix)]
fn parse_wrapper_request_line(request_line: &str) -> Result<WrapperIpcRequest, ToolError> {
serde_json::from_str(request_line)
.map_err(|err| ToolError::Rejected(format!("parse wrapper request payload: {err}")))
}
#[cfg(unix)]
async fn write_json_line<W: tokio::io::AsyncWrite + Unpin, T: Serialize>(
writer: &mut W,
message: &T,
) -> Result<(), ToolError> {
let encoded = serde_json::to_string(message)
.map_err(|err| ToolError::Rejected(format!("serialize wrapper message: {err}")))?;
tokio::io::AsyncWriteExt::write_all(writer, encoded.as_bytes())
.await
.map_err(|err| ToolError::Rejected(format!("write wrapper message: {err}")))?;
tokio::io::AsyncWriteExt::write_all(writer, b"\n")
.await
.map_err(|err| ToolError::Rejected(format!("write wrapper newline: {err}")))?;
tokio::io::AsyncWriteExt::flush(writer)
.await
.map_err(|err| ToolError::Rejected(format!("flush wrapper message: {err}")))?;
Ok(())
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn parse_wrapper_request_line_rejects_malformed_json() {
let err = parse_wrapper_request_line("this-is-not-json").unwrap_err();
let ToolError::Rejected(message) = err else {
panic!("expected ToolError::Rejected");
};
assert!(message.starts_with("parse wrapper request payload:"));
let mut command = std::process::Command::new(&file);
if args.len() > 2 {
command.args(&args[2..]);
}
command.env_remove(ZSH_EXEC_WRAPPER_MODE_ENV_VAR);
command.env_remove(ZSH_EXEC_BRIDGE_SOCKET_ENV_VAR);
command.env_remove(EXEC_WRAPPER_ENV_VAR);
let status = command.status().context("spawn wrapped executable")?;
std::process::exit(status.code().unwrap_or(1));
}

View File

@@ -24,6 +24,7 @@ anyhow = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive"] }
codex-core = { workspace = true }
codex-shell-exec-bridge = { workspace = true }
codex-execpolicy = { workspace = true }
codex-protocol = { workspace = true }
codex-shell-command = { workspace = true }

View File

@@ -73,14 +73,20 @@ use tracing_subscriber::EnvFilter;
use tracing_subscriber::{self};
use crate::posix::mcp_escalation_policy::ExecPolicyOutcome;
use codex_shell_exec_bridge::ESCALATE_SOCKET_ENV_VAR;
use codex_shell_exec_bridge::EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::EscalateAction;
use codex_shell_exec_bridge::EscalateRequest;
use codex_shell_exec_bridge::EscalateResponse;
use codex_shell_exec_bridge::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::SuperExecMessage;
use codex_shell_exec_bridge::SuperExecResult;
mod escalate_client;
mod escalate_protocol;
mod escalate_server;
mod escalation_policy;
mod mcp;
mod mcp_escalation_policy;
mod socket;
mod stopwatch;
pub use mcp::ExecResult;

View File

@@ -4,17 +4,16 @@ use std::os::fd::FromRawFd as _;
use std::os::fd::OwnedFd;
use anyhow::Context as _;
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::EscalateAction;
use crate::posix::escalate_protocol::EscalateRequest;
use crate::posix::escalate_protocol::EscalateResponse;
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::SuperExecMessage;
use crate::posix::escalate_protocol::SuperExecResult;
use crate::posix::socket::AsyncDatagramSocket;
use crate::posix::socket::AsyncSocket;
use codex_shell_exec_bridge::AsyncDatagramSocket;
use codex_shell_exec_bridge::AsyncSocket;
use codex_shell_exec_bridge::ESCALATE_SOCKET_ENV_VAR;
use codex_shell_exec_bridge::EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::EscalateAction;
use codex_shell_exec_bridge::EscalateRequest;
use codex_shell_exec_bridge::EscalateResponse;
use codex_shell_exec_bridge::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::SuperExecMessage;
use codex_shell_exec_bridge::SuperExecResult;
fn get_escalate_client() -> anyhow::Result<AsyncDatagramSocket> {
// TODO: we should defensively require only calling this once, since AsyncSocket will take ownership of the fd.

View File

@@ -12,21 +12,21 @@ use codex_core::SandboxState;
use codex_core::exec::process_exec_tool_call;
use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_shell_exec_bridge::AsyncDatagramSocket;
use codex_shell_exec_bridge::AsyncSocket;
use codex_shell_exec_bridge::ESCALATE_SOCKET_ENV_VAR;
use codex_shell_exec_bridge::EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::EscalateAction;
use codex_shell_exec_bridge::EscalateRequest;
use codex_shell_exec_bridge::EscalateResponse;
use codex_shell_exec_bridge::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use codex_shell_exec_bridge::SuperExecMessage;
use codex_shell_exec_bridge::SuperExecResult;
use tokio::process::Command;
use tokio_util::sync::CancellationToken;
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::EscalateAction;
use crate::posix::escalate_protocol::EscalateRequest;
use crate::posix::escalate_protocol::EscalateResponse;
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::SuperExecMessage;
use crate::posix::escalate_protocol::SuperExecResult;
use crate::posix::escalation_policy::EscalationPolicy;
use crate::posix::mcp::ExecParams;
use crate::posix::socket::AsyncDatagramSocket;
use crate::posix::socket::AsyncSocket;
use codex_core::exec::ExecExpiration;
pub(crate) struct EscalateServer {