fix: harden plugin feature gating (#15020)

1. Use requirement-resolved config.features as the plugin gate.
2. Guard plugin/list, plugin/read, and related flows behind that gate.
3. Skip bad marketplace.json files instead of failing the whole list.
4. Simplify plugin state and caching.
This commit is contained in:
xl-openai
2026-03-18 10:11:43 -07:00
committed by GitHub
parent 606d85055f
commit 580f32ad2a
40 changed files with 926 additions and 52 deletions

View File

@@ -423,7 +423,10 @@ impl CodexMessageProcessor {
Ok(config) => self
.thread_manager
.plugins_manager()
.maybe_start_curated_repo_sync_for_config(&config),
.maybe_start_curated_repo_sync_for_config(
&config,
&self.thread_manager.session_source(),
),
Err(err) => warn!("failed to load latest config for curated plugin sync: {err:?}"),
}
}
@@ -5302,6 +5305,7 @@ impl CodexMessageProcessor {
force_reload,
per_cwd_extra_user_roots,
} = params;
let session_source = self.thread_manager.session_source();
let cwds = if cwds.is_empty() {
vec![self.config.cwd.clone()]
} else {
@@ -5346,9 +5350,12 @@ impl CodexMessageProcessor {
let extra_roots = extra_roots_by_cwd
.get(&cwd)
.map_or(&[][..], std::vec::Vec::as_slice);
let outcome = skills_manager
.skills_for_cwd_with_extra_user_roots(&cwd, force_reload, extra_roots)
.await;
let outcome = codex_core::skills::filter_skill_load_outcome_for_session_source(
skills_manager
.skills_for_cwd_with_extra_user_roots(&cwd, force_reload, extra_roots)
.await,
&session_source,
);
let errors = errors_to_info(&outcome.errors);
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
data.push(codex_app_server_protocol::SkillsListEntry {
@@ -5364,6 +5371,7 @@ impl CodexMessageProcessor {
async fn plugin_list(&self, request_id: ConnectionRequestId, params: PluginListParams) {
let plugins_manager = self.thread_manager.plugins_manager();
let session_source = self.thread_manager.session_source();
let PluginListParams {
cwds,
force_remote_sync,
@@ -5417,15 +5425,13 @@ impl CodexMessageProcessor {
Ok::<Vec<PluginMarketplaceEntry>, MarketplaceError>(
marketplaces
.into_iter()
.map(|marketplace| PluginMarketplaceEntry {
name: marketplace.name,
path: marketplace.path,
interface: marketplace.interface.map(|interface| MarketplaceInterface {
display_name: interface.display_name,
}),
plugins: marketplace
.filter_map(|marketplace| {
let plugins = marketplace
.plugins
.into_iter()
.filter(|plugin| {
session_source.matches_product_restriction(&plugin.policy.products)
})
.map(|plugin| PluginSummary {
id: plugin.id,
installed: plugin.installed,
@@ -5436,7 +5442,18 @@ impl CodexMessageProcessor {
auth_policy: plugin.policy.authentication.into(),
interface: plugin.interface.map(plugin_interface_to_info),
})
.collect(),
.collect::<Vec<_>>();
(!plugins.is_empty()).then_some(PluginMarketplaceEntry {
name: marketplace.name,
path: marketplace.path,
interface: marketplace.interface.map(|interface| {
MarketplaceInterface {
display_name: interface.display_name,
}
}),
plugins,
})
})
.collect(),
)
@@ -5511,6 +5528,11 @@ impl CodexMessageProcessor {
return;
}
};
let session_source = self.thread_manager.session_source();
let plugin_skills = codex_core::skills::filter_skills_for_session_source(
outcome.plugin.skills,
&session_source,
);
let app_summaries =
plugin_app_helpers::load_plugin_app_summaries(&config, &outcome.plugin.apps).await;
let plugin = PluginDetail {
@@ -5527,7 +5549,7 @@ impl CodexMessageProcessor {
interface: outcome.plugin.interface.map(plugin_interface_to_info),
},
description: outcome.plugin.description,
skills: plugin_skills_to_info(&outcome.plugin.skills),
skills: plugin_skills_to_info(&plugin_skills),
apps: app_summaries,
mcp_servers: outcome.plugin.mcp_server_names,
};