mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
123e78b97b
## Why Windows sandboxed commands run as a sandbox user, while workspace repositories are usually owned by the real user. The sandbox compensates by injecting a temporary Git `safe.directory` entry into the child environment. That injection was still broken for linked worktrees because the helper followed the `.git` file's `gitdir:` pointer and injected the internal `.git/worktrees/...` location. Git's dubious-ownership check expects the worktree root instead, so sandboxed Git commands still failed in worktree-based Codex checkouts. ## What changed - Treat any `.git` marker, directory or file, as the worktree root for `safe.directory` injection. - Keep the safe-directory logic in `windows-sandbox-rs/src/sandbox_utils.rs` and have the one-shot elevated path reuse it. - Add regression coverage for both normal `.git` directories and gitfile-based worktrees. ## Validation - `cargo test -p codex-windows-sandbox sandbox_utils::tests` - `cargo test -p codex-windows-sandbox` built and ran; the new `sandbox_utils` tests passed, while two pre-existing legacy sandbox tests failed locally with `Access is denied`: `session::tests::legacy_non_tty_cmd_emits_output` and `spawn_prep::tests::legacy_spawn_env_applies_offline_network_rewrite`.
117 lines
4.0 KiB
Rust
117 lines
4.0 KiB
Rust
//! Shared helper utilities for Windows sandbox setup.
|
|
//!
|
|
//! These helpers centralize small pieces of setup logic used across both legacy and
|
|
//! elevated paths, including unified_exec sessions and capture flows. They cover
|
|
//! codex home directory creation and git safe.directory injection so sandboxed
|
|
//! users can run git inside a repo owned by the primary user.
|
|
|
|
use anyhow::Result;
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
|
|
/// Walk upward from `start` to locate the git worktree root for `safe.directory`.
|
|
fn find_git_worktree_root_for_safe_directory(start: &Path) -> Option<std::path::PathBuf> {
|
|
let mut cur = dunce::canonicalize(start).ok()?;
|
|
loop {
|
|
if cur.join(".git").exists() {
|
|
return Some(cur);
|
|
}
|
|
let parent = cur.parent()?;
|
|
if parent == cur {
|
|
return None;
|
|
}
|
|
cur = parent.to_path_buf();
|
|
}
|
|
}
|
|
|
|
/// Ensure the sandbox codex home directory exists.
|
|
pub fn ensure_codex_home_exists(p: &Path) -> Result<()> {
|
|
std::fs::create_dir_all(p)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Adds a git safe.directory entry to the environment when running inside a repository.
|
|
/// git will not otherwise allow the Sandbox user to run git commands on the repo directory
|
|
/// which is owned by the primary user.
|
|
pub fn inject_git_safe_directory(env_map: &mut HashMap<String, String>, cwd: &Path) {
|
|
if let Some(git_root) = find_git_worktree_root_for_safe_directory(cwd) {
|
|
let mut cfg_count: usize = env_map
|
|
.get("GIT_CONFIG_COUNT")
|
|
.and_then(|v| v.parse::<usize>().ok())
|
|
.unwrap_or(0);
|
|
let git_path = git_root.to_string_lossy().replace("\\\\", "/");
|
|
env_map.insert(
|
|
format!("GIT_CONFIG_KEY_{cfg_count}"),
|
|
"safe.directory".to_string(),
|
|
);
|
|
env_map.insert(format!("GIT_CONFIG_VALUE_{cfg_count}"), git_path);
|
|
cfg_count += 1;
|
|
env_map.insert("GIT_CONFIG_COUNT".to_string(), cfg_count.to_string());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::inject_git_safe_directory;
|
|
use pretty_assertions::assert_eq;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use tempfile::TempDir;
|
|
|
|
fn safe_directory_value(path: &Path) -> String {
|
|
dunce::canonicalize(path)
|
|
.expect("canonicalize path")
|
|
.to_string_lossy()
|
|
.replace("\\\\", "/")
|
|
}
|
|
|
|
#[test]
|
|
fn injects_safe_directory_for_git_directory() {
|
|
let temp = TempDir::new().expect("tempdir");
|
|
let repo = temp.path().join("repo");
|
|
let nested = repo.join("nested");
|
|
fs::create_dir_all(repo.join(".git")).expect("create .git");
|
|
fs::create_dir_all(&nested).expect("create nested dir");
|
|
|
|
let mut env_map = HashMap::new();
|
|
inject_git_safe_directory(&mut env_map, &nested);
|
|
|
|
let expected = HashMap::from([
|
|
("GIT_CONFIG_COUNT".to_string(), "1".to_string()),
|
|
("GIT_CONFIG_KEY_0".to_string(), "safe.directory".to_string()),
|
|
(
|
|
"GIT_CONFIG_VALUE_0".to_string(),
|
|
safe_directory_value(&repo),
|
|
),
|
|
]);
|
|
assert_eq!(env_map, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn injects_worktree_root_for_gitfile() {
|
|
let temp = TempDir::new().expect("tempdir");
|
|
let repo = temp.path().join("repo");
|
|
let nested = repo.join("nested");
|
|
fs::create_dir_all(&nested).expect("create nested dir");
|
|
fs::write(
|
|
repo.join(".git"),
|
|
"gitdir: C:/Users/example/repo/.git/worktrees/codex3\n",
|
|
)
|
|
.expect("write .git file");
|
|
|
|
let mut env_map = HashMap::new();
|
|
inject_git_safe_directory(&mut env_map, &nested);
|
|
|
|
let expected = HashMap::from([
|
|
("GIT_CONFIG_COUNT".to_string(), "1".to_string()),
|
|
("GIT_CONFIG_KEY_0".to_string(), "safe.directory".to_string()),
|
|
(
|
|
"GIT_CONFIG_VALUE_0".to_string(),
|
|
safe_directory_value(&repo),
|
|
),
|
|
]);
|
|
assert_eq!(env_map, expected);
|
|
}
|
|
}
|