Files
codex/codex-rs/core-plugins/src/git_policy.rs
T
Eric Traut fbc169827e Suppress Windows console windows for background subprocesses (#49164)
## Why

Background helpers launched from a detached Windows process can allocate console windows. Job Object launches and containment fallbacks need to preserve console suppression as well.

## What changed

- Add `codex_utils_process::background_command` to set `CREATE_NO_WINDOW` on Windows, and use it across background subprocess launches, including Git, plugins, authentication, diagnostics, and sandbox helpers.
- Centralize console suppression in Job Object launches. Keep `CREATE_NO_WINDOW` when retrying without containment, and clear `CREATE_SUSPENDED` so the fallback child can run.
- Preserve console inheritance for interactive shell launches and interactive AWS authentication refreshes.

## Testing

Add Windows regression tests covering console-free launches from detached parents through standard and Tokio commands, stdout/stderr capture, successful containment, Job Object creation and assignment failures, and sandbox setup refresh helpers.

GitOrigin-RevId: b2c238fa7d4f9fbd868e5adf7d4be9958ff01e6c
2026-09-29 04:38:37 +00:00

86 lines
3.4 KiB
Rust

use codex_utils_absolute_path::AbsolutePathBuf;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
// These variables can redirect Git to an untrusted repository or inject command-scoped settings.
pub(crate) const REPOSITORY_LOCAL_GIT_ENVIRONMENT_VARIABLES: &[&str] = &[
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_CEILING_DIRECTORIES",
"GIT_COMMON_DIR",
"GIT_CONFIG",
"GIT_CONFIG_COUNT",
"GIT_CONFIG_PARAMETERS",
"GIT_DIR",
"GIT_DISCOVERY_ACROSS_FILESYSTEM",
"GIT_GRAFT_FILE",
"GIT_IMPLICIT_WORK_TREE",
"GIT_INDEX_FILE",
"GIT_NAMESPACE",
"GIT_OBJECT_DIRECTORY",
"GIT_PREFIX",
"GIT_REPLACE_REF_BASE",
"GIT_SHALLOW_FILE",
"GIT_WORK_TREE",
];
/// Distinguishes isolated background Git operations from explicitly requested user operations.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum PluginGitMode {
/// Ignores repository-scoped Git configuration inherited from the launch project.
Automatic,
/// Preserves the configuration explicitly available to user-requested Git commands.
Manual,
}
impl PluginGitMode {
/// Creates a Git command with the repository-environment policy for this operation.
/// Automatic remote lookups and initial clones must also select a trusted repository.
pub(crate) fn command(self, git_binary: &Path) -> Command {
let mut command = codex_utils_process::background_command(git_binary);
command.args(["-c", codex_git_utils::SAFE_BARE_REPOSITORY_CONFIG]);
if matches!(self, Self::Automatic) {
command.env("GIT_OPTIONAL_LOCKS", "0");
for name in REPOSITORY_LOCAL_GIT_ENVIRONMENT_VARIABLES {
command.env_remove(name);
}
}
command
}
}
/// Isolates an automatic Git command from repository discovery in the launch project.
///
/// The command must already have repository-scoped environment variables removed. Keep the
/// returned guard alive until the Git process exits because it owns the selected repository.
pub(crate) fn configure_trusted_git_repository(
command: &mut Command,
codex_home: &Path,
) -> Result<TempDir, String> {
let canonical_home = AbsolutePathBuf::from_absolute_path(codex_home)
.and_then(|path| path.canonicalize())
.map_err(|err| format!("failed to resolve trusted Codex home: {err}"))?;
let staging_root = canonical_home.join(".tmp");
std::fs::create_dir_all(staging_root.as_path())
.map_err(|err| format!("failed to create trusted Git repository root: {err}"))?;
let staging_root = staging_root
.canonicalize()
.map_err(|err| format!("failed to resolve trusted Git repository root: {err}"))?;
if !staging_root.as_path().starts_with(canonical_home.as_path()) {
return Err("trusted Git repository root escapes Codex home".to_string());
}
let repository = tempfile::Builder::new()
.prefix("git-")
.tempdir_in(staging_root.as_path())
.map_err(|err| format!("failed to create trusted Git repository: {err}"))?;
for directory in ["objects", "refs"] {
std::fs::create_dir(repository.path().join(directory))
.map_err(|err| format!("failed to initialize trusted Git repository: {err}"))?;
}
std::fs::write(repository.path().join("HEAD"), "ref: refs/heads/main\n")
.map_err(|err| format!("failed to initialize trusted Git repository HEAD: {err}"))?;
command.env("GIT_DIR", repository.path());
Ok(repository)
}