feat: add support for CODEX_SECURE_MODE=1 to restrict process observability

This commit is contained in:
Michael Bolin
2025-09-25 08:52:08 -07:00
parent 87b299aa3f
commit 948aaa703d
5 changed files with 243 additions and 1 deletions
+15
View File
@@ -28,6 +28,7 @@ codex-mcp-server = { workspace = true }
codex-protocol = { workspace = true }
codex-protocol-ts = { workspace = true }
codex-tui = { workspace = true }
ctor = { workspace = true }
owo-colors = { workspace = true }
serde_json = { workspace = true }
supports-color = { workspace = true }
@@ -41,6 +42,20 @@ tokio = { workspace = true, features = [
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
libc = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
libc = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_Foundation",
"Win32_System_Diagnostics_Debug",
"Win32_System_ErrorReporting",
"Win32_System_SystemServices",
] }
[dev-dependencies]
assert_cmd = { workspace = true }
predicates = { workspace = true }
+29
View File
@@ -21,6 +21,7 @@ use std::path::PathBuf;
use supports_color::Stream;
mod mcp_cmd;
mod pre_main_hardening;
use crate::mcp_cmd::McpCli;
use crate::proto::ProtoCli;
@@ -194,6 +195,34 @@ fn print_exit_messages(exit_info: AppExitInfo) {
}
}
pub(crate) const CODEX_SECURE_MODE_ENV_VAR: &str = "CODEX_SECURE_MODE";
/// As early as possible in the process lifecycle, apply hardening measures
/// if the CODEX_SECURE_MODE environment variable is set to "1".
#[ctor::ctor]
fn pre_main_hardening() {
let secure_mode = match std::env::var(CODEX_SECURE_MODE_ENV_VAR) {
Ok(value) => value,
Err(_) => return,
};
if secure_mode == "1" {
#[cfg(target_os = "linux")]
crate::pre_main_hardening::pre_main_hardening_linux();
#[cfg(target_os = "macos")]
crate::pre_main_hardening::pre_main_hardening_macos();
#[cfg(windows)]
crate::pre_main_hardening::pre_main_hardening_windows();
}
// Always clear this env var so child processes don't inherit it.
unsafe {
std::env::remove_var(CODEX_SECURE_MODE_ENV_VAR);
}
}
fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
cli_main(codex_linux_sandbox_exe).await?;
+162
View File
@@ -0,0 +1,162 @@
#[cfg(target_os = "linux")]
const PRCTL_FAILED_EXIT_CODE: i32 = 5;
#[cfg(target_os = "macos")]
const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6;
#[cfg(any(target_os = "linux", target_os = "macos"))]
const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7;
#[cfg(windows)]
const DEBUG_SET_PROCESS_KILL_ON_EXIT_FAILED_EXIT_CODE: i32 = 8;
#[cfg(windows)]
const WER_SET_FLAGS_FAILED_EXIT_CODE: i32 = 9;
#[cfg(windows)]
const WINDOWS_PROFILER_ENV_VARS: [&str; 11] = [
"COR_ENABLE_PROFILING",
"COR_PROFILER",
"COR_PROFILER_PATH",
"COR_PROFILER_PATH_32",
"COR_PROFILER_PATH_64",
"CORECLR_ENABLE_PROFILING",
"CORECLR_PROFILER",
"CORECLR_PROFILER_PATH",
"CORECLR_PROFILER_PATH_32",
"CORECLR_PROFILER_PATH_64",
"DOTNET_STARTUP_HOOKS",
];
#[cfg(target_os = "linux")]
pub(crate) fn pre_main_hardening_linux() {
// Disable ptrace attach / mark process non-dumpable.
unsafe {
if libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) != 0 {
eprintln!(
"ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
std::io::Error::last_os_error()
);
std::process::exit(PRCTL_FAILED_EXIT_CODE);
}
}
// For "defense in depth," set the core file size limit to 0.
unsafe {
let rlim = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if libc::setrlimit(libc::RLIMIT_CORE, &rlim) != 0 {
eprintln!(
"ERROR: setrlimit(RLIMIT_CORE) failed: {}",
std::io::Error::last_os_error()
);
std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
}
}
// Official Codex releases are MUSL-linked, which means that variables such
// as LD_PRELOAD are ignored anyway, but just to be sure, clear them here.
let ld_keys: Vec<String> = std::env::vars()
.filter_map(|(key, _)| {
if key.starts_with("LD_") {
Some(key)
} else {
None
}
})
.collect();
for key in ld_keys {
unsafe {
std::env::remove_var(key);
}
}
}
#[cfg(target_os = "macos")]
pub(crate) fn pre_main_hardening_macos() {
unsafe {
if libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) == -1 {
eprintln!(
"ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
std::io::Error::last_os_error()
);
std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
}
}
unsafe {
let rlim = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if libc::setrlimit(libc::RLIMIT_CORE, &rlim) != 0 {
eprintln!(
"ERROR: setrlimit(RLIMIT_CORE) failed: {}",
std::io::Error::last_os_error()
);
std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
}
}
// Remove all DYLD_ environment variables, which can be used to
// subvert library loading.
let dyld_keys: Vec<String> = std::env::vars()
.filter_map(|(key, _)| {
if key.starts_with("DYLD_") {
Some(key)
} else {
None
}
})
.collect();
for key in dyld_keys {
unsafe {
std::env::remove_var(key);
}
}
}
#[cfg(windows)]
pub(crate) fn pre_main_hardening_windows() {
unsafe {
use windows_sys::Win32::Foundation::TRUE;
use windows_sys::Win32::System::Diagnostics::Debug::DebugSetProcessKillOnExit;
use windows_sys::Win32::System::Diagnostics::Debug::SEM_FAILCRITICALERRORS;
use windows_sys::Win32::System::Diagnostics::Debug::SEM_NOGPFAULTERRORBOX;
use windows_sys::Win32::System::Diagnostics::Debug::SEM_NOOPENFILEERRORBOX;
use windows_sys::Win32::System::Diagnostics::Debug::SetErrorMode;
use windows_sys::Win32::System::ErrorReporting::WER_FAULT_REPORTING_FLAG_NOHEAP;
use windows_sys::Win32::System::ErrorReporting::WER_FAULT_REPORTING_FLAG_QUEUE;
use windows_sys::Win32::System::ErrorReporting::WER_FAULT_REPORTING_NO_UI;
use windows_sys::Win32::System::ErrorReporting::WerSetFlags;
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
if DebugSetProcessKillOnExit(TRUE) == 0 {
eprintln!(
"ERROR: DebugSetProcessKillOnExit(TRUE) failed: {}",
std::io::Error::last_os_error()
);
std::process::exit(DEBUG_SET_PROCESS_KILL_ON_EXIT_FAILED_EXIT_CODE);
}
let flags = WER_FAULT_REPORTING_NO_UI
| WER_FAULT_REPORTING_FLAG_NOHEAP
| WER_FAULT_REPORTING_FLAG_QUEUE;
let hr = WerSetFlags(flags);
if hr < 0 {
eprintln!("ERROR: WerSetFlags() failed with HRESULT {hr:#010x}");
std::process::exit(WER_SET_FLAGS_FAILED_EXIT_CODE);
}
}
for var in WINDOWS_PROFILER_ENV_VARS {
unsafe {
std::env::remove_var(var);
}
}
}