fix: add test that verifies that codex-exec-mcp-server starts up

This commit is contained in:
Michael Bolin
2025-12-05 12:06:21 -08:00
parent e91bb6b947
commit eca4502f60
17 changed files with 558 additions and 36 deletions
+3
View File
@@ -6,3 +6,6 @@ pub use posix::main_execve_wrapper;
#[cfg(unix)]
pub use posix::main_mcp_server;
#[cfg(unix)]
pub use posix::ExecResult;
+2
View File
@@ -82,6 +82,8 @@ mod mcp_escalation_policy;
mod socket;
mod stopwatch;
pub use mcp::ExecResult;
/// Default value of --execve option relative to the current executable.
/// Note this must match the name of the binary as specified in Cargo.toml.
const CODEX_EXECVE_WRAPPER_EXE_NAME: &str = "codex-execve-wrapper";
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::io;
use std::os::fd::AsRawFd;
use std::os::fd::FromRawFd as _;
@@ -34,21 +35,30 @@ pub(crate) async fn run(file: String, argv: Vec<String>) -> anyhow::Result<i32>
.send_with_fds(&HANDSHAKE_MESSAGE, &[server.into_inner().into()])
.await
.context("failed to send handshake datagram")?;
let env = std::env::vars()
.filter(|(k, _)| {
!matches!(
k.as_str(),
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR
)
})
.collect();
let mut env = HashMap::new();
const MAX_ENV_ENTRY_LEN: i64 = 8_192;
for (key, value) in std::env::vars() {
if matches!(
key.as_str(),
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR
) {
continue;
}
let entry_len = (key.len() + value.len()) as i64;
if entry_len > MAX_ENV_ENTRY_LEN {
tracing::debug!(key, entry_len, "skipping oversized environment variable");
continue;
}
env.insert(key, value);
}
let request = EscalateRequest {
file: file.clone().into(),
argv: argv.clone(),
workdir: std::env::current_dir()?,
env,
};
client
.send(EscalateRequest {
file: file.clone().into(),
argv: argv.clone(),
workdir: std::env::current_dir()?,
env,
})
.send(request)
.await
.context("failed to send EscalateRequest")?;
let message = client.receive::<EscalateResponse>().await?;
@@ -138,19 +138,23 @@ async fn handle_escalate_session_with_policy(
socket: AsyncSocket,
policy: Arc<dyn EscalationPolicy>,
) -> anyhow::Result<()> {
tracing::error!("in handle_escalate_session_with_policy");
let EscalateRequest {
file,
argv,
workdir,
env,
} = socket.receive::<EscalateRequest>().await?;
} = socket
.receive::<EscalateRequest>()
.await
.context("failed to receive EscalateRequest")?;
let file = PathBuf::from(&file).absolutize()?.into_owned();
let workdir = PathBuf::from(&workdir).absolutize()?.into_owned();
let action = policy
.determine_action(file.as_path(), &argv, &workdir)
.await?;
tracing::debug!("decided {action:?} for {file:?} {argv:?} {workdir:?}");
tracing::error!("decided {action:?} for {file:?} {argv:?} {workdir:?}");
match action {
EscalateAction::Run => {
@@ -165,7 +169,8 @@ async fn handle_escalate_session_with_policy(
.send(EscalateResponse {
action: EscalateAction::Escalate,
})
.await?;
.await
.context("failed to send EscalateResponse")?;
let (msg, fds) = socket
.receive_with_fds::<SuperExecMessage>()
.await
+1 -1
View File
@@ -54,7 +54,7 @@ pub struct ExecParams {
pub login: Option<bool>,
}
#[derive(Debug, serde::Serialize, schemars::JsonSchema)]
#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ExecResult {
pub exit_code: i32,
pub output: String,
+21 -15
View File
@@ -182,21 +182,26 @@ fn send_message_bytes(socket: &Socket, data: &[u8], fds: &[OwnedFd]) -> std::io:
frame.extend_from_slice(&encode_length(data.len())?);
frame.extend_from_slice(data);
let mut control = vec![0u8; control_space_for_fds(fds.len())];
unsafe {
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
(*cmsg).cmsg_len = libc::CMSG_LEN(size_of::<RawFd>() as c_uint * fds.len() as c_uint) as _;
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
let data_ptr = libc::CMSG_DATA(cmsg).cast::<RawFd>();
for (i, fd) in fds.iter().enumerate() {
data_ptr.add(i).write(fd.as_raw_fd());
}
}
let mut control;
let payload = [IoSlice::new(&frame)];
let msg = MsgHdr::new().with_buffers(&payload).with_control(&control);
let mut sent = socket.sendmsg(&msg, 0)?;
let mut sent = if fds.is_empty() {
socket.send(&frame)?
} else {
control = vec![0u8; control_space_for_fds(fds.len())];
unsafe {
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
(*cmsg).cmsg_len =
libc::CMSG_LEN(size_of::<RawFd>() as c_uint * fds.len() as c_uint) as _;
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
let data_ptr = libc::CMSG_DATA(cmsg).cast::<RawFd>();
for (i, fd) in fds.iter().enumerate() {
data_ptr.add(i).write(fd.as_raw_fd());
}
}
let msg = MsgHdr::new().with_buffers(&payload).with_control(&control);
socket.sendmsg(&msg, 0)?
};
while sent < frame.len() {
let bytes = socket.send(&frame[sent..])?;
if bytes == 0 {
@@ -236,8 +241,9 @@ fn send_datagram_bytes(socket: &Socket, data: &[u8], fds: &[OwnedFd]) -> std::io
format!("too many fds: {}", fds.len()),
));
}
let mut control = vec![0u8; control_space_for_fds(fds.len())];
let mut control = Vec::new();
if !fds.is_empty() {
control = vec![0u8; control_space_for_fds(fds.len())];
unsafe {
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
(*cmsg).cmsg_len =