mirror of
https://github.com/openai/codex.git
synced 2026-04-26 23:55:25 +00:00
add codex debug seatbelt --log-denials (#4098)
This adds a debugging tool for analyzing why certain commands fail to execute under the sandbox. Example output: ``` $ codex debug seatbelt --log-denials bash -lc "(echo foo > ~/foo.txt)" bash: /Users/nornagon/foo.txt: Operation not permitted === Sandbox denials === (bash) file-write-data /dev/tty (bash) file-write-data /dev/ttys001 (bash) sysctl-read kern.ngroups (bash) file-write-create /Users/nornagon/foo.txt ``` It operates by: 1. spawning `log stream` to watch system logs, and 2. tracking all descendant PIDs using kqueue + proc_listchildpids. this is a "best-effort" technique, as `log stream` may drop logs(?), and kqueue + proc_listchildpids isn't atomic and can end up missing very short-lived processes. But it works well enough in my testing to be useful :)
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod pid_tracker;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod seatbelt;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_common::CliConfigOverrides;
|
||||
@@ -15,6 +20,9 @@ use crate::SeatbeltCommand;
|
||||
use crate::WindowsCommand;
|
||||
use crate::exit_status::handle_exit_status;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use seatbelt::DenialLogger;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn run_command_under_seatbelt(
|
||||
command: SeatbeltCommand,
|
||||
@@ -22,6 +30,7 @@ pub async fn run_command_under_seatbelt(
|
||||
) -> anyhow::Result<()> {
|
||||
let SeatbeltCommand {
|
||||
full_auto,
|
||||
log_denials,
|
||||
config_overrides,
|
||||
command,
|
||||
} = command;
|
||||
@@ -31,6 +40,7 @@ pub async fn run_command_under_seatbelt(
|
||||
config_overrides,
|
||||
codex_linux_sandbox_exe,
|
||||
SandboxType::Seatbelt,
|
||||
log_denials,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -58,6 +68,7 @@ pub async fn run_command_under_landlock(
|
||||
config_overrides,
|
||||
codex_linux_sandbox_exe,
|
||||
SandboxType::Landlock,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -77,6 +88,7 @@ pub async fn run_command_under_windows(
|
||||
config_overrides,
|
||||
codex_linux_sandbox_exe,
|
||||
SandboxType::Windows,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -94,6 +106,7 @@ async fn run_command_under_sandbox(
|
||||
config_overrides: CliConfigOverrides,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
sandbox_type: SandboxType,
|
||||
log_denials: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let sandbox_mode = create_sandbox_mode(full_auto);
|
||||
let config = Config::load_with_cli_overrides(
|
||||
@@ -180,6 +193,11 @@ async fn run_command_under_sandbox(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut denial_logger = log_denials.then(DenialLogger::new).flatten();
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let _ = log_denials;
|
||||
|
||||
let mut child = match sandbox_type {
|
||||
#[cfg(target_os = "macos")]
|
||||
SandboxType::Seatbelt => {
|
||||
@@ -213,8 +231,27 @@ async fn run_command_under_sandbox(
|
||||
unreachable!("Windows sandbox should have been handled above");
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(denial_logger) = &mut denial_logger {
|
||||
denial_logger.on_child_spawn(&child);
|
||||
}
|
||||
|
||||
let status = child.wait().await?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(denial_logger) = denial_logger {
|
||||
let denials = denial_logger.finish().await;
|
||||
eprintln!("\n=== Sandbox denials ===");
|
||||
if denials.is_empty() {
|
||||
eprintln!("None found.");
|
||||
} else {
|
||||
for seatbelt::SandboxDenial { name, capability } in denials {
|
||||
eprintln!("({name}) {capability}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_exit_status(status);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user