This ensures Codex cannot drop a `.env` file with a value of `CODEX_HOME` that points to a folder that Codex can control.
116 lines
4.2 KiB
Rust
116 lines
4.2 KiB
Rust
use std::future::Future;
|
||
use std::path::Path;
|
||
use std::path::PathBuf;
|
||
|
||
use codex_core::CODEX_APPLY_PATCH_ARG1;
|
||
|
||
/// While we want to deploy the Codex CLI as a single executable for simplicity,
|
||
/// we also want to expose some of its functionality as distinct CLIs, so we use
|
||
/// the "arg0 trick" to determine which CLI to dispatch. This effectively allows
|
||
/// us to simulate deploying multiple executables as a single binary on Mac and
|
||
/// Linux (but not Windows).
|
||
///
|
||
/// When the current executable is invoked through the hard-link or alias named
|
||
/// `codex-linux-sandbox` we *directly* execute
|
||
/// [`codex_linux_sandbox::run_main`] (which never returns). Otherwise we:
|
||
///
|
||
/// 1. Use [`dotenvy::from_path`] and [`dotenvy::dotenv`] to modify the
|
||
/// environment before creating any threads.
|
||
/// 2. Construct a Tokio multi-thread runtime.
|
||
/// 3. Derive the path to the current executable (so children can re-invoke the
|
||
/// sandbox) when running on Linux.
|
||
/// 4. Execute the provided async `main_fn` inside that runtime, forwarding any
|
||
/// error. Note that `main_fn` receives `codex_linux_sandbox_exe:
|
||
/// Option<PathBuf>`, as an argument, which is generally needed as part of
|
||
/// constructing [`codex_core::config::Config`].
|
||
///
|
||
/// This function should be used to wrap any `main()` function in binary crates
|
||
/// in this workspace that depends on these helper CLIs.
|
||
pub fn arg0_dispatch_or_else<F, Fut>(main_fn: F) -> anyhow::Result<()>
|
||
where
|
||
F: FnOnce(Option<PathBuf>) -> Fut,
|
||
Fut: Future<Output = anyhow::Result<()>>,
|
||
{
|
||
// Determine if we were invoked via the special alias.
|
||
let mut args = std::env::args_os();
|
||
let argv0 = args.next().unwrap_or_default();
|
||
let exe_name = Path::new(&argv0)
|
||
.file_name()
|
||
.and_then(|s| s.to_str())
|
||
.unwrap_or("");
|
||
|
||
if exe_name == "codex-linux-sandbox" {
|
||
// Safety: [`run_main`] never returns.
|
||
codex_linux_sandbox::run_main();
|
||
}
|
||
|
||
let argv1 = args.next().unwrap_or_default();
|
||
if argv1 == CODEX_APPLY_PATCH_ARG1 {
|
||
let patch_arg = args.next().and_then(|s| s.to_str().map(|s| s.to_owned()));
|
||
let exit_code = match patch_arg {
|
||
Some(patch_arg) => {
|
||
let mut stdout = std::io::stdout();
|
||
let mut stderr = std::io::stderr();
|
||
match codex_apply_patch::apply_patch(&patch_arg, &mut stdout, &mut stderr) {
|
||
Ok(()) => 0,
|
||
Err(_) => 1,
|
||
}
|
||
}
|
||
None => {
|
||
eprintln!("Error: {CODEX_APPLY_PATCH_ARG1} requires a UTF-8 PATCH argument.");
|
||
1
|
||
}
|
||
};
|
||
std::process::exit(exit_code);
|
||
}
|
||
|
||
// This modifies the environment, which is not thread-safe, so do this
|
||
// before creating any threads/the Tokio runtime.
|
||
load_dotenv();
|
||
|
||
// Regular invocation – create a Tokio runtime and execute the provided
|
||
// async entry-point.
|
||
let runtime = tokio::runtime::Runtime::new()?;
|
||
runtime.block_on(async move {
|
||
let codex_linux_sandbox_exe: Option<PathBuf> = if cfg!(target_os = "linux") {
|
||
std::env::current_exe().ok()
|
||
} else {
|
||
None
|
||
};
|
||
|
||
main_fn(codex_linux_sandbox_exe).await
|
||
})
|
||
}
|
||
|
||
const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_";
|
||
|
||
/// Load env vars from ~/.codex/.env and `$(pwd)/.env`.
|
||
///
|
||
/// Security: Do not allow `.env` files to create or modify any variables
|
||
/// with names starting with `CODEX_`.
|
||
fn load_dotenv() {
|
||
if let Ok(codex_home) = codex_core::config::find_codex_home() {
|
||
if let Ok(iter) = dotenvy::from_path_iter(codex_home.join(".env")) {
|
||
set_filtered(iter);
|
||
}
|
||
}
|
||
|
||
if let Ok(iter) = dotenvy::dotenv_iter() {
|
||
set_filtered(iter);
|
||
}
|
||
}
|
||
|
||
/// Helper to set vars from a dotenvy iterator while filtering out `CODEX_` keys.
|
||
fn set_filtered<I>(iter: I)
|
||
where
|
||
I: IntoIterator<Item = Result<(String, String), dotenvy::Error>>,
|
||
{
|
||
for (key, value) in iter.into_iter().flatten() {
|
||
if !key.to_ascii_uppercase().starts_with(ILLEGAL_ENV_VAR_PREFIX) {
|
||
// It is safe to call set_var() because our process is
|
||
// single-threaded at this point in its execution.
|
||
unsafe { std::env::set_var(&key, &value) };
|
||
}
|
||
}
|
||
}
|