mirror of
https://github.com/openai/codex.git
synced 2026-04-29 10:53:24 +03:00
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:
@@ -2272,6 +2272,7 @@ pub enum SessionSource {
|
||||
VSCode,
|
||||
Exec,
|
||||
Mcp,
|
||||
Custom(String),
|
||||
SubAgent(SubAgentSource),
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
@@ -2302,6 +2303,7 @@ impl fmt::Display for SessionSource {
|
||||
SessionSource::VSCode => f.write_str("vscode"),
|
||||
SessionSource::Exec => f.write_str("exec"),
|
||||
SessionSource::Mcp => f.write_str("mcp"),
|
||||
SessionSource::Custom(source) => f.write_str(source),
|
||||
SessionSource::SubAgent(sub_source) => write!(f, "subagent_{sub_source}"),
|
||||
SessionSource::Unknown => f.write_str("unknown"),
|
||||
}
|
||||
@@ -2309,6 +2311,23 @@ impl fmt::Display for SessionSource {
|
||||
}
|
||||
|
||||
impl SessionSource {
|
||||
pub fn from_startup_arg(value: &str) -> Result<Self, &'static str> {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("session source must not be empty");
|
||||
}
|
||||
|
||||
let normalized = trimmed.to_ascii_lowercase();
|
||||
Ok(match normalized.as_str() {
|
||||
"cli" => SessionSource::Cli,
|
||||
"vscode" => SessionSource::VSCode,
|
||||
"exec" => SessionSource::Exec,
|
||||
"mcp" | "appserver" | "app-server" | "app_server" => SessionSource::Mcp,
|
||||
"unknown" => SessionSource::Unknown,
|
||||
_ => SessionSource::Custom(trimmed.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_nickname(&self) -> Option<String> {
|
||||
match self {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_nickname, .. }) => {
|
||||
@@ -2332,6 +2351,25 @@ impl SessionSource {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restriction_product(&self) -> Option<Product> {
|
||||
match self {
|
||||
SessionSource::Custom(source) => Product::from_session_source_name(source),
|
||||
SessionSource::Cli
|
||||
| SessionSource::VSCode
|
||||
| SessionSource::Exec
|
||||
| SessionSource::Mcp
|
||||
| SessionSource::SubAgent(_)
|
||||
| SessionSource::Unknown => Some(Product::Codex),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_product_restriction(&self, products: &[Product]) -> bool {
|
||||
products.is_empty()
|
||||
|| self
|
||||
.restriction_product()
|
||||
.is_some_and(|product| products.contains(&product))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubAgentSource {
|
||||
@@ -2923,6 +2961,18 @@ pub enum Product {
|
||||
#[serde(alias = "ATLAS")]
|
||||
Atlas,
|
||||
}
|
||||
|
||||
impl Product {
|
||||
pub fn from_session_source_name(value: &str) -> Option<Self> {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"chatgpt" => Some(Self::Chatgpt),
|
||||
"codex" => Some(Self::Codex),
|
||||
"atlas" => Some(Self::Atlas),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
@@ -3423,6 +3473,92 @@ mod tests {
|
||||
.any(|root| root.is_path_writable(path))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_from_startup_arg_maps_known_values() {
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("vscode").unwrap(),
|
||||
SessionSource::VSCode
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("app-server").unwrap(),
|
||||
SessionSource::Mcp
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_from_startup_arg_preserves_custom_values() {
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("atlas").unwrap(),
|
||||
SessionSource::Custom("atlas".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_restriction_product_defaults_non_custom_sources_to_codex() {
|
||||
assert_eq!(
|
||||
SessionSource::Cli.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::VSCode.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Exec.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Mcp.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::SubAgent(SubAgentSource::Review).restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Unknown.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_restriction_product_maps_custom_sources_to_products() {
|
||||
assert_eq!(
|
||||
SessionSource::Custom("chatgpt".to_string()).restriction_product(),
|
||||
Some(Product::Chatgpt)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("ATLAS".to_string()).restriction_product(),
|
||||
Some(Product::Atlas)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("codex".to_string()).restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("atlas-dev".to_string()).restriction_product(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_matches_product_restriction() {
|
||||
assert!(
|
||||
SessionSource::Custom("chatgpt".to_string())
|
||||
.matches_product_restriction(&[Product::Chatgpt])
|
||||
);
|
||||
assert!(
|
||||
!SessionSource::Custom("chatgpt".to_string())
|
||||
.matches_product_restriction(&[Product::Codex])
|
||||
);
|
||||
assert!(SessionSource::VSCode.matches_product_restriction(&[Product::Codex]));
|
||||
assert!(
|
||||
!SessionSource::Custom("atlas-dev".to_string())
|
||||
.matches_product_restriction(&[Product::Atlas])
|
||||
);
|
||||
assert!(SessionSource::Custom("atlas-dev".to_string()).matches_product_restriction(&[]));
|
||||
}
|
||||
|
||||
fn sandbox_policy_probe_paths(policy: &SandboxPolicy, cwd: &Path) -> Vec<PathBuf> {
|
||||
let mut paths = vec![cwd.to_path_buf()];
|
||||
paths.extend(
|
||||
|
||||
Reference in New Issue
Block a user