mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Isolate automatic plugin Git operations (#39520)
## Why Background marketplace and plugin refreshes can otherwise inherit repository-local or command-scoped Git configuration from the project that launched Codex. That configuration can redirect remotes or invoke Git helpers during an automatic operation. ## What changed - Run automatic marketplace upgrades, plugin cache refreshes, and curated marketplace lookups with repository-scoped Git environment variables removed and a temporary trusted repository under the Codex home. - Carry the automatic/manual trust mode through marketplace upgrades and plugin materialization. - Preserve the caller's Git configuration for explicit marketplace upgrades, plugin installs, and refreshes. ## Testing - Add regression coverage for repository URL rewrites, protocol helpers, command-scoped configuration, and manual Git filters. GitOrigin-RevId: 957f45fa3bd7ce7e1b83355f276cf65dc5e99c8e
This commit is contained in:
1 parent
18937b2265
commit
ffad922340
13 files changed
+690
-89
No files matched your search
@@ -1,3 +1,4 @@
|
||||
use crate::PluginGitMode;
|
||||
use crate::app_mcp_routing::apply_app_mcp_routing_policy;
|
||||
use crate::app_mcp_routing::apps_route_available;
|
||||
use crate::is_openai_curated_marketplace_name;
|
||||
@@ -56,7 +57,6 @@ use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use tempfile::TempDir;
|
||||
use tracing::instrument;
|
||||
use tracing::warn;
|
||||
@@ -496,6 +496,7 @@ pub(crate) fn refresh_non_curated_plugin_cache(
|
||||
codex_home,
|
||||
additional_roots,
|
||||
configured_plugin_keys,
|
||||
PluginGitMode::Automatic,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -503,12 +504,14 @@ pub(crate) fn refresh_non_curated_plugin_cache_detailed(
|
||||
codex_home: &Path,
|
||||
additional_roots: &[AbsolutePathBuf],
|
||||
configured_plugin_keys: &[String],
|
||||
git_mode: PluginGitMode,
|
||||
) -> Result<NonCuratedCacheRefreshOutcome, String> {
|
||||
refresh_non_curated_plugin_cache_with_mode(
|
||||
codex_home,
|
||||
additional_roots,
|
||||
configured_plugin_keys,
|
||||
NonCuratedCacheRefreshMode::IfVersionChanged,
|
||||
git_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -522,6 +525,7 @@ pub(crate) fn refresh_non_curated_plugin_cache_force_reinstall(
|
||||
codex_home,
|
||||
additional_roots,
|
||||
configured_plugin_keys,
|
||||
PluginGitMode::Automatic,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -529,12 +533,14 @@ pub(crate) fn refresh_non_curated_plugin_cache_force_reinstall_detailed(
|
||||
codex_home: &Path,
|
||||
additional_roots: &[AbsolutePathBuf],
|
||||
configured_plugin_keys: &[String],
|
||||
git_mode: PluginGitMode,
|
||||
) -> Result<NonCuratedCacheRefreshOutcome, String> {
|
||||
refresh_non_curated_plugin_cache_with_mode(
|
||||
codex_home,
|
||||
additional_roots,
|
||||
configured_plugin_keys,
|
||||
NonCuratedCacheRefreshMode::ForceReinstall,
|
||||
git_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -543,6 +549,7 @@ fn refresh_non_curated_plugin_cache_with_mode(
|
||||
additional_roots: &[AbsolutePathBuf],
|
||||
configured_plugin_keys: &[String],
|
||||
mode: NonCuratedCacheRefreshMode,
|
||||
git_mode: PluginGitMode,
|
||||
) -> Result<NonCuratedCacheRefreshOutcome, String> {
|
||||
let mut configured_non_curated_plugin_ids = configured_plugin_keys
|
||||
.iter()
|
||||
@@ -644,9 +651,10 @@ fn refresh_non_curated_plugin_cache_with_mode(
|
||||
};
|
||||
let refresh_result = (|| -> Result<bool, String> {
|
||||
let materialized =
|
||||
materialize_marketplace_plugin_source(codex_home, &source).map_err(|err| {
|
||||
format!("failed to materialize plugin source for {plugin_key}: {err}")
|
||||
})?;
|
||||
materialize_marketplace_plugin_source_with_mode(codex_home, &source, git_mode)
|
||||
.map_err(|err| {
|
||||
format!("failed to materialize plugin source for {plugin_key}: {err}")
|
||||
})?;
|
||||
let source_path = materialized.path;
|
||||
let plugin_version = match manifest_fallback_contents.as_deref() {
|
||||
Some(manifest_contents) => plugin_version_for_source_with_fallback_manifest(
|
||||
@@ -1706,6 +1714,15 @@ pub struct MaterializedMarketplacePluginSource {
|
||||
pub fn materialize_marketplace_plugin_source(
|
||||
codex_home: &Path,
|
||||
source: &MarketplacePluginSource,
|
||||
) -> Result<MaterializedMarketplacePluginSource, String> {
|
||||
materialize_marketplace_plugin_source_with_mode(codex_home, source, PluginGitMode::Manual)
|
||||
}
|
||||
|
||||
/// Applies the initiating operation's Git trust policy throughout plugin materialization.
|
||||
pub(crate) fn materialize_marketplace_plugin_source_with_mode(
|
||||
codex_home: &Path,
|
||||
source: &MarketplacePluginSource,
|
||||
mode: PluginGitMode,
|
||||
) -> Result<MaterializedMarketplacePluginSource, String> {
|
||||
match source {
|
||||
MarketplacePluginSource::Local { path } => Ok(MaterializedMarketplacePluginSource {
|
||||
@@ -1735,11 +1752,13 @@ pub fn materialize_marketplace_plugin_source(
|
||||
)
|
||||
})?;
|
||||
clone_git_plugin_source(
|
||||
codex_home,
|
||||
url,
|
||||
ref_name.as_deref(),
|
||||
sha.as_deref(),
|
||||
path.as_deref(),
|
||||
tempdir.path(),
|
||||
mode,
|
||||
)?;
|
||||
let path = if let Some(path) = path {
|
||||
AbsolutePathBuf::try_from(tempdir.path().join(path)).map_err(|err| {
|
||||
@@ -1775,12 +1794,18 @@ pub fn materialize_marketplace_plugin_source(
|
||||
}
|
||||
|
||||
fn clone_git_plugin_source(
|
||||
codex_home: &Path,
|
||||
url: &str,
|
||||
ref_name: Option<&str>,
|
||||
sha: Option<&str>,
|
||||
sparse_checkout_path: Option<&str>,
|
||||
destination: &Path,
|
||||
mode: PluginGitMode,
|
||||
) -> Result<(), String> {
|
||||
let clone_cwd = match mode {
|
||||
PluginGitMode::Automatic => Some(codex_home),
|
||||
PluginGitMode::Manual => None,
|
||||
};
|
||||
if let Some(sparse_checkout_path) = sparse_checkout_path {
|
||||
run_git(
|
||||
&[
|
||||
@@ -1791,7 +1816,8 @@ fn clone_git_plugin_source(
|
||||
url,
|
||||
destination.to_string_lossy().as_ref(),
|
||||
],
|
||||
/*cwd*/ None,
|
||||
clone_cwd,
|
||||
mode,
|
||||
)?;
|
||||
run_git(
|
||||
&[
|
||||
@@ -1802,42 +1828,56 @@ fn clone_git_plugin_source(
|
||||
sparse_checkout_path,
|
||||
],
|
||||
Some(destination),
|
||||
mode,
|
||||
)?;
|
||||
} else {
|
||||
run_git(
|
||||
&["clone", url, destination.to_string_lossy().as_ref()],
|
||||
/*cwd*/ None,
|
||||
clone_cwd,
|
||||
mode,
|
||||
)?;
|
||||
}
|
||||
if let Some(sha) = sha {
|
||||
run_git(&["checkout", sha], Some(destination))?;
|
||||
let checked_out_sha = run_git_output(&["rev-parse", "HEAD"], Some(destination))?;
|
||||
run_git(&["checkout", sha], Some(destination), mode)?;
|
||||
let checked_out_sha = run_git_output(&["rev-parse", "HEAD"], Some(destination), mode)?;
|
||||
if !checked_out_sha.eq_ignore_ascii_case(sha) {
|
||||
return Err(format!(
|
||||
"checked out Git SHA {checked_out_sha} does not match requested SHA {sha}"
|
||||
));
|
||||
}
|
||||
} else if let Some(ref_name) = ref_name {
|
||||
run_git(&["checkout", ref_name], Some(destination))?;
|
||||
run_git(&["checkout", ref_name], Some(destination), mode)?;
|
||||
} else if sparse_checkout_path.is_some() {
|
||||
run_git(&["checkout"], Some(destination))?;
|
||||
run_git(&["checkout"], Some(destination), mode)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_git(args: &[&str], cwd: Option<&Path>) -> Result<(), String> {
|
||||
run_git_output(args, cwd).map(drop)
|
||||
fn run_git(args: &[&str], cwd: Option<&Path>, mode: PluginGitMode) -> Result<(), String> {
|
||||
run_git_output(args, cwd, mode).map(drop)
|
||||
}
|
||||
|
||||
fn run_git_output(args: &[&str], cwd: Option<&Path>) -> Result<String, String> {
|
||||
let mut command = Command::new("git");
|
||||
command
|
||||
.args(["-c", codex_git_utils::SAFE_BARE_REPOSITORY_CONFIG])
|
||||
.args(args);
|
||||
fn run_git_output(
|
||||
args: &[&str],
|
||||
cwd: Option<&Path>,
|
||||
mode: PluginGitMode,
|
||||
) -> Result<String, String> {
|
||||
let mut command = mode.command(Path::new("git"));
|
||||
command.args(args);
|
||||
command.env("GIT_TERMINAL_PROMPT", "0");
|
||||
if let Some(cwd) = cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
let _trusted_repository = if let Some(cwd) = cwd
|
||||
&& args.first() == Some(&"clone")
|
||||
{
|
||||
Some(crate::configure_trusted_git_repository(&mut command, cwd)?)
|
||||
} else {
|
||||
if let Some(cwd) = cwd {
|
||||
command.current_dir(cwd);
|
||||
if matches!(mode, PluginGitMode::Manual) {
|
||||
command.env_remove("GIT_DIR");
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
|
||||
Reference in new issue
Block a user