mirror of
https://github.com/openai/codex.git
synced 2026-03-14 18:53:47 +00:00
Compare commits
1 Commits
pr14624
...
dev/cc/mul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d7ad8d90 |
@@ -41,6 +41,7 @@ use crate::realtime_conversation::handle_close as handle_realtime_conversation_c
|
||||
use crate::realtime_conversation::handle_start as handle_realtime_conversation_start;
|
||||
use crate::realtime_conversation::handle_text as handle_realtime_conversation_text;
|
||||
use crate::rollout::session_index;
|
||||
use crate::skill_network_proxy_cache::SkillNetworkProxyCache;
|
||||
use crate::stream_events_utils::HandleOutputCtx;
|
||||
use crate::stream_events_utils::handle_non_tool_response_item;
|
||||
use crate::stream_events_utils::handle_output_item_done;
|
||||
@@ -360,6 +361,7 @@ pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) plugins_manager: Arc<PluginsManager>,
|
||||
pub(crate) mcp_manager: Arc<McpManager>,
|
||||
pub(crate) file_watcher: Arc<FileWatcher>,
|
||||
pub(crate) skill_network_proxy_cache: Arc<SkillNetworkProxyCache>,
|
||||
pub(crate) conversation_history: InitialHistory,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) agent_control: AgentControl,
|
||||
@@ -410,6 +412,7 @@ impl Codex {
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
skill_network_proxy_cache,
|
||||
conversation_history,
|
||||
session_source,
|
||||
agent_control,
|
||||
@@ -599,6 +602,7 @@ impl Codex {
|
||||
plugins_manager,
|
||||
mcp_manager.clone(),
|
||||
file_watcher,
|
||||
skill_network_proxy_cache,
|
||||
agent_control,
|
||||
)
|
||||
.instrument(session_init_span)
|
||||
@@ -1182,6 +1186,50 @@ impl Session {
|
||||
Ok((network_proxy, session_network_proxy))
|
||||
}
|
||||
|
||||
fn shared_skill_network_proxy_spec(
|
||||
&self,
|
||||
skill: &SkillMetadata,
|
||||
) -> Option<crate::config::NetworkProxySpec> {
|
||||
let managed_network_override = skill.managed_network_override.as_ref()?;
|
||||
let base_spec = self.services.network_proxy_spec.as_ref()?;
|
||||
Some(base_spec.with_skill_managed_network_override(managed_network_override))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_or_start_skill_network_proxy(
|
||||
self: &Arc<Self>,
|
||||
skill: &SkillMetadata,
|
||||
) -> anyhow::Result<Option<NetworkProxy>> {
|
||||
let Some(spec) = self.shared_skill_network_proxy_spec(skill) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let key = spec.shared_skill_proxy_key();
|
||||
let sandbox_policy = {
|
||||
let state = self.state.lock().await;
|
||||
state.session_configuration.sandbox_policy.get().clone()
|
||||
};
|
||||
let proxy = self
|
||||
.services
|
||||
.skill_network_proxy_cache
|
||||
.get_or_start(key, || async move {
|
||||
// Shared skill proxies are cached by spec and reused across commands, so this
|
||||
// startup path must not capture turn-specific approval callbacks or per-command
|
||||
// audit context. The proxy should enforce the precomputed skill override in
|
||||
// `spec` only, which is why the optional hooks stay `None`, approval flow is
|
||||
// disabled, and audit metadata falls back to the empty default.
|
||||
spec.start_proxy(
|
||||
&sandbox_policy,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
NetworkProxyAuditMetadata::default(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| anyhow::anyhow!("failed to start shared skill network proxy: {err}"))
|
||||
})
|
||||
.await?;
|
||||
Ok(Some(proxy.proxy()))
|
||||
}
|
||||
|
||||
/// Don't expand the number of mutated arguments on config. We are in the process of getting rid of it.
|
||||
pub(crate) fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config {
|
||||
// todo(aibrahim): store this state somewhere else so we don't need to mut config
|
||||
@@ -1355,6 +1403,7 @@ impl Session {
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
mcp_manager: Arc<McpManager>,
|
||||
file_watcher: Arc<FileWatcher>,
|
||||
skill_network_proxy_cache: Arc<SkillNetworkProxyCache>,
|
||||
agent_control: AgentControl,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
debug!(
|
||||
@@ -1725,7 +1774,9 @@ impl Session {
|
||||
mcp_manager: Arc::clone(&mcp_manager),
|
||||
file_watcher,
|
||||
agent_control,
|
||||
network_proxy_spec: config.permissions.network.clone().map(Arc::new),
|
||||
network_proxy,
|
||||
skill_network_proxy_cache,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: state_db_ctx.clone(),
|
||||
model_client: ModelClient::new(
|
||||
|
||||
@@ -67,6 +67,7 @@ pub(crate) async fn run_codex_thread_interactive(
|
||||
plugins_manager: Arc::clone(&parent_session.services.plugins_manager),
|
||||
mcp_manager: Arc::clone(&parent_session.services.mcp_manager),
|
||||
file_watcher: Arc::clone(&parent_session.services.file_watcher),
|
||||
skill_network_proxy_cache: Arc::clone(&parent_session.services.skill_network_proxy_cache),
|
||||
conversation_history: initial_history.unwrap_or(InitialHistory::New),
|
||||
session_source: SessionSource::SubAgent(subagent_source),
|
||||
agent_control: parent_session.services.agent_control.clone(),
|
||||
|
||||
@@ -2025,6 +2025,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
Arc::new(FileWatcher::noop()),
|
||||
Arc::new(crate::skill_network_proxy_cache::SkillNetworkProxyCache::new()),
|
||||
AgentControl::default(),
|
||||
)
|
||||
.await;
|
||||
@@ -2152,7 +2153,11 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
agent_control,
|
||||
network_proxy_spec: None,
|
||||
network_proxy: None,
|
||||
skill_network_proxy_cache: Arc::new(
|
||||
crate::skill_network_proxy_cache::SkillNetworkProxyCache::new(),
|
||||
),
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: None,
|
||||
model_client: ModelClient::new(
|
||||
@@ -2794,7 +2799,11 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
agent_control,
|
||||
network_proxy_spec: None,
|
||||
network_proxy: None,
|
||||
skill_network_proxy_cache: Arc::new(
|
||||
crate::skill_network_proxy_cache::SkillNetworkProxyCache::new(),
|
||||
),
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: None,
|
||||
model_client: ModelClient::new(
|
||||
|
||||
@@ -376,6 +376,9 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
skill_network_proxy_cache: Arc::new(
|
||||
crate::skill_network_proxy_cache::SkillNetworkProxyCache::new(),
|
||||
),
|
||||
conversation_history: InitialHistory::New,
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
GUARDIAN_SUBAGENT_NAME.to_string(),
|
||||
|
||||
@@ -114,6 +114,7 @@ pub use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
|
||||
pub use managed_features::ManagedFeatures;
|
||||
pub use network_proxy_spec::NetworkProxySpec;
|
||||
pub(crate) use network_proxy_spec::SkillNetworkProxyKey;
|
||||
pub use network_proxy_spec::StartedNetworkProxy;
|
||||
pub use permissions::FilesystemPermissionToml;
|
||||
pub use permissions::FilesystemPermissionsToml;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::config_loader::NetworkConstraints;
|
||||
use crate::skills::model::SkillManagedNetworkOverride;
|
||||
use async_trait::async_trait;
|
||||
use codex_network_proxy::BlockedRequestObserver;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
@@ -17,6 +18,9 @@ use codex_network_proxy::validate_policy_against_constraints;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct SkillNetworkProxyKey(String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NetworkProxySpec {
|
||||
config: NetworkProxyConfig,
|
||||
@@ -81,6 +85,33 @@ impl NetworkProxySpec {
|
||||
self.config.network.enable_socks5
|
||||
}
|
||||
|
||||
pub(crate) fn with_skill_managed_network_override(
|
||||
&self,
|
||||
managed_network_override: &SkillManagedNetworkOverride,
|
||||
) -> Self {
|
||||
let mut spec = self.clone();
|
||||
if let Some(allowed_domains) = managed_network_override.allowed_domains.as_ref() {
|
||||
spec.config.network.allowed_domains = allowed_domains.clone();
|
||||
spec.constraints.allowed_domains = Some(allowed_domains.clone());
|
||||
}
|
||||
if let Some(denied_domains) = managed_network_override.denied_domains.as_ref() {
|
||||
spec.config.network.denied_domains = denied_domains.clone();
|
||||
spec.constraints.denied_domains = Some(denied_domains.clone());
|
||||
}
|
||||
spec
|
||||
}
|
||||
|
||||
pub(crate) fn shared_skill_proxy_key(&self) -> SkillNetworkProxyKey {
|
||||
let mut normalized = self.clone();
|
||||
sort_string_list(&mut normalized.config.network.allowed_domains);
|
||||
sort_string_list(&mut normalized.config.network.denied_domains);
|
||||
sort_string_list(&mut normalized.config.network.allow_unix_sockets);
|
||||
sort_option_string_list(&mut normalized.constraints.allowed_domains);
|
||||
sort_option_string_list(&mut normalized.constraints.denied_domains);
|
||||
sort_option_string_list(&mut normalized.constraints.allow_unix_sockets);
|
||||
SkillNetworkProxyKey(format!("{normalized:?}"))
|
||||
}
|
||||
|
||||
pub(crate) fn from_config_and_constraints(
|
||||
config: NetworkProxyConfig,
|
||||
requirements: Option<NetworkConstraints>,
|
||||
@@ -279,6 +310,16 @@ impl NetworkProxySpec {
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_string_list(values: &mut [String]) {
|
||||
values.sort_unstable();
|
||||
}
|
||||
|
||||
fn sort_option_string_list(values: &mut Option<Vec<String>>) {
|
||||
if let Some(values) = values.as_mut() {
|
||||
sort_string_list(values);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "network_proxy_spec_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::skills::model::SkillManagedNetworkOverride;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -200,3 +201,99 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() {
|
||||
);
|
||||
assert_eq!(spec.constraints.denylist_expansion_enabled, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_managed_network_override_replaces_only_requested_domain_lists() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.allowed_domains = vec!["base-allow.example.com".to_string()];
|
||||
config.network.denied_domains = vec!["base-deny.example.com".to_string()];
|
||||
let spec = NetworkProxySpec {
|
||||
config,
|
||||
constraints: NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec!["base-allow.example.com".to_string()]),
|
||||
denied_domains: Some(vec!["base-deny.example.com".to_string()]),
|
||||
allowlist_expansion_enabled: Some(false),
|
||||
denylist_expansion_enabled: Some(false),
|
||||
..NetworkProxyConstraints::default()
|
||||
},
|
||||
hard_deny_allowlist_misses: true,
|
||||
};
|
||||
|
||||
let updated = spec.with_skill_managed_network_override(&SkillManagedNetworkOverride {
|
||||
allowed_domains: Some(vec!["skill-allow.example.com".to_string()]),
|
||||
denied_domains: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
updated.config.network.allowed_domains,
|
||||
vec!["skill-allow.example.com".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
updated.config.network.denied_domains,
|
||||
vec!["base-deny.example.com".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
updated.constraints.allowed_domains,
|
||||
Some(vec!["skill-allow.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
updated.constraints.denied_domains,
|
||||
Some(vec!["base-deny.example.com".to_string()])
|
||||
);
|
||||
assert_eq!(updated.constraints.allowlist_expansion_enabled, Some(false));
|
||||
assert_eq!(updated.constraints.denylist_expansion_enabled, Some(false));
|
||||
assert!(updated.hard_deny_allowlist_misses);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_skill_proxy_key_normalizes_domain_order() {
|
||||
let mut base_config = NetworkProxyConfig::default();
|
||||
base_config.network.allowed_domains =
|
||||
vec!["b.example.com".to_string(), "a.example.com".to_string()];
|
||||
base_config.network.denied_domains = vec![
|
||||
"deny-b.example.com".to_string(),
|
||||
"deny-a.example.com".to_string(),
|
||||
];
|
||||
let base = NetworkProxySpec {
|
||||
config: base_config,
|
||||
constraints: NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec![
|
||||
"managed-b.example.com".to_string(),
|
||||
"managed-a.example.com".to_string(),
|
||||
]),
|
||||
denied_domains: Some(vec![
|
||||
"managed-deny-b.example.com".to_string(),
|
||||
"managed-deny-a.example.com".to_string(),
|
||||
]),
|
||||
..NetworkProxyConstraints::default()
|
||||
},
|
||||
hard_deny_allowlist_misses: false,
|
||||
};
|
||||
let mut reordered_config = NetworkProxyConfig::default();
|
||||
reordered_config.network.allowed_domains =
|
||||
vec!["a.example.com".to_string(), "b.example.com".to_string()];
|
||||
reordered_config.network.denied_domains = vec![
|
||||
"deny-a.example.com".to_string(),
|
||||
"deny-b.example.com".to_string(),
|
||||
];
|
||||
let reordered = NetworkProxySpec {
|
||||
config: reordered_config,
|
||||
constraints: NetworkProxyConstraints {
|
||||
allowed_domains: Some(vec![
|
||||
"managed-a.example.com".to_string(),
|
||||
"managed-b.example.com".to_string(),
|
||||
]),
|
||||
denied_domains: Some(vec![
|
||||
"managed-deny-a.example.com".to_string(),
|
||||
"managed-deny-b.example.com".to_string(),
|
||||
]),
|
||||
..NetworkProxyConstraints::default()
|
||||
},
|
||||
hard_deny_allowlist_misses: false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
base.shared_skill_proxy_key(),
|
||||
reordered.shared_skill_proxy_key()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ pub(crate) mod safety;
|
||||
pub mod seatbelt;
|
||||
pub mod shell;
|
||||
pub mod shell_snapshot;
|
||||
mod skill_network_proxy_cache;
|
||||
pub mod skills;
|
||||
pub mod spawn;
|
||||
pub mod state_db;
|
||||
|
||||
36
codex-rs/core/src/skill_network_proxy_cache.rs
Normal file
36
codex-rs/core/src/skill_network_proxy_cache.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::config::SkillNetworkProxyKey;
|
||||
use crate::config::StartedNetworkProxy;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SkillNetworkProxyCache {
|
||||
proxies: Mutex<HashMap<SkillNetworkProxyKey, Arc<StartedNetworkProxy>>>,
|
||||
}
|
||||
|
||||
impl SkillNetworkProxyCache {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_or_start<F, Fut>(
|
||||
&self,
|
||||
key: SkillNetworkProxyKey,
|
||||
start: F,
|
||||
) -> anyhow::Result<Arc<StartedNetworkProxy>>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: Future<Output = anyhow::Result<StartedNetworkProxy>>,
|
||||
{
|
||||
let mut proxies = self.proxies.lock().await;
|
||||
if let Some(proxy) = proxies.get(&key) {
|
||||
return Ok(Arc::clone(proxy));
|
||||
}
|
||||
|
||||
let proxy = Arc::new(start().await?);
|
||||
proxies.insert(key, Arc::clone(&proxy));
|
||||
Ok(proxy)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::RolloutRecorder;
|
||||
use crate::agent::AgentControl;
|
||||
use crate::analytics_client::AnalyticsEventsClient;
|
||||
use crate::client::ModelClient;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::StartedNetworkProxy;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
@@ -13,6 +14,7 @@ use crate::mcp::McpManager;
|
||||
use crate::mcp_connection_manager::McpConnectionManager;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skill_network_proxy_cache::SkillNetworkProxyCache;
|
||||
use crate::skills::SkillsManager;
|
||||
use crate::state_db::StateDbHandle;
|
||||
use crate::tools::code_mode::CodeModeService;
|
||||
@@ -55,7 +57,9 @@ pub(crate) struct SessionServices {
|
||||
pub(crate) mcp_manager: Arc<McpManager>,
|
||||
pub(crate) file_watcher: Arc<FileWatcher>,
|
||||
pub(crate) agent_control: AgentControl,
|
||||
pub(crate) network_proxy_spec: Option<Arc<NetworkProxySpec>>,
|
||||
pub(crate) network_proxy: Option<StartedNetworkProxy>,
|
||||
pub(crate) skill_network_proxy_cache: Arc<SkillNetworkProxyCache>,
|
||||
pub(crate) network_approval: Arc<NetworkApprovalService>,
|
||||
pub(crate) state_db: Option<StateDbHandle>,
|
||||
/// Session-scoped model client shared across turns.
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::protocol::SessionConfiguredEvent;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::truncation;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::skill_network_proxy_cache::SkillNetworkProxyCache;
|
||||
use crate::skills::SkillsManager;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
@@ -155,6 +156,7 @@ pub(crate) struct ThreadManagerState {
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
mcp_manager: Arc<McpManager>,
|
||||
file_watcher: Arc<FileWatcher>,
|
||||
skill_network_proxy_cache: Arc<SkillNetworkProxyCache>,
|
||||
session_source: SessionSource,
|
||||
// Captures submitted ops for testing purpose when test mode is enabled.
|
||||
ops_log: Option<SharedCapturedOps>,
|
||||
@@ -177,6 +179,7 @@ impl ThreadManager {
|
||||
config.bundled_skills_enabled(),
|
||||
));
|
||||
let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager));
|
||||
let skill_network_proxy_cache = Arc::new(SkillNetworkProxyCache::new());
|
||||
Self {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
threads: Arc::new(RwLock::new(HashMap::new())),
|
||||
@@ -191,6 +194,7 @@ impl ThreadManager {
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
skill_network_proxy_cache,
|
||||
auth_manager,
|
||||
session_source,
|
||||
ops_log: should_use_test_thread_manager_behavior()
|
||||
@@ -237,6 +241,7 @@ impl ThreadManager {
|
||||
true,
|
||||
));
|
||||
let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager));
|
||||
let skill_network_proxy_cache = Arc::new(SkillNetworkProxyCache::new());
|
||||
Self {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
threads: Arc::new(RwLock::new(HashMap::new())),
|
||||
@@ -250,6 +255,7 @@ impl ThreadManager {
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
skill_network_proxy_cache,
|
||||
auth_manager,
|
||||
session_source: SessionSource::Exec,
|
||||
ops_log: should_use_test_thread_manager_behavior()
|
||||
@@ -685,6 +691,7 @@ impl ThreadManagerState {
|
||||
plugins_manager: Arc::clone(&self.plugins_manager),
|
||||
mcp_manager: Arc::clone(&self.mcp_manager),
|
||||
file_watcher: Arc::clone(&self.file_watcher),
|
||||
skill_network_proxy_cache: Arc::clone(&self.skill_network_proxy_cache),
|
||||
conversation_history: initial_history,
|
||||
session_source,
|
||||
agent_control,
|
||||
|
||||
@@ -50,6 +50,7 @@ use codex_shell_escalation::ShellCommandExecutor;
|
||||
use codex_shell_escalation::Stopwatch;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -142,6 +143,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
ctx.session.services.exec_policy.current().as_ref().clone(),
|
||||
));
|
||||
let command_executor = CoreShellCommandExecutor {
|
||||
session: Some(Arc::clone(&ctx.session)),
|
||||
command,
|
||||
cwd: sandbox_cwd,
|
||||
sandbox_policy,
|
||||
@@ -259,6 +261,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
ctx.session.services.exec_policy.current().as_ref().clone(),
|
||||
));
|
||||
let command_executor = CoreShellCommandExecutor {
|
||||
session: Some(Arc::clone(&ctx.session)),
|
||||
command: exec_request.command.clone(),
|
||||
cwd: exec_request.cwd.clone(),
|
||||
sandbox_policy: exec_request.sandbox_policy.clone(),
|
||||
@@ -503,26 +506,7 @@ impl CoreShellActionProvider {
|
||||
/// an absolute path. The idea is that we check to see whether it matches
|
||||
/// any skills.
|
||||
async fn find_skill(&self, program: &AbsolutePathBuf) -> Option<SkillMetadata> {
|
||||
let force_reload = false;
|
||||
let skills_outcome = self
|
||||
.session
|
||||
.services
|
||||
.skills_manager
|
||||
.skills_for_cwd(&self.turn.cwd, force_reload)
|
||||
.await;
|
||||
|
||||
let program_path = program.as_path();
|
||||
for skill in skills_outcome.skills {
|
||||
// We intentionally ignore "enabled" status here for now.
|
||||
let Some(skill_root) = skill.path_to_skills_md.parent() else {
|
||||
continue;
|
||||
};
|
||||
if program_path.starts_with(skill_root.join("scripts")) {
|
||||
return Some(skill);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
find_skill_for_program(self.session.as_ref(), self.turn.cwd.as_path(), program).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -865,6 +849,7 @@ fn commands_for_intercepted_exec_policy(
|
||||
}
|
||||
|
||||
struct CoreShellCommandExecutor {
|
||||
session: Option<Arc<crate::codex::Session>>,
|
||||
command: Vec<String>,
|
||||
cwd: PathBuf,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
@@ -888,6 +873,7 @@ struct PrepareSandboxedExecParams<'a> {
|
||||
command: Vec<String>,
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
network: Option<codex_network_proxy::NetworkProxy>,
|
||||
sandbox_policy: &'a SandboxPolicy,
|
||||
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -969,10 +955,12 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
arg0: Some(first_arg.clone()),
|
||||
},
|
||||
EscalationExecution::TurnDefault => {
|
||||
let network = self.network_for_program(program).await?;
|
||||
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
network,
|
||||
sandbox_policy: &self.sandbox_policy,
|
||||
file_system_sandbox_policy: &self.file_system_sandbox_policy,
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
@@ -988,10 +976,12 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
)) => {
|
||||
// Merge additive permissions into the existing turn/request sandbox policy.
|
||||
// On macOS, additional profile extensions are unioned with the turn defaults.
|
||||
let network = self.network_for_program(program).await?;
|
||||
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
network,
|
||||
sandbox_policy: &self.sandbox_policy,
|
||||
file_system_sandbox_policy: &self.file_system_sandbox_policy,
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
@@ -1004,10 +994,12 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
}
|
||||
EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => {
|
||||
// Use a fully specified sandbox policy instead of merging into the turn policy.
|
||||
let network = self.network_for_program(program).await?;
|
||||
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
network,
|
||||
sandbox_policy: &permissions.sandbox_policy,
|
||||
file_system_sandbox_policy: &permissions.file_system_sandbox_policy,
|
||||
network_sandbox_policy: permissions.network_sandbox_policy,
|
||||
@@ -1034,6 +1026,7 @@ impl CoreShellCommandExecutor {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
network,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -1072,8 +1065,8 @@ impl CoreShellCommandExecutor {
|
||||
file_system_policy: file_system_sandbox_policy,
|
||||
network_policy: network_sandbox_policy,
|
||||
sandbox,
|
||||
enforce_managed_network: self.network.is_some(),
|
||||
network: self.network.as_ref(),
|
||||
enforce_managed_network: network.is_some(),
|
||||
network: network.as_ref(),
|
||||
sandbox_policy_cwd: &self.sandbox_policy_cwd,
|
||||
#[cfg(target_os = "macos")]
|
||||
macos_seatbelt_profile_extensions,
|
||||
@@ -1092,6 +1085,26 @@ impl CoreShellCommandExecutor {
|
||||
arg0: exec_request.arg0,
|
||||
})
|
||||
}
|
||||
|
||||
async fn network_for_program(
|
||||
&self,
|
||||
program: &AbsolutePathBuf,
|
||||
) -> anyhow::Result<Option<codex_network_proxy::NetworkProxy>> {
|
||||
let Some(session) = self.session.as_ref() else {
|
||||
return Ok(self.network.clone());
|
||||
};
|
||||
let Some(skill) =
|
||||
find_skill_for_program(session.as_ref(), self.sandbox_policy_cwd.as_path(), program)
|
||||
.await
|
||||
else {
|
||||
return Ok(self.network.clone());
|
||||
};
|
||||
if let Some(network) = session.get_or_start_skill_network_proxy(&skill).await? {
|
||||
Ok(Some(network))
|
||||
} else {
|
||||
Ok(self.network.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
@@ -1167,6 +1180,31 @@ fn join_program_and_argv(program: &AbsolutePathBuf, argv: &[String]) -> Vec<Stri
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
async fn find_skill_for_program(
|
||||
session: &crate::codex::Session,
|
||||
cwd: &Path,
|
||||
program: &AbsolutePathBuf,
|
||||
) -> Option<SkillMetadata> {
|
||||
let force_reload = false;
|
||||
let skills_outcome = session
|
||||
.services
|
||||
.skills_manager
|
||||
.skills_for_cwd(cwd, force_reload)
|
||||
.await;
|
||||
|
||||
let program_path = program.as_path();
|
||||
for skill in skills_outcome.skills {
|
||||
let Some(skill_root) = skill.path_to_skills_md.parent() else {
|
||||
continue;
|
||||
};
|
||||
if program_path.starts_with(skill_root.join("scripts")) {
|
||||
return Some(skill);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "unix_escalation_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -651,6 +651,7 @@ host_executable(name = "git", paths = ["{allowed_git_literal}"])
|
||||
async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions() {
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
|
||||
let executor = CoreShellCommandExecutor {
|
||||
session: None,
|
||||
command: vec!["echo".to_string(), "ok".to_string()],
|
||||
cwd: cwd.to_path_buf(),
|
||||
env: HashMap::new(),
|
||||
@@ -703,6 +704,7 @@ async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions
|
||||
async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions() {
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
|
||||
let executor = CoreShellCommandExecutor {
|
||||
session: None,
|
||||
command: vec!["echo".to_string(), "ok".to_string()],
|
||||
cwd: cwd.to_path_buf(),
|
||||
env: HashMap::new(),
|
||||
@@ -777,6 +779,7 @@ async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_mac
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let executor = CoreShellCommandExecutor {
|
||||
session: None,
|
||||
command: vec!["echo".to_string(), "ok".to_string()],
|
||||
cwd: cwd.to_path_buf(),
|
||||
env: HashMap::new(),
|
||||
|
||||
Reference in New Issue
Block a user