[plugins] Enforce marketplace source policy at runtime (#29691)

## Summary

- project effective marketplace/plugin config through the enterprise
source policy so blocked installed plugins become inactive
- filter plugin list/read/discovery and CLI marketplace source/snapshot
reporting using the same policy
- enforce source admission for background marketplace cache refreshes
- continue refreshing/upgrading independent marketplaces and plugins
when one entry fails, returning per-entry errors
- include policy-projected plugin state in cache and refresh keys so
requirement changes invalidate stale results

## Stack

This is PR 2 of 2 and is based on #29690. Review the admission model and
source matcher in #29690 first; this PR contains only runtime
enforcement.

## Test plan

- `just test -p codex-core-plugins` (287 tests)
- `just test -p codex-cli
plugin_list_ignores_implicit_system_marketplace_roots_without_manifests`
- `cargo check -p codex-cli -p codex-app-server --tests`
This commit is contained in:
xl-openai
2026-06-27 15:22:05 -07:00
committed by GitHub
Unverified
parent e2398d0b16
commit 9dbdb4e2c0
12 changed files with 897 additions and 144 deletions
+11 -1
View File
@@ -10,6 +10,7 @@ use codex_core_plugins::PluginInstallOutcome;
use codex_core_plugins::PluginInstallRequest;
use codex_core_plugins::PluginsConfigInput;
use codex_core_plugins::PluginsManager;
use codex_core_plugins::allowed_configured_marketplace_names;
use codex_core_plugins::installed_marketplaces::marketplace_install_root;
use codex_core_plugins::installed_marketplaces::resolve_configured_marketplace_root;
use codex_core_plugins::marketplace::MarketplaceListError;
@@ -228,7 +229,7 @@ pub async fn run_plugin_list(
.is_none_or(|name| marketplace.name == *name)
})
.collect::<Vec<_>>();
let marketplace_sources = configured_marketplace_sources(&plugins_input);
let marketplace_sources = configured_marketplace_sources(&plugins_input, codex_home.as_path());
if args.json {
let output = JsonPluginListOutput::from_marketplaces(
@@ -480,6 +481,7 @@ pub(crate) struct JsonMarketplaceSource {
pub(crate) fn configured_marketplace_sources(
plugins_input: &PluginsConfigInput,
codex_home: &Path,
) -> HashMap<String, JsonMarketplaceSource> {
let Some(user_config) = plugins_input.config_layer_stack.effective_user_config() else {
return HashMap::new();
@@ -490,9 +492,12 @@ pub(crate) fn configured_marketplace_sources(
else {
return HashMap::new();
};
let allowed_marketplace_names =
allowed_configured_marketplace_names(&plugins_input.config_layer_stack, codex_home);
marketplaces
.iter()
.filter(|(marketplace_name, _)| allowed_marketplace_names.contains(*marketplace_name))
.filter_map(|(marketplace_name, marketplace)| {
let source_type = marketplace
.get("source_type")
@@ -746,11 +751,16 @@ pub(crate) fn configured_marketplace_snapshot_issues(
else {
return Vec::new();
};
let allowed_marketplace_names =
allowed_configured_marketplace_names(&plugins_input.config_layer_stack, codex_home);
let default_install_root = marketplace_install_root(codex_home);
let mut manifest_paths = Vec::new();
let mut issues = Vec::new();
for (configured_name, marketplace) in configured_marketplaces {
if !allowed_marketplace_names.contains(configured_name) {
continue;
}
if marketplace_name.is_some_and(|name| configured_name != name) {
continue;
}