mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
## Why Automatic startup work and `codex doctor` can run before a workspace is trusted. A repository-controlled `PATH` must not be able to make those flows execute workspace-provided helpers. ## What changed - Resolve helpers used by automatic startup from trusted system installation directories, and give plugin-sync Git subprocesses a sanitized environment. - Make terminal detection environment-only and have doctor inspect executable locations without running them. Fetch update metadata with the HTTP client instead of `curl`. - Fall back conservatively when trusted terminal helpers are unavailable, including for tmux keyboard enhancement flags. ## Testing Add black-box coverage with hostile workspace `PATH` entries for startup, interactive tmux startup, support log collection, doctor, and curated plugin sync. Add unit coverage for trusted executable resolution and bounded update HTTP responses. GitOrigin-RevId: 3b8995eb422b60ed53b0386951de59e8f9bfc542
157 lines
5.1 KiB
Rust
157 lines
5.1 KiB
Rust
//! Installed helper discovery for automatic work before workspace trust.
|
|
//!
|
|
//! Only OS and conventional package-manager locations are trusted, never ambient
|
|
//! PATH or the working directory. Custom installations fall back to no helper.
|
|
//! This assumes the installed software directories themselves are trusted; it
|
|
//! does not defend against an attacker who can modify system installations.
|
|
|
|
use std::ffi::OsString;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
/// Finds an installed helper without consulting PATH, PATHEXT, or the working directory.
|
|
pub fn system_executable(name: &str) -> Option<PathBuf> {
|
|
executable_in_directories(name, &system_directories())
|
|
}
|
|
|
|
/// Child-process PATH for automatic helpers, not for user-requested tools.
|
|
pub fn system_path() -> std::io::Result<OsString> {
|
|
let directories = system_directories();
|
|
if directories.is_empty() {
|
|
return Err(std::io::Error::other("no trusted executable directories"));
|
|
}
|
|
std::env::join_paths(directories).map_err(std::io::Error::other)
|
|
}
|
|
|
|
fn executable_in_directories(name: &str, directories: &[PathBuf]) -> Option<PathBuf> {
|
|
// Do not allow callers to accidentally escape the installation directories.
|
|
if Path::new(name).file_name() != Some(std::ffi::OsStr::new(name)) {
|
|
return None;
|
|
}
|
|
let name = format!("{name}{}", std::env::consts::EXE_SUFFIX);
|
|
directories.iter().find_map(|directory| {
|
|
let executable = dunce::canonicalize(directory.join(&name)).ok()?;
|
|
// A symlink into a workspace is not an installed executable. Package
|
|
// manager links may target sibling libexec/Cellar directories.
|
|
let installed = directories.iter().any(|root| executable.starts_with(root))
|
|
|| installation_roots()
|
|
.iter()
|
|
.any(|root| executable.starts_with(root));
|
|
if !installed || !executable.is_file() {
|
|
return None;
|
|
}
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
if executable.metadata().ok()?.permissions().mode() & 0o111 == 0 {
|
|
return None;
|
|
}
|
|
}
|
|
Some(executable)
|
|
})
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn installation_roots() -> Vec<PathBuf> {
|
|
[
|
|
"/usr/bin",
|
|
"/bin",
|
|
"/usr/sbin",
|
|
"/sbin",
|
|
"/usr/local",
|
|
"/opt/homebrew",
|
|
"/opt/local",
|
|
"/Library/Developer/CommandLineTools",
|
|
"/Applications/Xcode.app/Contents/Developer",
|
|
"/nix/store",
|
|
"/mnt/c/Windows/System32",
|
|
]
|
|
.into_iter()
|
|
.map(PathBuf::from)
|
|
.collect()
|
|
}
|
|
|
|
fn system_directories() -> Vec<PathBuf> {
|
|
#[cfg(unix)]
|
|
let directories = [
|
|
"/opt/homebrew/bin",
|
|
"/usr/local/bin",
|
|
"/opt/local/bin",
|
|
"/Library/Developer/CommandLineTools/usr/bin",
|
|
"/Applications/Xcode.app/Contents/Developer/usr/bin",
|
|
"/usr/bin",
|
|
"/bin",
|
|
"/usr/sbin",
|
|
"/sbin",
|
|
]
|
|
.into_iter()
|
|
.map(PathBuf::from);
|
|
#[cfg(target_os = "linux")]
|
|
let directories =
|
|
directories.chain(crate::is_wsl().then(|| PathBuf::from("/mnt/c/Windows/System32")));
|
|
#[cfg(windows)]
|
|
let directories = installation_roots().into_iter().flat_map(|root| {
|
|
vec![
|
|
root.join("Git/cmd"),
|
|
root.join("Git/mingw64/bin"),
|
|
root.join("Git/usr/bin"),
|
|
root,
|
|
]
|
|
});
|
|
let roots = installation_roots();
|
|
directories
|
|
.filter_map(|directory| dunce::canonicalize(directory).ok())
|
|
.filter(|directory| roots.iter().any(|root| directory.starts_with(root)))
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn installation_roots() -> Vec<PathBuf> {
|
|
use std::os::windows::ffi::OsStringExt;
|
|
use windows_sys::Win32::System::Com::CoTaskMemFree;
|
|
use windows_sys::Win32::UI::Shell::FOLDERID_ProgramFiles;
|
|
use windows_sys::Win32::UI::Shell::FOLDERID_ProgramFilesX86;
|
|
use windows_sys::Win32::UI::Shell::FOLDERID_System;
|
|
use windows_sys::Win32::UI::Shell::KF_FLAG_DEFAULT_PATH;
|
|
use windows_sys::Win32::UI::Shell::SHGetKnownFolderPath;
|
|
|
|
[
|
|
FOLDERID_ProgramFiles,
|
|
FOLDERID_ProgramFilesX86,
|
|
FOLDERID_System,
|
|
]
|
|
.iter()
|
|
.filter_map(|id| {
|
|
let mut path = std::ptr::null_mut();
|
|
// SAFETY: the API writes a CoTaskMem-allocated, NUL-terminated UTF-16 path.
|
|
let result = unsafe {
|
|
SHGetKnownFolderPath(
|
|
id,
|
|
KF_FLAG_DEFAULT_PATH as u32,
|
|
/*htoken*/ 0,
|
|
&mut path,
|
|
)
|
|
};
|
|
if result != 0 || path.is_null() {
|
|
return None;
|
|
}
|
|
// SAFETY: success guarantees the string; free it after copying.
|
|
let directory = unsafe {
|
|
let mut len = 0;
|
|
while *path.add(len) != 0 {
|
|
len += 1;
|
|
}
|
|
let directory =
|
|
PathBuf::from(OsString::from_wide(std::slice::from_raw_parts(path, len)));
|
|
CoTaskMemFree(path.cast());
|
|
directory
|
|
};
|
|
dunce::canonicalize(directory).ok()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "system_commands_tests.rs"]
|
|
mod tests;
|