mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
permissions: make runtime config profile-backed (#19606)
## Why This supersedes #19391. During stack repair, GitHub marked #19391 as merged into a temporary stack branch rather than into `main`, so the runtime-config change needed a fresh PR. `PermissionProfile` is now the canonical permissions shape after #19231 because it can distinguish `Managed`, `Disabled`, and `External` enforcement while also carrying filesystem rules that legacy `SandboxPolicy` cannot represent cleanly. Core config and session state still needed to accept profile-backed permissions without forcing every profile through the strict legacy bridge, which rejected valid runtime profiles such as direct write roots. The unrelated CI/test hardening that previously rode along with this PR has been split into #19683 so this PR stays focused on the permissions model migration. ## What Changed - Adds `Permissions.permission_profile` and `SessionConfiguration.permission_profile` as constrained runtime state, while keeping `sandbox_policy` as a legacy compatibility projection. - Introduces profile setters that keep `PermissionProfile`, split filesystem/network policies, and legacy `SandboxPolicy` projections synchronized. - Uses a compatibility projection for requirement checks and legacy consumers instead of rejecting profiles that cannot round-trip through `SandboxPolicy` exactly. - Updates config loading, config overrides, session updates, turn context plumbing, prompt permission text, sandbox tags, and exec request construction to carry profile-backed runtime permissions. - Preserves configured deny-read entries and `glob_scan_max_depth` when command/session profiles are narrowed. - Adds `PermissionProfile::read_only()` and `PermissionProfile::workspace_write()` presets that match legacy defaults. ## Verification - `cargo test -p codex-core direct_write_roots` - `cargo test -p codex-core runtime_roots_to_legacy_projection` - `cargo test -p codex-app-server requested_permissions_trust_project_uses_permission_profile_intent` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19606). * #19395 * #19394 * #19393 * #19392 * __->__ #19606
This commit is contained in:
@@ -161,7 +161,7 @@ fn sample_thread_start_response(thread_id: &str, ephemeral: bool, model: &str) -
|
||||
}
|
||||
|
||||
fn sample_permission_profile() -> AppServerPermissionProfile {
|
||||
CorePermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::DangerFullAccess).into()
|
||||
CorePermissionProfile::Disabled.into()
|
||||
}
|
||||
|
||||
fn sample_app_server_client_metadata() -> CodexAppServerClientMetadata {
|
||||
|
||||
@@ -359,6 +359,7 @@ use codex_rmcp_client::perform_oauth_login_return_url;
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
use codex_rollout::state_db::get_state_db;
|
||||
use codex_rollout::state_db::reconcile_rollout;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_state::StateRuntime;
|
||||
use codex_state::ThreadMetadata;
|
||||
use codex_state::ThreadMetadataBuilder;
|
||||
@@ -2272,44 +2273,34 @@ impl CodexMessageProcessor {
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
let (
|
||||
effective_policy,
|
||||
effective_file_system_sandbox_policy,
|
||||
effective_network_sandbox_policy,
|
||||
) = if let Some(permission_profile) = permission_profile {
|
||||
let effective_permission_profile = if let Some(permission_profile) = permission_profile {
|
||||
let permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from(permission_profile);
|
||||
let sandbox_policy = match permission_profile.to_legacy_sandbox_policy(&sandbox_cwd) {
|
||||
Ok(sandbox_policy) => sandbox_policy,
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: format!("invalid permission profile: {err}"),
|
||||
data: None,
|
||||
};
|
||||
self.outgoing.send_error(request, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
Self::preserve_configured_deny_read_restrictions(
|
||||
&mut file_system_sandbox_policy,
|
||||
&self.config.permissions.file_system_sandbox_policy,
|
||||
);
|
||||
let effective_permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
sandbox_cwd.as_path(),
|
||||
);
|
||||
match self
|
||||
.config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.can_set(&sandbox_policy)
|
||||
{
|
||||
Ok(()) => {
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
Self::preserve_configured_deny_read_restrictions(
|
||||
&mut file_system_sandbox_policy,
|
||||
&self.config.permissions.file_system_sandbox_policy,
|
||||
);
|
||||
(
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
}
|
||||
Ok(()) => effective_permission_profile,
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
@@ -2327,7 +2318,13 @@ impl CodexMessageProcessor {
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd);
|
||||
let network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(&policy);
|
||||
(policy, file_system_sandbox_policy, network_sandbox_policy)
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
&policy,
|
||||
),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
}
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
@@ -2340,11 +2337,7 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(
|
||||
self.config.permissions.sandbox_policy.get().clone(),
|
||||
self.config.permissions.file_system_sandbox_policy.clone(),
|
||||
self.config.permissions.network_sandbox_policy,
|
||||
)
|
||||
self.config.permissions.permission_profile()
|
||||
};
|
||||
|
||||
let codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone();
|
||||
@@ -2363,9 +2356,7 @@ impl CodexMessageProcessor {
|
||||
|
||||
match codex_core::exec::build_exec_request(
|
||||
exec_params,
|
||||
&effective_policy,
|
||||
&effective_file_system_sandbox_policy,
|
||||
effective_network_sandbox_policy,
|
||||
&effective_permission_profile,
|
||||
&sandbox_cwd,
|
||||
&codex_linux_sandbox_exe,
|
||||
use_legacy_landlock,
|
||||
@@ -10161,16 +10152,20 @@ fn requested_permissions_trust_project(overrides: &ConfigOverrides, cwd: &Path)
|
||||
.permission_profile
|
||||
.as_ref()
|
||||
.is_some_and(|profile| {
|
||||
profile
|
||||
.to_legacy_sandbox_policy(cwd)
|
||||
.is_ok_and(|sandbox_policy| {
|
||||
matches!(
|
||||
sandbox_policy,
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
| codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. }
|
||||
)
|
||||
})
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
profile.to_runtime_permissions();
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
cwd,
|
||||
);
|
||||
matches!(
|
||||
sandbox_policy,
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
| codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10672,16 +10667,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn thread_response_permission_profile_preserves_enforcement() {
|
||||
let full_access_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
);
|
||||
let external_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::ExternalSandbox {
|
||||
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
|
||||
},
|
||||
);
|
||||
let full_access_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let external_profile = codex_protocol::models::PermissionProfile::External {
|
||||
network: codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(external_profile.clone()),
|
||||
@@ -10696,17 +10685,20 @@ mod tests {
|
||||
#[test]
|
||||
fn requested_permissions_trust_project_uses_permission_profile_intent() {
|
||||
let cwd = test_path_buf("/tmp/project").abs();
|
||||
let full_access_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
);
|
||||
let workspace_write_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
);
|
||||
let read_only_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
let full_access_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let workspace_write_profile = codex_protocol::models::PermissionProfile::workspace_write();
|
||||
let read_only_profile = codex_protocol::models::PermissionProfile::read_only();
|
||||
let direct_write_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions(
|
||||
&codex_protocol::permissions::FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: test_path_buf("/tmp/other").abs(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
]),
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
assert!(requested_permissions_trust_project(
|
||||
@@ -10723,6 +10715,13 @@ mod tests {
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
permission_profile: Some(direct_write_profile),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(!requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
permission_profile: Some(read_only_profile),
|
||||
@@ -10915,10 +10914,7 @@ mod tests {
|
||||
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
permission_profile:
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
),
|
||||
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
|
||||
cwd,
|
||||
ephemeral: false,
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -710,9 +710,7 @@ mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -729,12 +727,10 @@ mod tests {
|
||||
use crate::outgoing_message::OutgoingMessage;
|
||||
|
||||
fn windows_sandbox_exec_request() -> ExecRequest {
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly {
|
||||
network_access: false,
|
||||
};
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
|
||||
ExecRequest::new(
|
||||
vec!["cmd".to_string()],
|
||||
AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
cwd,
|
||||
HashMap::new(),
|
||||
/*network*/ None,
|
||||
ExecExpiration::DefaultTimeout,
|
||||
@@ -742,9 +738,7 @@ mod tests {
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*windows_sandbox_private_desktop*/ false,
|
||||
sandbox_policy.clone(),
|
||||
FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
PermissionProfile::read_only(),
|
||||
/*arg0*/ None,
|
||||
)
|
||||
}
|
||||
@@ -834,9 +828,7 @@ mod tests {
|
||||
connection_id: ConnectionId(8),
|
||||
request_id: codex_app_server_protocol::RequestId::Integer(100),
|
||||
};
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly {
|
||||
network_access: false,
|
||||
};
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
|
||||
|
||||
manager
|
||||
.start(StartCommandExecParams {
|
||||
@@ -845,7 +837,7 @@ mod tests {
|
||||
process_id: Some("proc-100".to_string()),
|
||||
exec_request: ExecRequest::new(
|
||||
vec!["sh".to_string(), "-lc".to_string(), "sleep 30".to_string()],
|
||||
AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
cwd.clone(),
|
||||
HashMap::new(),
|
||||
/*network*/ None,
|
||||
ExecExpiration::Cancellation(CancellationToken::new()),
|
||||
@@ -853,9 +845,7 @@ mod tests {
|
||||
SandboxType::None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*windows_sandbox_private_desktop*/ false,
|
||||
sandbox_policy.clone(),
|
||||
FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
PermissionProfile::read_only(),
|
||||
/*arg0*/ None,
|
||||
),
|
||||
started_network_proxy: None,
|
||||
|
||||
@@ -17,6 +17,9 @@ use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(25);
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const INSTALLED_MARKETPLACES_DIR: &str = ".tmp/marketplaces";
|
||||
|
||||
@@ -63,13 +66,14 @@ fn commit_marketplace_marker(root: &Path, marker: &str) -> Result<String> {
|
||||
fn configured_git_marketplace_update<'a>(
|
||||
source: &'a str,
|
||||
last_revision: Option<&'a str>,
|
||||
ref_name: Option<&'a str>,
|
||||
) -> MarketplaceConfigUpdate<'a> {
|
||||
MarketplaceConfigUpdate {
|
||||
last_updated: "2026-04-13T00:00:00Z",
|
||||
last_revision,
|
||||
source_type: "git",
|
||||
source,
|
||||
ref_name: None,
|
||||
ref_name,
|
||||
sparse_paths: &[],
|
||||
}
|
||||
}
|
||||
@@ -90,12 +94,13 @@ fn record_git_marketplace(
|
||||
marketplace_name: &str,
|
||||
source: &Path,
|
||||
last_revision: &str,
|
||||
ref_name: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let source = source.display().to_string();
|
||||
record_user_marketplace(
|
||||
codex_home,
|
||||
marketplace_name,
|
||||
&configured_git_marketplace_update(&source, Some(last_revision)),
|
||||
&configured_git_marketplace_update(&source, Some(last_revision), ref_name),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -153,12 +158,14 @@ async fn marketplace_upgrade_all_configured_git_marketplaces() -> Result<()> {
|
||||
"debug",
|
||||
debug_source.path(),
|
||||
&debug_old_revision,
|
||||
Some(&debug_new_revision),
|
||||
)?;
|
||||
record_git_marketplace(
|
||||
codex_home.path(),
|
||||
"tools",
|
||||
tools_source.path(),
|
||||
&tools_old_revision,
|
||||
Some(&tools_new_revision),
|
||||
)?;
|
||||
disable_plugin_startup_tasks(codex_home.path())?;
|
||||
|
||||
@@ -205,12 +212,14 @@ async fn marketplace_upgrade_named_marketplace_only() -> Result<()> {
|
||||
"debug",
|
||||
debug_source.path(),
|
||||
&debug_old_revision,
|
||||
/*ref_name*/ None,
|
||||
)?;
|
||||
record_git_marketplace(
|
||||
codex_home.path(),
|
||||
"tools",
|
||||
tools_source.path(),
|
||||
&tools_old_revision,
|
||||
/*ref_name*/ None,
|
||||
)?;
|
||||
disable_plugin_startup_tasks(codex_home.path())?;
|
||||
|
||||
@@ -246,7 +255,13 @@ async fn marketplace_upgrade_returns_empty_roots_when_already_up_to_date() -> Re
|
||||
let source = TempDir::new()?;
|
||||
let old_revision = init_marketplace_repo(source.path(), "debug", "debug old")?;
|
||||
commit_marketplace_marker(source.path(), "debug new")?;
|
||||
record_git_marketplace(codex_home.path(), "debug", source.path(), &old_revision)?;
|
||||
record_git_marketplace(
|
||||
codex_home.path(),
|
||||
"debug",
|
||||
source.path(),
|
||||
&old_revision,
|
||||
/*ref_name*/ None,
|
||||
)?;
|
||||
disable_plugin_startup_tasks(codex_home.path())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
|
||||
@@ -749,13 +749,17 @@ async fn turn_start_rejects_combined_oversized_text_input() -> Result<()> {
|
||||
#[tokio::test]
|
||||
async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let unsupported_write_root = TempDir::new()?;
|
||||
let disallowed_write_root = TempDir::new()?;
|
||||
create_config_toml(
|
||||
codex_home.path(),
|
||||
"http://localhost/unused",
|
||||
"never",
|
||||
&BTreeMap::from([(Feature::Personality, true)]),
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("managed_config.toml"),
|
||||
"sandbox_mode = \"read-only\"\n",
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
@@ -772,7 +776,7 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
let unsupported_write_root = AbsolutePathBuf::from_absolute_path(unsupported_write_root.path())
|
||||
let disallowed_write_root = AbsolutePathBuf::from_absolute_path(disallowed_write_root.path())
|
||||
.expect("tempdir path should be absolute");
|
||||
|
||||
let turn_req = mcp
|
||||
@@ -787,7 +791,7 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
file_system: PermissionProfileFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: unsupported_write_root,
|
||||
path: disallowed_write_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}],
|
||||
@@ -806,9 +810,9 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
assert_eq!(err.error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert!(err.error.message.contains("invalid turn context override"));
|
||||
assert!(
|
||||
err.error
|
||||
.message
|
||||
.contains("filesystem writes outside the workspace root")
|
||||
err.error.message.contains("allowed set [ReadOnly]"),
|
||||
"unexpected error message: {}",
|
||||
err.error.message
|
||||
);
|
||||
let turn_started = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(250),
|
||||
|
||||
@@ -56,6 +56,7 @@ use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -63,6 +64,7 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use codex_protocol::protocol::RealtimeVoice;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use serde::Deserialize;
|
||||
@@ -843,6 +845,145 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_preserves_managed_unrestricted_filesystem()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let permission_profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
permission_profile: Some(permission_profile.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.permissions.permission_profile(), permission_profile);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::ExternalSandbox {
|
||||
network_access: NetworkAccess::Restricted,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_unrestricted_permission_profile_still_enables_network_requirements()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let permission_profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
};
|
||||
|
||||
let mut config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
permission_profile: Some(permission_profile),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
"the legacy projection is intentionally lossy for managed unrestricted profiles"
|
||||
);
|
||||
|
||||
let layers = config
|
||||
.config_layer_stack
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let mut requirements = config.config_layer_stack.requirements().clone();
|
||||
requirements.network = Some(Sourced::new(
|
||||
crate::config_loader::NetworkConstraints {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
RequirementSource::CloudRequirements,
|
||||
));
|
||||
let mut requirements_toml = config.config_layer_stack.requirements_toml().clone();
|
||||
requirements_toml.network = Some(crate::config_loader::NetworkRequirementsToml {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
config.config_layer_stack = ConfigLayerStack::new(layers, requirements, requirements_toml)
|
||||
.expect("config layer stack with network requirements");
|
||||
|
||||
assert!(config.managed_network_requirements_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_applies_runtime_roots_to_legacy_projection()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
permission_profile: Some(permission_profile),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let memories_root = codex_home.path().join("memories").abs();
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(memories_root.as_path(), cwd.path())
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![memories_root],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_preserves_configured_network_proxy() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -1020,13 +1161,16 @@ async fn permissions_profiles_require_default_permissions() -> std::io::Result<(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_profiles_reject_writes_outside_workspace_root() -> std::io::Result<()> {
|
||||
async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
let external_write_path = if cfg!(windows) { r"C:\temp" } else { "/tmp" };
|
||||
let external_write_dir = TempDir::new()?;
|
||||
let external_write_path =
|
||||
AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(external_write_dir.path())?)?;
|
||||
|
||||
let err = Config::load_from_base_config_with_overrides(
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
@@ -1036,7 +1180,7 @@ async fn permissions_profiles_reject_writes_outside_workspace_root() -> std::io:
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
external_write_path.to_string(),
|
||||
external_write_path.to_string_lossy().into_owned(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
)]),
|
||||
}),
|
||||
@@ -1052,14 +1196,25 @@ async fn permissions_profiles_reject_writes_outside_workspace_root() -> std::io:
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await
|
||||
.expect_err("writes outside the workspace root should be rejected");
|
||||
.await?;
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
let memories_root = AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(
|
||||
codex_home.path().join("memories"),
|
||||
)?)?;
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("filesystem writes outside the workspace root"),
|
||||
"{err}"
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(external_write_path.as_path(), cwd.path())
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![external_write_path, memories_root],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -5292,6 +5447,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
@@ -5489,6 +5645,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
model_provider: fixture.openai_custom_provider.clone(),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
@@ -5640,6 +5797,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
@@ -5776,6 +5934,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
@@ -6602,6 +6761,40 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_falls_back_when_disallowed_by_requirements()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let requirements = crate::config_loader::ConfigRequirementsToml {
|
||||
allowed_sandbox_modes: Some(vec![crate::config_loader::SandboxModeRequirement::ReadOnly]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.harness_overrides(ConfigOverrides {
|
||||
permission_profile: Some(PermissionProfile::Disabled),
|
||||
..Default::default()
|
||||
})
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async move {
|
||||
Ok(Some(requirements))
|
||||
}))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let expected_sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
assert_eq!(
|
||||
*config.permissions.sandbox_policy.get(),
|
||||
expected_sandbox_policy
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.permission_profile(),
|
||||
PermissionProfile::read_only()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn requirements_web_search_mode_overrides_danger_full_access_default() -> std::io::Result<()>
|
||||
{
|
||||
|
||||
+159
-22
@@ -115,6 +115,7 @@ pub use codex_config::Constrained;
|
||||
pub use codex_config::ConstraintError;
|
||||
pub use codex_config::ConstraintResult;
|
||||
pub use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
pub use codex_sandboxing::system_bwrap_warning;
|
||||
pub use managed_features::ManagedFeatures;
|
||||
pub use network_proxy_spec::NetworkProxySpec;
|
||||
@@ -191,13 +192,25 @@ pub(crate) async fn test_config() -> Config {
|
||||
pub struct Permissions {
|
||||
/// Approval policy for executing commands.
|
||||
pub approval_policy: Constrained<AskForApproval>,
|
||||
/// Canonical effective runtime permissions after config requirements and
|
||||
/// runtime readable-root additions have been applied.
|
||||
pub permission_profile: Constrained<PermissionProfile>,
|
||||
/// Effective sandbox policy used for shell/unified exec.
|
||||
///
|
||||
/// Legacy projection retained while runtime call sites migrate to
|
||||
/// `permission_profile`.
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Effective filesystem sandbox policy, including entries that cannot yet
|
||||
/// be fully represented by the legacy [`SandboxPolicy`] projection.
|
||||
///
|
||||
/// Runtime projection retained while callers migrate to
|
||||
/// `permission_profile`.
|
||||
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
/// Effective network sandbox policy split out from the legacy
|
||||
/// [`SandboxPolicy`] projection.
|
||||
///
|
||||
/// Runtime projection retained while callers migrate to
|
||||
/// `permission_profile`.
|
||||
pub network_sandbox_policy: NetworkSandboxPolicy,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
@@ -223,12 +236,87 @@ impl Permissions {
|
||||
/// Effective runtime permissions after config requirements and runtime
|
||||
/// readable-root additions have been applied.
|
||||
pub fn permission_profile(&self) -> PermissionProfile {
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(self.sandbox_policy.get()),
|
||||
&self.file_system_sandbox_policy,
|
||||
self.network_sandbox_policy,
|
||||
)
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
/// Effective filesystem sandbox policy projection.
|
||||
pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.file_system_sandbox_policy.clone()
|
||||
}
|
||||
|
||||
/// Effective network sandbox policy projection.
|
||||
pub fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.network_sandbox_policy
|
||||
}
|
||||
|
||||
/// Replace permissions from a legacy sandbox policy and keep every
|
||||
/// permission projection in sync.
|
||||
pub fn set_legacy_sandbox_policy(
|
||||
&mut self,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> ConstraintResult<()> {
|
||||
self.sandbox_policy.can_set(&sandbox_policy)?;
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
self.permission_profile.can_set(&permission_profile)?;
|
||||
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replace permissions from the canonical profile and update compatibility
|
||||
/// projections for legacy consumers.
|
||||
pub fn set_permission_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
cwd: &Path,
|
||||
) -> ConstraintResult<()> {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
cwd,
|
||||
);
|
||||
self.permission_profile.can_set(&permission_profile)?;
|
||||
self.sandbox_policy.can_set(&sandbox_policy)?;
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn constrained_permission_profile_from_sandbox_projection(
|
||||
initial_value: PermissionProfile,
|
||||
sandbox_constraint: Constrained<SandboxPolicy>,
|
||||
cwd: AbsolutePathBuf,
|
||||
) -> std::io::Result<Constrained<PermissionProfile>> {
|
||||
Constrained::new(initial_value, move |candidate| {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
candidate.to_runtime_permissions();
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
candidate,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
cwd.as_path(),
|
||||
);
|
||||
sandbox_constraint.can_set(&sandbox_policy)
|
||||
})
|
||||
.map_err(std::io::Error::from)
|
||||
}
|
||||
|
||||
/// Configured thread persistence backend.
|
||||
@@ -1807,10 +1895,11 @@ impl Config {
|
||||
&& has_permission_profiles);
|
||||
let (
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
) = if let Some(permission_profile) = permission_profile {
|
||||
) = if let Some(mut permission_profile) = permission_profile {
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let configured_network_proxy_config =
|
||||
@@ -1836,25 +1925,33 @@ impl Config {
|
||||
} else {
|
||||
NetworkProxyConfig::default()
|
||||
};
|
||||
let mut sandbox_policy = permission_profile
|
||||
.to_legacy_sandbox_policy(resolved_cwd.as_path())
|
||||
.map_err(|err| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("invalid permission_profile override: {err}"),
|
||||
)
|
||||
})?;
|
||||
let mut sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
|
||||
file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_additional_writable_roots(
|
||||
resolved_cwd.as_path(),
|
||||
&additional_writable_roots,
|
||||
);
|
||||
sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
}
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -1882,19 +1979,36 @@ impl Config {
|
||||
resolved_cwd.as_path(),
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
let mut permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let mut sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
|
||||
file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_additional_writable_roots(
|
||||
resolved_cwd.as_path(),
|
||||
&additional_writable_roots,
|
||||
);
|
||||
sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
}
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -1923,8 +2037,14 @@ impl Config {
|
||||
resolved_cwd.as_path(),
|
||||
);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -2343,6 +2463,22 @@ impl Config {
|
||||
} else {
|
||||
NetworkSandboxPolicy::from(&effective_sandbox_policy)
|
||||
};
|
||||
let effective_enforcement = if effective_sandbox_policy == original_sandbox_policy {
|
||||
permission_profile.enforcement()
|
||||
} else {
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&effective_sandbox_policy)
|
||||
};
|
||||
let effective_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
effective_enforcement,
|
||||
&effective_file_system_sandbox_policy,
|
||||
effective_network_sandbox_policy,
|
||||
);
|
||||
let constrained_permission_profile =
|
||||
constrained_permission_profile_from_sandbox_projection(
|
||||
effective_permission_profile,
|
||||
constrained_sandbox_policy.value.clone(),
|
||||
resolved_cwd.clone(),
|
||||
)?;
|
||||
let config = Self {
|
||||
model,
|
||||
service_tier,
|
||||
@@ -2355,6 +2491,7 @@ impl Config {
|
||||
startup_warnings,
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
permission_profile: constrained_permission_profile,
|
||||
sandbox_policy: constrained_sandbox_policy.value,
|
||||
file_system_sandbox_policy: effective_file_system_sandbox_policy,
|
||||
network_sandbox_policy: effective_network_sandbox_policy,
|
||||
@@ -2610,8 +2747,8 @@ impl Config {
|
||||
|
||||
pub fn managed_network_requirements_enabled(&self) -> bool {
|
||||
!matches!(
|
||||
self.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::DangerFullAccess
|
||||
self.permissions.permission_profile.get(),
|
||||
PermissionProfile::Disabled
|
||||
) && self
|
||||
.config_layer_stack
|
||||
.requirements_toml()
|
||||
|
||||
@@ -383,6 +383,16 @@ fn validate_glob_scan_max_depth(max_depth: Option<usize>) -> io::Result<Option<u
|
||||
}
|
||||
|
||||
fn contains_glob_chars(path: &str) -> bool {
|
||||
contains_glob_chars_for_platform(path, cfg!(windows))
|
||||
}
|
||||
|
||||
fn contains_glob_chars_for_platform(path: &str, is_windows: bool) -> bool {
|
||||
let normalized_windows_path = if is_windows {
|
||||
normalize_windows_device_path(path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let path = normalized_windows_path.as_deref().unwrap_or(path);
|
||||
path.chars().any(|ch| matches!(ch, '*' | '?' | '[' | ']'))
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,18 @@ fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() {
|
||||
assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_verbatim_path_prefix_does_not_count_as_glob_syntax() {
|
||||
assert!(!contains_glob_chars_for_platform(
|
||||
r"\\?\D:\c\x\worktrees\2508\swift-base",
|
||||
/*is_windows*/ true,
|
||||
));
|
||||
assert!(contains_glob_chars_for_platform(
|
||||
r"\\?\D:\c\x\worktrees\2508\**\*.env",
|
||||
/*is_windows*/ true,
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
|
||||
@@ -2,7 +2,9 @@ use super::ContextualUserFragment;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::format_allow_prefixes;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
@@ -57,7 +59,33 @@ pub struct PermissionsInstructions {
|
||||
}
|
||||
|
||||
impl PermissionsInstructions {
|
||||
/// Builds permissions instructions from the effective sandbox and approval policy.
|
||||
/// Builds permissions instructions from the effective permission profile and approval policy.
|
||||
pub fn from_permission_profile(
|
||||
permission_profile: &PermissionProfile,
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
exec_policy: &Policy,
|
||||
cwd: &Path,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
request_permissions_tool_enabled: bool,
|
||||
) -> Self {
|
||||
let (sandbox_mode, writable_roots) = sandbox_prompt_from_profile(permission_profile, cwd);
|
||||
|
||||
Self::from_permissions_with_network(
|
||||
sandbox_mode,
|
||||
network_access_from_policy(permission_profile.network_sandbox_policy()),
|
||||
PermissionsPromptConfig {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
exec_policy,
|
||||
exec_permission_approvals_enabled,
|
||||
request_permissions_tool_enabled,
|
||||
},
|
||||
writable_roots,
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds permissions instructions from a legacy sandbox policy.
|
||||
pub fn from_policy(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
approval_policy: AskForApproval,
|
||||
@@ -67,33 +95,14 @@ impl PermissionsInstructions {
|
||||
exec_permission_approvals_enabled: bool,
|
||||
request_permissions_tool_enabled: bool,
|
||||
) -> Self {
|
||||
let network_access = if sandbox_policy.has_full_network_access() {
|
||||
NetworkAccess::Enabled
|
||||
} else {
|
||||
NetworkAccess::Restricted
|
||||
};
|
||||
|
||||
let (sandbox_mode, writable_roots) = match sandbox_policy {
|
||||
SandboxPolicy::DangerFullAccess => (SandboxMode::DangerFullAccess, None),
|
||||
SandboxPolicy::ReadOnly { .. } => (SandboxMode::ReadOnly, None),
|
||||
SandboxPolicy::ExternalSandbox { .. } => (SandboxMode::DangerFullAccess, None),
|
||||
SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
let roots = sandbox_policy.get_writable_roots_with_cwd(cwd);
|
||||
(SandboxMode::WorkspaceWrite, Some(roots))
|
||||
}
|
||||
};
|
||||
|
||||
Self::from_permissions_with_network(
|
||||
sandbox_mode,
|
||||
network_access,
|
||||
PermissionsPromptConfig {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
exec_policy,
|
||||
exec_permission_approvals_enabled,
|
||||
request_permissions_tool_enabled,
|
||||
},
|
||||
writable_roots,
|
||||
Self::from_permission_profile(
|
||||
&PermissionProfile::from_legacy_sandbox_policy(sandbox_policy),
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
exec_policy,
|
||||
cwd,
|
||||
exec_permission_approvals_enabled,
|
||||
request_permissions_tool_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,6 +134,38 @@ impl PermissionsInstructions {
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_prompt_from_profile(
|
||||
permission_profile: &PermissionProfile,
|
||||
cwd: &Path,
|
||||
) -> (SandboxMode, Option<Vec<WritableRoot>>) {
|
||||
match permission_profile {
|
||||
PermissionProfile::Disabled | PermissionProfile::External { .. } => {
|
||||
(SandboxMode::DangerFullAccess, None)
|
||||
}
|
||||
PermissionProfile::Managed { .. } => {
|
||||
let file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
if file_system_policy.has_full_disk_write_access() {
|
||||
return (SandboxMode::DangerFullAccess, None);
|
||||
}
|
||||
|
||||
let writable_roots = file_system_policy.get_writable_roots_with_cwd(cwd);
|
||||
if writable_roots.is_empty() {
|
||||
(SandboxMode::ReadOnly, None)
|
||||
} else {
|
||||
(SandboxMode::WorkspaceWrite, Some(writable_roots))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn network_access_from_policy(network_policy: NetworkSandboxPolicy) -> NetworkAccess {
|
||||
if network_policy.is_enabled() {
|
||||
NetworkAccess::Enabled
|
||||
} else {
|
||||
NetworkAccess::Restricted
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for PermissionsInstructions {
|
||||
const ROLE: &'static str = "developer";
|
||||
const START_MARKER: &'static str = "<permissions instructions>";
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use super::*;
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -70,6 +76,36 @@ fn builds_permissions_from_policy() {
|
||||
assert!(text.contains("`approval_policy` is `unless-trusted`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_permissions_from_profile() {
|
||||
let cwd = PathBuf::from("/tmp");
|
||||
let writable_root =
|
||||
AbsolutePathBuf::from_absolute_path(cwd.join("repo")).expect("absolute path");
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: writable_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
|
||||
let instructions = PermissionsInstructions::from_permission_profile(
|
||||
&permission_profile,
|
||||
AskForApproval::UnlessTrusted,
|
||||
ApprovalsReviewer::User,
|
||||
&Policy::empty(),
|
||||
&cwd,
|
||||
/*exec_permission_approvals_enabled*/ false,
|
||||
/*request_permissions_tool_enabled*/ false,
|
||||
);
|
||||
let text = instructions.body();
|
||||
assert!(text.contains("`sandbox_mode` is `workspace-write`"));
|
||||
assert!(text.contains("Network access is enabled."));
|
||||
assert!(text.contains(writable_root.to_string_lossy().as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn includes_request_rule_instructions_for_on_request() {
|
||||
let mut exec_policy = Policy::empty();
|
||||
|
||||
@@ -56,8 +56,8 @@ fn build_permissions_update_item(
|
||||
}
|
||||
|
||||
Some(
|
||||
PermissionsInstructions::from_policy(
|
||||
next.sandbox_policy.get(),
|
||||
PermissionsInstructions::from_permission_profile(
|
||||
&next.permission_profile,
|
||||
next.approval_policy.value(),
|
||||
next.config.approvals_reviewer,
|
||||
exec_policy,
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_protocol::error::Result;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -220,9 +221,7 @@ pub struct StdoutStream {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn process_exec_tool_call(
|
||||
params: ExecParams,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_cwd: &AbsolutePathBuf,
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_legacy_landlock: bool,
|
||||
@@ -230,9 +229,7 @@ pub async fn process_exec_tool_call(
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
let exec_req = build_exec_request(
|
||||
params,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
sandbox_cwd,
|
||||
codex_linux_sandbox_exe,
|
||||
use_legacy_landlock,
|
||||
@@ -246,9 +243,7 @@ pub async fn process_exec_tool_call(
|
||||
/// spawned under the requested sandbox policy.
|
||||
pub fn build_exec_request(
|
||||
params: ExecParams,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_cwd: &AbsolutePathBuf,
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_legacy_landlock: bool,
|
||||
@@ -271,8 +266,10 @@ pub fn build_exec_request(
|
||||
} = params;
|
||||
|
||||
let enforce_managed_network = network.is_some();
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let sandbox_type = select_process_exec_tool_sandbox_type(
|
||||
file_system_sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
windows_sandbox_level,
|
||||
enforce_managed_network,
|
||||
@@ -304,9 +301,7 @@ pub fn build_exec_request(
|
||||
let mut exec_req = manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command,
|
||||
policy: sandbox_policy,
|
||||
file_system_policy: file_system_sandbox_policy,
|
||||
network_policy: network_sandbox_policy,
|
||||
permissions: permission_profile,
|
||||
sandbox: sandbox_type,
|
||||
enforce_managed_network,
|
||||
network: network.as_ref(),
|
||||
@@ -366,6 +361,7 @@ pub(crate) async fn execute_exec_request(
|
||||
windows_sandbox_policy_cwd: _,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
permission_profile: _,
|
||||
sandbox_policy,
|
||||
// TODO(mbolin): Use file_system_sandbox_policy instead of sandbox_policy.
|
||||
file_system_sandbox_policy: _,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::PathExt;
|
||||
@@ -346,6 +347,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
|
||||
|
||||
let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;
|
||||
let sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy);
|
||||
let output = process_exec_tool_call(
|
||||
ExecParams {
|
||||
command,
|
||||
@@ -360,9 +362,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
|
||||
justification: None,
|
||||
arg0: None,
|
||||
},
|
||||
&sandbox_policy,
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
@@ -1021,11 +1021,10 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
|
||||
tokio::time::sleep(Duration::from_millis(1_000)).await;
|
||||
cancel_tx.cancel();
|
||||
});
|
||||
let permission_profile = PermissionProfile::Disabled;
|
||||
let result = process_exec_tool_call(
|
||||
params,
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_analytics::GuardianReviewAnalyticsResult;
|
||||
use codex_analytics::GuardianReviewSessionKind;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -843,8 +844,17 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
);
|
||||
guardian_config.developer_instructions = None;
|
||||
guardian_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
guardian_config.permissions.sandbox_policy =
|
||||
Constrained::allow_only(SandboxPolicy::new_read_only_policy());
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
guardian_config.permissions.permission_profile = Constrained::allow_only(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
);
|
||||
guardian_config.permissions.sandbox_policy = Constrained::allow_only(sandbox_policy.clone());
|
||||
guardian_config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(sandbox_policy, guardian_config.cwd.as_path())
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!("guardian review session could not set sandbox policy: {err}")
|
||||
})?;
|
||||
guardian_config.include_apps_instructions = false;
|
||||
guardian_config
|
||||
.mcp_servers
|
||||
|
||||
@@ -9,7 +9,7 @@ use codex_mcp::ToolInfo;
|
||||
use codex_models_manager::test_support::construct_model_info_offline_for_tests;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::ToolsConfigParams;
|
||||
@@ -104,7 +104,7 @@ async fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
tools_config.search_tool = search_tool;
|
||||
|
||||
@@ -16,8 +16,6 @@ use crate::session::session::Session;
|
||||
use codex_config::Constrained;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
@@ -327,21 +325,10 @@ mod agent {
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let consolidation_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&consolidation_sandbox_policy,
|
||||
agent_config.cwd.as_path(),
|
||||
);
|
||||
let consolidation_network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(&consolidation_sandbox_policy);
|
||||
agent_config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(consolidation_sandbox_policy)
|
||||
.set_legacy_sandbox_policy(consolidation_sandbox_policy, agent_config.cwd.as_path())
|
||||
.ok()?;
|
||||
agent_config.permissions.file_system_sandbox_policy =
|
||||
consolidation_file_system_sandbox_policy;
|
||||
agent_config.permissions.network_sandbox_policy = consolidation_network_sandbox_policy;
|
||||
|
||||
agent_config.model = Some(
|
||||
config
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::get_platform_sandbox;
|
||||
use codex_sandboxing::policy_transforms::should_require_platform_sandbox;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn sandbox_tag(
|
||||
policy: &SandboxPolicy,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> &'static str {
|
||||
if matches!(policy, SandboxPolicy::DangerFullAccess) {
|
||||
return "none";
|
||||
}
|
||||
if matches!(policy, SandboxPolicy::ExternalSandbox { .. }) {
|
||||
return "external";
|
||||
permission_profile_sandbox_tag(
|
||||
&PermissionProfile::from_legacy_sandbox_policy(policy),
|
||||
windows_sandbox_level,
|
||||
/*enforce_managed_network*/ false,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn permission_profile_sandbox_tag(
|
||||
profile: &PermissionProfile,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
enforce_managed_network: bool,
|
||||
) -> &'static str {
|
||||
match profile {
|
||||
PermissionProfile::Disabled => return "none",
|
||||
PermissionProfile::External { .. } => return "external",
|
||||
PermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => {
|
||||
let file_system_policy = file_system.to_sandbox_policy();
|
||||
if !should_require_platform_sandbox(
|
||||
&file_system_policy,
|
||||
*network,
|
||||
enforce_managed_network,
|
||||
) {
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg!(target_os = "windows") && matches!(windows_sandbox_level, WindowsSandboxLevel::Elevated)
|
||||
{
|
||||
@@ -23,6 +51,29 @@ pub(crate) fn sandbox_tag(
|
||||
.unwrap_or("none")
|
||||
}
|
||||
|
||||
pub(crate) fn permission_profile_policy_tag(
|
||||
profile: &PermissionProfile,
|
||||
cwd: &Path,
|
||||
) -> &'static str {
|
||||
match profile {
|
||||
PermissionProfile::Disabled => "danger-full-access",
|
||||
PermissionProfile::External { .. } => "external-sandbox",
|
||||
PermissionProfile::Managed { .. } => {
|
||||
let file_system_policy = profile.file_system_sandbox_policy();
|
||||
if file_system_policy.has_full_disk_write_access() {
|
||||
"danger-full-access"
|
||||
} else if file_system_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.is_empty()
|
||||
{
|
||||
"read-only"
|
||||
} else {
|
||||
"workspace-write"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sandbox_tags_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
use super::permission_profile_policy_tag;
|
||||
use super::permission_profile_sandbox_tag;
|
||||
use super::sandbox_tag;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::get_platform_sandbox;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn danger_full_access_is_untagged_even_when_linux_sandbox_defaults_apply() {
|
||||
@@ -37,3 +49,112 @@ fn default_linux_sandbox_uses_platform_sandbox_tag() {
|
||||
.unwrap_or("none");
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_sandbox_tag_distinguishes_disabled_from_external() {
|
||||
assert_eq!(
|
||||
permission_profile_sandbox_tag(
|
||||
&PermissionProfile::Disabled,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
),
|
||||
"none"
|
||||
);
|
||||
assert_eq!(
|
||||
permission_profile_sandbox_tag(
|
||||
&PermissionProfile::External {
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
),
|
||||
"external"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrestricted_managed_profile_with_enabled_network_is_untagged() {
|
||||
let profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_sandbox_tag(
|
||||
&profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
),
|
||||
"none"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_write_managed_profile_with_enabled_network_is_untagged() {
|
||||
let profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_sandbox_tag(
|
||||
&profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
),
|
||||
"none"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_network_enforcement_tags_unrestricted_profiles_as_sandboxed() {
|
||||
let profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
};
|
||||
let expected = get_platform_sandbox(/*windows_sandbox_enabled*/ false)
|
||||
.map(SandboxType::as_metric_tag)
|
||||
.unwrap_or("none");
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_sandbox_tag(
|
||||
&profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ true,
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_policy_tag_reports_closest_legacy_mode() {
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(Path::new("/tmp/codex")).expect("absolute cwd");
|
||||
let writable_root = AbsolutePathBuf::from_absolute_path(Path::new("/tmp/codex/work"))
|
||||
.expect("absolute writable root");
|
||||
let profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy {
|
||||
kind: FileSystemSandboxKind::Restricted,
|
||||
glob_scan_max_depth: None,
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: writable_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}],
|
||||
},
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_policy_tag(&profile, cwd.as_path()),
|
||||
"workspace-write"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
pub use codex_protocol::models::SandboxPermissions;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxExecRequest;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -52,6 +54,7 @@ pub struct ExecRequest {
|
||||
pub windows_sandbox_policy_cwd: AbsolutePathBuf,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub windows_sandbox_private_desktop: bool,
|
||||
pub permission_profile: PermissionProfile,
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
pub network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -71,12 +74,18 @@ impl ExecRequest {
|
||||
sandbox: SandboxType,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
windows_sandbox_private_desktop: bool,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: PermissionProfile,
|
||||
arg0: Option<String>,
|
||||
) -> Self {
|
||||
let windows_sandbox_policy_cwd = cwd.clone();
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
cwd.as_path(),
|
||||
);
|
||||
Self {
|
||||
command,
|
||||
cwd,
|
||||
@@ -89,6 +98,7 @@ impl ExecRequest {
|
||||
windows_sandbox_policy_cwd,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -110,6 +120,7 @@ impl ExecRequest {
|
||||
sandbox,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -141,6 +152,7 @@ impl ExecRequest {
|
||||
windows_sandbox_policy_cwd,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
|
||||
@@ -125,6 +125,7 @@ use codex_rollout::state_db;
|
||||
use codex_rollout_trace::AgentResultTracePayload;
|
||||
use codex_rollout_trace::ThreadStartedTraceMetadata;
|
||||
use codex_rollout_trace::ThreadTraceContext;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
|
||||
use codex_shell_command::parse_command::parse_command;
|
||||
use codex_terminal_detection::user_agent;
|
||||
@@ -604,6 +605,7 @@ impl Codex {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -2518,8 +2520,8 @@ impl Session {
|
||||
}
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
developer_sections.push(
|
||||
PermissionsInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
PermissionsInstructions::from_permission_profile(
|
||||
&turn_context.permission_profile,
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.config.approvals_reviewer,
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(super) async fn spawn_review_thread(
|
||||
)),
|
||||
web_search_mode: Some(review_web_search_mode),
|
||||
session_source: parent_turn_context.session_source.clone(),
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.get(),
|
||||
permission_profile: &parent_turn_context.permission_profile,
|
||||
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
@@ -97,8 +97,9 @@ pub(super) async fn spawn_review_thread(
|
||||
&session_source,
|
||||
review_turn_id.clone(),
|
||||
parent_turn_context.cwd.clone(),
|
||||
parent_turn_context.sandbox_policy.get(),
|
||||
&parent_turn_context.permission_profile,
|
||||
parent_turn_context.windows_sandbox_level,
|
||||
parent_turn_context.network.is_some(),
|
||||
));
|
||||
|
||||
let review_turn_context = TurnContext {
|
||||
@@ -127,6 +128,7 @@ pub(super) async fn spawn_review_thread(
|
||||
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
||||
personality: parent_turn_context.personality,
|
||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||
permission_profile: parent_turn_context.permission_profile(),
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: parent_turn_context.network_sandbox_policy,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::*;
|
||||
use crate::config::ConstraintError;
|
||||
use crate::goals::GoalRuntimeState;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
@@ -57,9 +56,13 @@ pub(crate) struct SessionConfiguration {
|
||||
/// When to escalate for approval for execution
|
||||
pub(super) approval_policy: Constrained<AskForApproval>,
|
||||
pub(super) approvals_reviewer: ApprovalsReviewer,
|
||||
/// How to sandbox commands executed in the system
|
||||
/// Canonical permission profile for the session.
|
||||
pub(super) permission_profile: Constrained<PermissionProfile>,
|
||||
/// Legacy sandbox projection retained while lower-level callers migrate.
|
||||
pub(super) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Filesystem sandbox projection of `permission_profile`.
|
||||
pub(super) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
/// Network sandbox projection of `permission_profile`.
|
||||
pub(super) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
@@ -95,11 +98,16 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
pub(super) fn permission_profile(&self) -> PermissionProfile {
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(self.sandbox_policy.get()),
|
||||
&self.file_system_sandbox_policy,
|
||||
self.network_sandbox_policy,
|
||||
)
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.sandbox_policy.get().clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.file_system_sandbox_policy.clone()
|
||||
}
|
||||
|
||||
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
@@ -109,7 +117,7 @@ impl SessionConfiguration {
|
||||
service_tier: self.service_tier,
|
||||
approval_policy: self.approval_policy.value(),
|
||||
approvals_reviewer: self.approvals_reviewer,
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
sandbox_policy: self.sandbox_policy(),
|
||||
permission_profile: self.permission_profile(),
|
||||
cwd: self.cwd.clone(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
@@ -171,23 +179,10 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
let sandbox_policy = permission_profile
|
||||
.to_legacy_sandbox_policy(&next_configuration.cwd)
|
||||
.map_err(|err| ConstraintError::InvalidValue {
|
||||
field_name: "permission_profile",
|
||||
candidate: format!("{permission_profile:?}"),
|
||||
allowed: format!(
|
||||
"permission profiles that can be represented by the active sandbox constraints: {err}"
|
||||
),
|
||||
requirement_source: codex_config::RequirementSource::Unknown,
|
||||
})?;
|
||||
next_configuration.sandbox_policy.set(sandbox_policy)?;
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
file_system_sandbox_policy
|
||||
.preserve_deny_read_restrictions_from(&self.file_system_sandbox_policy);
|
||||
next_configuration.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
next_configuration.network_sandbox_policy = network_sandbox_policy;
|
||||
next_configuration.set_permission_profile_projection(
|
||||
permission_profile,
|
||||
Some(&self.file_system_sandbox_policy),
|
||||
)?;
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
next_configuration.sandbox_policy.set(sandbox_policy)?;
|
||||
next_configuration.file_system_sandbox_policy =
|
||||
@@ -198,6 +193,15 @@ impl SessionConfiguration {
|
||||
);
|
||||
next_configuration.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
} else if cwd_changed && file_system_policy_matches_legacy {
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using the legacy bridge.
|
||||
@@ -206,6 +210,15 @@ impl SessionConfiguration {
|
||||
next_configuration.sandbox_policy.get(),
|
||||
&next_configuration.cwd,
|
||||
);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
if let Some(app_server_client_name) = updates.app_server_client_name.clone() {
|
||||
next_configuration.app_server_client_name = Some(app_server_client_name);
|
||||
@@ -215,6 +228,37 @@ impl SessionConfiguration {
|
||||
}
|
||||
Ok(next_configuration)
|
||||
}
|
||||
|
||||
fn set_permission_profile_projection(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
preserve_deny_reads_from: Option<&FileSystemSandboxPolicy>,
|
||||
) -> ConstraintResult<()> {
|
||||
let enforcement = permission_profile.enforcement();
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
if let Some(existing_file_system_policy) = preserve_deny_reads_from {
|
||||
file_system_sandbox_policy
|
||||
.preserve_deny_read_restrictions_from(existing_file_system_policy);
|
||||
}
|
||||
let effective_permission_profile =
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
enforcement,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
self.cwd.as_path(),
|
||||
);
|
||||
self.permission_profile.set(effective_permission_profile)?;
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -1493,6 +1494,11 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
|
||||
config.permissions.sandbox_policy = codex_config::Constrained::allow_any(sandbox_policy);
|
||||
config.permissions.file_system_sandbox_policy = FileSystemSandboxPolicy::external_sandbox();
|
||||
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
config.permissions.permission_profile =
|
||||
codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.network_sandbox_policy,
|
||||
));
|
||||
});
|
||||
|
||||
let test = builder.build(&server).await?;
|
||||
@@ -2246,6 +2252,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -2350,6 +2357,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -2799,6 +2807,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -2922,6 +2931,52 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configuration_apply_permission_profile_accepts_direct_write_roots() {
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
let cwd = tempfile::tempdir().expect("create cwd");
|
||||
session_configuration.cwd = cwd.path().abs();
|
||||
let external_write_dir = tempfile::tempdir().expect("create external write root");
|
||||
let external_write_path = AbsolutePathBuf::from_absolute_path(
|
||||
codex_utils_absolute_path::canonicalize_preserving_symlinks(external_write_dir.path())
|
||||
.expect("canonical temp dir"),
|
||||
)
|
||||
.expect("canonical temp dir should be absolute");
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: external_write_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
permission_profile: Some(permission_profile.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("permission profile update should accept direct runtime permissions");
|
||||
|
||||
assert_eq!(updated.permission_profile(), permission_profile);
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy(),
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
assert_eq!(
|
||||
updated.sandbox_policy(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![external_write_path],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, ignore)]
|
||||
#[tokio::test]
|
||||
async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {
|
||||
@@ -3114,6 +3169,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -3220,6 +3276,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -3434,6 +3491,7 @@ async fn make_session_with_config_and_rx(
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
@@ -4583,6 +4641,7 @@ where
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::*;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
|
||||
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
|
||||
@@ -73,6 +72,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) collaboration_mode: CollaborationMode,
|
||||
pub(crate) personality: Option<Personality>,
|
||||
pub(crate) approval_policy: Constrained<AskForApproval>,
|
||||
pub(crate) permission_profile: PermissionProfile,
|
||||
pub(crate) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
pub(crate) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
pub(crate) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -96,11 +96,7 @@ pub(crate) struct TurnContext {
|
||||
}
|
||||
impl TurnContext {
|
||||
pub(crate) fn permission_profile(&self) -> PermissionProfile {
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&self.sandbox_policy),
|
||||
&self.file_system_sandbox_policy,
|
||||
self.network_sandbox_policy,
|
||||
)
|
||||
self.permission_profile.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn model_context_window(&self) -> Option<i64> {
|
||||
@@ -170,7 +166,7 @@ impl TurnContext {
|
||||
),
|
||||
web_search_mode: self.tools_config.web_search_mode,
|
||||
session_source: self.session_source.clone(),
|
||||
sandbox_policy: self.sandbox_policy.get(),
|
||||
permission_profile: &self.permission_profile,
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone())
|
||||
@@ -213,6 +209,7 @@ impl TurnContext {
|
||||
collaboration_mode,
|
||||
personality: self.personality,
|
||||
approval_policy: self.approval_policy.clone(),
|
||||
permission_profile: self.permission_profile.clone(),
|
||||
sandbox_policy: self.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
@@ -258,7 +255,7 @@ impl TurnContext {
|
||||
additional_permissions.as_ref(),
|
||||
);
|
||||
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&self.sandbox_policy),
|
||||
self.permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
@@ -367,6 +364,13 @@ impl Session {
|
||||
per_turn_config.service_tier = session_configuration.service_tier;
|
||||
per_turn_config.personality = session_configuration.personality;
|
||||
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
|
||||
per_turn_config.permissions.permission_profile =
|
||||
session_configuration.permission_profile.clone();
|
||||
per_turn_config.permissions.sandbox_policy = session_configuration.sandbox_policy.clone();
|
||||
per_turn_config.permissions.file_system_sandbox_policy =
|
||||
session_configuration.file_system_sandbox_policy.clone();
|
||||
per_turn_config.permissions.network_sandbox_policy =
|
||||
session_configuration.network_sandbox_policy;
|
||||
let resolved_web_search_mode = resolve_web_search_mode_for_turn(
|
||||
&per_turn_config.web_search_mode,
|
||||
session_configuration.sandbox_policy.get(),
|
||||
@@ -429,7 +433,7 @@ impl Session {
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode: Some(per_turn_config.web_search_mode.value()),
|
||||
session_source: session_source.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get(),
|
||||
permission_profile: &session_configuration.permission_profile(),
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
@@ -455,8 +459,9 @@ impl Session {
|
||||
&session_source,
|
||||
sub_id.clone(),
|
||||
cwd.clone(),
|
||||
session_configuration.sandbox_policy.get(),
|
||||
&session_configuration.permission_profile(),
|
||||
session_configuration.windows_sandbox_level,
|
||||
network.is_some(),
|
||||
));
|
||||
let (current_date, timezone) = local_time_context();
|
||||
TurnContext {
|
||||
@@ -483,6 +488,7 @@ impl Session {
|
||||
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
||||
personality: session_configuration.personality,
|
||||
approval_policy: session_configuration.approval_policy.clone(),
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: session_configuration.network_sandbox_policy,
|
||||
|
||||
@@ -33,10 +33,9 @@ use codex_shell_command::parse_command::parse_command;
|
||||
use super::SessionTask;
|
||||
use super::SessionTaskContext;
|
||||
use crate::session::session::Session;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
|
||||
const USER_SHELL_TIMEOUT_MS: u64 = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
@@ -157,7 +156,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
)
|
||||
.await;
|
||||
|
||||
let sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
let permission_profile = PermissionProfile::Disabled;
|
||||
let exec_env = ExecRequest {
|
||||
command: exec_command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -177,9 +176,10 @@ pub(crate) async fn execute_user_shell_command(
|
||||
.config
|
||||
.permissions
|
||||
.windows_sandbox_private_desktop,
|
||||
sandbox_policy: sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
permission_profile: permission_profile.clone(),
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
file_system_sandbox_policy: permission_profile.file_system_sandbox_policy(),
|
||||
network_sandbox_policy: permission_profile.network_sandbox_policy(),
|
||||
windows_sandbox_filesystem_overrides: None,
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
@@ -269,13 +269,10 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
config.cwd = turn.cwd.clone();
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.set_permission_profile(turn.permission_profile(), turn.cwd.as_path())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("sandbox_policy is invalid: {err}"))
|
||||
FunctionCallError::RespondToModel(format!("permission_profile is invalid: {err}"))
|
||||
})?;
|
||||
config.permissions.file_system_sandbox_policy = turn.file_system_sandbox_policy.clone();
|
||||
config.permissions.network_sandbox_policy = turn.network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -2103,6 +2105,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
let expected_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
|
||||
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
|
||||
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&expected_sandbox),
|
||||
&expected_file_system_sandbox_policy,
|
||||
expected_network_sandbox_policy,
|
||||
);
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy should be set");
|
||||
@@ -2111,6 +2118,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
.expect("sandbox policy should be set");
|
||||
turn.file_system_sandbox_policy = expected_file_system_sandbox_policy.clone();
|
||||
turn.network_sandbox_policy = expected_network_sandbox_policy;
|
||||
turn.permission_profile = expected_permission_profile.clone();
|
||||
assert_ne!(
|
||||
expected_sandbox,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
@@ -2149,6 +2157,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
.await;
|
||||
assert_eq!(snapshot.sandbox_policy, expected_sandbox);
|
||||
assert_eq!(snapshot.approval_policy, AskForApproval::OnRequest);
|
||||
assert_eq!(snapshot.permission_profile, expected_permission_profile);
|
||||
let child_thread = manager
|
||||
.get_thread(agent_id)
|
||||
.await
|
||||
@@ -2162,6 +2171,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
child_turn.network_sandbox_policy,
|
||||
expected_network_sandbox_policy
|
||||
);
|
||||
assert_eq!(child_turn.permission_profile(), expected_permission_profile);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -3622,11 +3632,17 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, &turn.cwd);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
turn.sandbox_policy
|
||||
.set(sandbox_policy)
|
||||
.expect("sandbox policy set");
|
||||
turn.file_system_sandbox_policy = file_system_sandbox_policy.clone();
|
||||
turn.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
turn.network_sandbox_policy = network_sandbox_policy;
|
||||
turn.permission_profile = permission_profile.clone();
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy set");
|
||||
@@ -3650,11 +3666,8 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
.expect("approval policy set");
|
||||
expected
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.expect("sandbox policy set");
|
||||
expected.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
expected.permissions.network_sandbox_policy = network_sandbox_policy;
|
||||
.set_permission_profile(permission_profile, turn.cwd.as_path())
|
||||
.expect("permission profile set");
|
||||
assert_eq!(config, expected);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,9 +206,7 @@ impl ToolOrchestrator {
|
||||
let use_legacy_landlock = turn_ctx.features.use_legacy_landlock();
|
||||
let initial_attempt = SandboxAttempt {
|
||||
sandbox: initial_sandbox,
|
||||
policy: &turn_ctx.sandbox_policy,
|
||||
file_system_policy: &turn_ctx.file_system_sandbox_policy,
|
||||
network_policy: turn_ctx.network_sandbox_policy,
|
||||
permissions: &turn_ctx.permission_profile,
|
||||
enforce_managed_network: managed_network_active,
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd: &turn_ctx.cwd,
|
||||
@@ -325,9 +323,7 @@ impl ToolOrchestrator {
|
||||
|
||||
let escalated_attempt = SandboxAttempt {
|
||||
sandbox: SandboxType::None,
|
||||
policy: &turn_ctx.sandbox_policy,
|
||||
file_system_policy: &turn_ctx.file_system_sandbox_policy,
|
||||
network_policy: turn_ctx.network_sandbox_policy,
|
||||
permissions: &turn_ctx.permission_profile,
|
||||
enforce_managed_network: managed_network_active,
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd: &turn_ctx.cwd,
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::hook_runtime::record_additional_contexts;
|
||||
use crate::hook_runtime::run_post_tool_use_hooks;
|
||||
use crate::hook_runtime::run_pre_tool_use_hooks;
|
||||
use crate::memories::usage::emit_metric_for_tool_read;
|
||||
use crate::sandbox_tags::sandbox_tag;
|
||||
use crate::sandbox_tags::permission_profile_policy_tag;
|
||||
use crate::sandbox_tags::permission_profile_sandbox_tag;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -26,7 +27,6 @@ use codex_hooks::HookToolInputLocalShell;
|
||||
use codex_hooks::HookToolKind;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_tools::ConfiguredToolSpec;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -275,14 +275,18 @@ impl ToolRegistry {
|
||||
let metric_tags = [
|
||||
(
|
||||
"sandbox",
|
||||
sandbox_tag(
|
||||
&invocation.turn.sandbox_policy,
|
||||
permission_profile_sandbox_tag(
|
||||
&invocation.turn.permission_profile,
|
||||
invocation.turn.windows_sandbox_level,
|
||||
invocation.turn.network.is_some(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"sandbox_policy",
|
||||
sandbox_policy_tag(&invocation.turn.sandbox_policy),
|
||||
permission_profile_policy_tag(
|
||||
&invocation.turn.permission_profile,
|
||||
invocation.turn.cwd.as_path(),
|
||||
),
|
||||
),
|
||||
];
|
||||
let (mcp_server, mcp_server_origin) = match &invocation.payload {
|
||||
@@ -580,15 +584,6 @@ fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &ToolName) ->
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_policy_tag(policy: &SandboxPolicy) -> &'static str {
|
||||
match policy {
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only",
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write",
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access",
|
||||
SandboxPolicy::ExternalSandbox { .. } => "external-sandbox",
|
||||
}
|
||||
}
|
||||
|
||||
// Hooks use a separate wire-facing input type so hook payload JSON stays stable
|
||||
// and decoupled from core's internal tool runtime representation.
|
||||
impl From<&ToolPayload> for HookToolInput {
|
||||
@@ -673,9 +668,17 @@ async fn dispatch_after_tool_use_hook(
|
||||
success: dispatch.success,
|
||||
duration_ms: u64::try_from(dispatch.duration.as_millis()).unwrap_or(u64::MAX),
|
||||
mutating: dispatch.mutating,
|
||||
sandbox: sandbox_tag(&turn.sandbox_policy, turn.windows_sandbox_level)
|
||||
.to_string(),
|
||||
sandbox_policy: sandbox_policy_tag(&turn.sandbox_policy).to_string(),
|
||||
sandbox: permission_profile_sandbox_tag(
|
||||
&turn.permission_profile,
|
||||
turn.windows_sandbox_level,
|
||||
turn.network.is_some(),
|
||||
)
|
||||
.to_string(),
|
||||
sandbox_policy: permission_profile_policy_tag(
|
||||
&turn.permission_profile,
|
||||
turn.cwd.as_path(),
|
||||
)
|
||||
.to_string(),
|
||||
output_preview: dispatch.output_preview.clone(),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,8 +24,6 @@ use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -35,8 +33,7 @@ use codex_protocol::protocol::FileChange;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
|
||||
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
|
||||
use codex_sandboxing::policy_transforms::effective_permission_profile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use futures::future::BoxFuture;
|
||||
use std::path::PathBuf;
|
||||
@@ -80,19 +77,8 @@ impl ApplyPatchRuntime {
|
||||
return None;
|
||||
}
|
||||
|
||||
let file_system_policy = effective_file_system_sandbox_policy(
|
||||
attempt.file_system_policy,
|
||||
req.additional_permissions.as_ref(),
|
||||
);
|
||||
let network_policy = effective_network_sandbox_policy(
|
||||
attempt.network_policy,
|
||||
req.additional_permissions.as_ref(),
|
||||
);
|
||||
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(attempt.policy),
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
);
|
||||
let permissions =
|
||||
effective_permission_profile(attempt.permissions, req.additional_permissions.as_ref());
|
||||
Some(FileSystemSandboxContext {
|
||||
permissions,
|
||||
cwd: Some(attempt.sandbox_cwd.clone()),
|
||||
|
||||
@@ -138,12 +138,14 @@ fn file_system_sandbox_context_uses_active_attempt() {
|
||||
};
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let manager = SandboxManager::new();
|
||||
let attempt = SandboxAttempt {
|
||||
sandbox: SandboxType::MacosSeatbelt,
|
||||
policy: &sandbox_policy,
|
||||
file_system_policy: &file_system_policy,
|
||||
network_policy: NetworkSandboxPolicy::Restricted,
|
||||
permissions: &permissions,
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &path,
|
||||
@@ -190,14 +192,11 @@ fn no_sandbox_attempt_has_no_file_system_context() {
|
||||
additional_permissions: None,
|
||||
permissions_preapproved: false,
|
||||
};
|
||||
let sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
let file_system_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let permissions = PermissionProfile::Disabled;
|
||||
let manager = SandboxManager::new();
|
||||
let attempt = SandboxAttempt {
|
||||
sandbox: SandboxType::None,
|
||||
policy: &sandbox_policy,
|
||||
file_system_policy: &file_system_policy,
|
||||
network_policy: NetworkSandboxPolicy::Enabled,
|
||||
permissions: &permissions,
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &path,
|
||||
|
||||
@@ -19,9 +19,7 @@ use codex_network_proxy::PROXY_ENV_KEYS;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -105,14 +103,11 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
|
||||
expiration: ExecExpiration::DefaultTimeout,
|
||||
capture_policy: ExecCapturePolicy::ShellTool,
|
||||
};
|
||||
let sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
let file_system_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let permissions = PermissionProfile::Disabled;
|
||||
let manager = SandboxManager::new();
|
||||
let attempt = SandboxAttempt {
|
||||
sandbox: SandboxType::None,
|
||||
policy: &sandbox_policy,
|
||||
file_system_policy: &file_system_policy,
|
||||
network_policy: NetworkSandboxPolicy::Enabled,
|
||||
permissions: &permissions,
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &cwd,
|
||||
|
||||
@@ -142,6 +142,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
windows_sandbox_policy_cwd: sandbox_policy_cwd,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: _windows_sandbox_private_desktop,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -159,6 +160,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
let command_executor = CoreShellCommandExecutor {
|
||||
command,
|
||||
cwd: sandbox_cwd,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
@@ -257,6 +259,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
let command_executor = CoreShellCommandExecutor {
|
||||
command: exec_request.command.clone(),
|
||||
cwd: exec_request.cwd.clone(),
|
||||
permission_profile: exec_request.permission_profile.clone(),
|
||||
sandbox_policy: exec_request.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: exec_request.network_sandbox_policy,
|
||||
@@ -746,6 +749,7 @@ fn commands_for_intercepted_exec_policy(
|
||||
struct CoreShellCommandExecutor {
|
||||
command: Vec<String>,
|
||||
cwd: AbsolutePathBuf,
|
||||
permission_profile: PermissionProfile,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -763,9 +767,7 @@ struct PrepareSandboxedExecParams<'a> {
|
||||
command: Vec<String>,
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
sandbox_policy: &'a SandboxPolicy,
|
||||
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &'a PermissionProfile,
|
||||
additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
}
|
||||
|
||||
@@ -801,6 +803,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
windows_sandbox_policy_cwd: self.sandbox_policy_cwd.clone(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: false,
|
||||
permission_profile: self.permission_profile.clone(),
|
||||
sandbox_policy: self.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
@@ -849,9 +852,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
sandbox_policy: &self.sandbox_policy,
|
||||
file_system_sandbox_policy: &self.file_system_sandbox_policy,
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
permission_profile: &self.permission_profile,
|
||||
additional_permissions: None,
|
||||
})?
|
||||
}
|
||||
@@ -863,9 +864,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
sandbox_policy: &self.sandbox_policy,
|
||||
file_system_sandbox_policy: &self.file_system_sandbox_policy,
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
permission_profile: &self.permission_profile,
|
||||
additional_permissions: Some(permission_profile),
|
||||
})?
|
||||
}
|
||||
@@ -873,15 +872,11 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
permissions,
|
||||
)) => {
|
||||
// Use a fully specified permission profile instead of merging into the turn policy.
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permissions.permission_profile.to_runtime_permissions();
|
||||
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
sandbox_policy: &permissions.sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile: &permissions.permission_profile,
|
||||
additional_permissions: None,
|
||||
})?
|
||||
}
|
||||
@@ -901,17 +896,17 @@ impl CoreShellCommandExecutor {
|
||||
command,
|
||||
workdir,
|
||||
env,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
additional_permissions,
|
||||
} = params;
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let (program, args) = command
|
||||
.split_first()
|
||||
.ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?;
|
||||
let sandbox_manager = SandboxManager::new();
|
||||
let sandbox = sandbox_manager.select_initial(
|
||||
file_system_sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
SandboxablePreference::Auto,
|
||||
self.windows_sandbox_level,
|
||||
@@ -930,9 +925,7 @@ impl CoreShellCommandExecutor {
|
||||
};
|
||||
let exec_request = sandbox_manager.transform(SandboxTransformRequest {
|
||||
command,
|
||||
policy: sandbox_policy,
|
||||
file_system_policy: file_system_sandbox_policy,
|
||||
network_policy: network_sandbox_policy,
|
||||
permissions: permission_profile,
|
||||
sandbox,
|
||||
enforce_managed_network: self.network.is_some(),
|
||||
network: self.network.as_ref(),
|
||||
|
||||
@@ -17,7 +17,6 @@ use codex_protocol::approvals::NetworkApprovalContext;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
#[cfg(test)]
|
||||
@@ -368,9 +367,7 @@ pub(crate) trait ToolRuntime<Req, Out>: Approvable<Req> + Sandboxable {
|
||||
|
||||
pub(crate) struct SandboxAttempt<'a> {
|
||||
pub sandbox: SandboxType,
|
||||
pub policy: &'a codex_protocol::protocol::SandboxPolicy,
|
||||
pub file_system_policy: &'a FileSystemSandboxPolicy,
|
||||
pub network_policy: NetworkSandboxPolicy,
|
||||
pub permissions: &'a codex_protocol::models::PermissionProfile,
|
||||
pub enforce_managed_network: bool,
|
||||
pub(crate) manager: &'a SandboxManager,
|
||||
pub(crate) sandbox_cwd: &'a AbsolutePathBuf,
|
||||
@@ -390,9 +387,7 @@ impl<'a> SandboxAttempt<'a> {
|
||||
self.manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command,
|
||||
policy: self.policy,
|
||||
file_system_policy: self.file_system_policy,
|
||||
network_policy: self.network_policy,
|
||||
permissions: self.permissions,
|
||||
sandbox: self.sandbox,
|
||||
enforce_managed_network: self.enforce_managed_network,
|
||||
network,
|
||||
|
||||
@@ -12,9 +12,9 @@ use codex_models_manager::bundled_models_response;
|
||||
use codex_models_manager::model_info::with_config_overrides;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_tools::AdditionalProperties;
|
||||
use codex_tools::ConfiguredToolSpec;
|
||||
@@ -230,7 +230,7 @@ async fn multi_agent_v2_tools_config() -> ToolsConfig {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_max_concurrent_threads_per_session(Some(4))
|
||||
@@ -309,7 +309,7 @@ async fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies()
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: &PermissionProfile::workspace_write(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
||||
});
|
||||
|
||||
@@ -335,7 +335,7 @@ async fn get_memory_requires_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -367,7 +367,7 @@ async fn assert_model_tools(
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode,
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let router = ToolRouter::from_config(
|
||||
@@ -650,7 +650,7 @@ async fn test_build_specs_default_shell_present() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -685,7 +685,7 @@ async fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let user_shell = Shell {
|
||||
@@ -809,7 +809,7 @@ async fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
@@ -845,7 +845,7 @@ async fn search_tool_description_handles_no_enabled_mcp_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -879,7 +879,7 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -930,7 +930,7 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1015,7 +1015,7 @@ async fn direct_mcp_tools_register_namespaced_handlers() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1052,7 +1052,7 @@ async fn unavailable_mcp_tools_are_exposed_as_dummy_function_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1101,7 +1101,7 @@ async fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1164,7 +1164,7 @@ async fn test_mcp_tool_preserves_integer_schema() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1226,7 +1226,7 @@ async fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1290,7 +1290,7 @@ async fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1359,7 +1359,7 @@ async fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
|
||||
@@ -8,13 +8,13 @@ use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::sandbox_tags::sandbox_tag;
|
||||
use crate::sandbox_tags::permission_profile_sandbox_tag;
|
||||
use codex_git_utils::get_git_remote_urls_assume_git_repo;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_git_utils::get_has_changes;
|
||||
use codex_git_utils::get_head_commit_hash;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
@@ -163,11 +163,19 @@ impl TurnMetadataState {
|
||||
session_source: &SessionSource,
|
||||
turn_id: String,
|
||||
cwd: AbsolutePathBuf,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
enforce_managed_network: bool,
|
||||
) -> Self {
|
||||
let repo_root = get_git_repo_root(&cwd).map(|root| root.to_string_lossy().into_owned());
|
||||
let sandbox = Some(sandbox_tag(sandbox_policy, windows_sandbox_level).to_string());
|
||||
let sandbox = Some(
|
||||
permission_profile_sandbox_tag(
|
||||
permission_profile,
|
||||
windows_sandbox_level,
|
||||
enforce_managed_network,
|
||||
)
|
||||
.to_string(),
|
||||
);
|
||||
let base_metadata = build_turn_metadata_bag(
|
||||
Some(session_id),
|
||||
session_source.thread_source_name(),
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::*;
|
||||
|
||||
use crate::sandbox_tags::sandbox_tag;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use core_test_support::PathBufExt;
|
||||
@@ -70,14 +73,16 @@ fn turn_metadata_state_uses_platform_sandbox_tag() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let cwd = temp_dir.path().abs();
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
|
||||
let state = TurnMetadataState::new(
|
||||
"session-a".to_string(),
|
||||
&SessionSource::Exec,
|
||||
"turn-a".to_string(),
|
||||
cwd,
|
||||
&sandbox_policy,
|
||||
&permission_profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
);
|
||||
|
||||
let header = state.current_header_value().expect("header");
|
||||
@@ -97,7 +102,7 @@ fn turn_metadata_state_uses_platform_sandbox_tag() {
|
||||
fn turn_metadata_state_classifies_subagent_thread_source() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let cwd = temp_dir.path().abs();
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
let session_source = SessionSource::SubAgent(SubAgentSource::Review);
|
||||
|
||||
let state = TurnMetadataState::new(
|
||||
@@ -105,8 +110,9 @@ fn turn_metadata_state_classifies_subagent_thread_source() {
|
||||
&session_source,
|
||||
"turn-a".to_string(),
|
||||
cwd,
|
||||
&sandbox_policy,
|
||||
&permission_profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
);
|
||||
|
||||
let header = state.current_header_value().expect("header");
|
||||
@@ -120,15 +126,16 @@ fn turn_metadata_state_classifies_subagent_thread_source() {
|
||||
fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let cwd = temp_dir.path().abs();
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
|
||||
let state = TurnMetadataState::new(
|
||||
"session-a".to_string(),
|
||||
&SessionSource::Exec,
|
||||
"turn-a".to_string(),
|
||||
cwd,
|
||||
&sandbox_policy,
|
||||
&permission_profile,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
/*enforce_managed_network*/ false,
|
||||
);
|
||||
state.set_responsesapi_client_metadata(HashMap::from([
|
||||
("fiber_run_id".to_string(), "fiber-123".to_string()),
|
||||
|
||||
@@ -55,9 +55,7 @@ fn test_exec_request(
|
||||
env: HashMap<String, String>,
|
||||
) -> ExecRequest {
|
||||
let windows_sandbox_private_desktop = false;
|
||||
let sandbox_policy = turn.sandbox_policy.get().clone();
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy.clone();
|
||||
let network_sandbox_policy = turn.network_sandbox_policy;
|
||||
let permission_profile = turn.permission_profile();
|
||||
let network = None;
|
||||
let arg0 = None;
|
||||
ExecRequest::new(
|
||||
@@ -70,9 +68,7 @@ fn test_exec_request(
|
||||
SandboxType::None,
|
||||
turn.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
arg0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,16 @@ fn exec_server_params_use_env_policy_overlay_contract() {
|
||||
.expect("current dir")
|
||||
.try_into()
|
||||
.expect("absolute path");
|
||||
let sandbox_policy = codex_protocol::protocol::SandboxPolicy::DangerFullAccess;
|
||||
let file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let network_sandbox_policy = codex_protocol::permissions::NetworkSandboxPolicy::Restricted;
|
||||
let permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let request = ExecRequest {
|
||||
command: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()],
|
||||
cwd: cwd.clone(),
|
||||
@@ -99,11 +109,10 @@ fn exec_server_params_use_env_policy_overlay_contract() {
|
||||
windows_sandbox_policy_cwd: cwd,
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
|
||||
windows_sandbox_private_desktop: false,
|
||||
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
file_system_sandbox_policy: codex_protocol::permissions::FileSystemSandboxPolicy::from(
|
||||
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
),
|
||||
network_sandbox_policy: codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
permission_profile,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
windows_sandbox_filesystem_overrides: None,
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
@@ -2880,7 +2880,11 @@ allow_local_binding = true
|
||||
};
|
||||
let mut builder = test_codex().with_home(home).with_config(move |config| {
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
let cwd = config.cwd.clone();
|
||||
config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path())
|
||||
.expect("test setup should allow sandbox policy");
|
||||
let layers = config
|
||||
.config_layer_stack
|
||||
.get_layers(
|
||||
|
||||
@@ -8,8 +8,7 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::get_platform_sandbox;
|
||||
@@ -52,12 +51,11 @@ where
|
||||
};
|
||||
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&policy);
|
||||
|
||||
process_exec_tool_call(
|
||||
params,
|
||||
&policy,
|
||||
&FileSystemSandboxPolicy::from(&policy),
|
||||
NetworkSandboxPolicy::from(&policy),
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
|
||||
@@ -672,12 +672,24 @@ async fn steered_user_input_follows_compact_when_only_the_steer_needs_follow_up(
|
||||
async fn steered_user_input_waits_when_tool_output_triggers_compact_before_next_request() {
|
||||
let (gate_first_completed_tx, gate_first_completed_rx) = oneshot::channel();
|
||||
|
||||
let large_output_command = if cfg!(windows) {
|
||||
"[Console]::Out.Write([string]::new([char]'0', 4000))"
|
||||
} else {
|
||||
"printf '%04000d' 0"
|
||||
};
|
||||
let large_output_args = json!({
|
||||
"command": large_output_command,
|
||||
"login": false,
|
||||
"timeout_ms": 2000,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let first_chunks = vec![
|
||||
chunk(ev_response_created("resp-1")),
|
||||
chunk(ev_function_call(
|
||||
"call-1",
|
||||
"shell_command",
|
||||
r#"{"command":"printf '%04000d' 0","login":false,"timeout_ms":2000}"#,
|
||||
&large_output_args,
|
||||
)),
|
||||
gated_chunk(
|
||||
gate_first_completed_rx,
|
||||
|
||||
@@ -550,7 +550,10 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(sandbox_policy_for_config, config.cwd.as_path())
|
||||
.expect("test sandbox policy should be allowed");
|
||||
config.config_layer_stack = ConfigLayerStack::default();
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
use codex_sandboxing::SandboxExecRequest;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
@@ -60,31 +60,27 @@ impl FileSystemSandboxRunner {
|
||||
add_helper_runtime_permissions(&mut file_system_policy, &helper_read_roots, cwd.as_path());
|
||||
normalize_file_system_policy_root_aliases(&mut file_system_policy);
|
||||
let network_policy = NetworkSandboxPolicy::Restricted;
|
||||
let sandbox_policy =
|
||||
compatibility_sandbox_policy(&file_system_policy, network_policy, cwd.as_path());
|
||||
let command = self.sandbox_exec_request(
|
||||
&sandbox_policy,
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
sandbox.permissions.enforcement(),
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
&cwd,
|
||||
sandbox,
|
||||
)?;
|
||||
);
|
||||
let command = self.sandbox_exec_request(&permission_profile, &cwd, sandbox)?;
|
||||
let request_json = serde_json::to_vec(&request).map_err(json_error)?;
|
||||
run_command(command, request_json).await
|
||||
}
|
||||
|
||||
fn sandbox_exec_request(
|
||||
&self,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
file_system_policy: &FileSystemSandboxPolicy,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
cwd: &AbsolutePathBuf,
|
||||
sandbox_context: &FileSystemSandboxContext,
|
||||
) -> Result<SandboxExecRequest, JSONRPCErrorError> {
|
||||
let helper = &self.runtime_paths.codex_self_exe;
|
||||
let sandbox_manager = SandboxManager::new();
|
||||
let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
|
||||
let sandbox = sandbox_manager.select_initial(
|
||||
file_system_policy,
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
SandboxablePreference::Auto,
|
||||
sandbox_context.windows_sandbox_level,
|
||||
@@ -100,9 +96,7 @@ impl FileSystemSandboxRunner {
|
||||
sandbox_manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command,
|
||||
policy: sandbox_policy,
|
||||
file_system_policy,
|
||||
network_policy,
|
||||
permissions: permission_profile,
|
||||
sandbox,
|
||||
enforce_managed_network: false,
|
||||
network: None,
|
||||
@@ -179,36 +173,6 @@ fn add_helper_runtime_permissions(
|
||||
}
|
||||
}
|
||||
|
||||
fn compatibility_sandbox_policy(
|
||||
file_system_policy: &FileSystemSandboxPolicy,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
cwd: &std::path::Path,
|
||||
) -> SandboxPolicy {
|
||||
file_system_policy
|
||||
.to_legacy_sandbox_policy(network_policy, cwd)
|
||||
.unwrap_or_else(|_| compatibility_workspace_write_policy(file_system_policy, cwd))
|
||||
}
|
||||
|
||||
fn compatibility_workspace_write_policy(
|
||||
file_system_policy: &FileSystemSandboxPolicy,
|
||||
cwd: &std::path::Path,
|
||||
) -> SandboxPolicy {
|
||||
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
||||
let writable_roots = file_system_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.into_iter()
|
||||
.map(|root| root.root)
|
||||
.filter(|root| cwd_abs.as_ref() != Some(root))
|
||||
.collect();
|
||||
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_file_system_policy_root_aliases(file_system_policy: &mut FileSystemSandboxPolicy) {
|
||||
for entry in &mut file_system_policy.entries {
|
||||
if let FileSystemPath::Path { path } = &mut entry.path {
|
||||
@@ -347,7 +311,6 @@ mod tests {
|
||||
|
||||
use super::FileSystemSandboxRunner;
|
||||
use super::add_helper_runtime_permissions;
|
||||
use super::compatibility_sandbox_policy;
|
||||
use super::helper_env;
|
||||
use super::helper_env_from_vars;
|
||||
use super::helper_env_key_is_allowed;
|
||||
@@ -488,18 +451,12 @@ mod tests {
|
||||
let file_system_policy =
|
||||
restricted_policy(vec![path_entry(cwd.clone(), FileSystemAccessMode::Write)]);
|
||||
let network_policy = NetworkSandboxPolicy::Restricted;
|
||||
let sandbox_policy =
|
||||
compatibility_sandbox_policy(&file_system_policy, network_policy, cwd.as_path());
|
||||
let permission_profile =
|
||||
PermissionProfile::from_runtime_permissions(&file_system_policy, network_policy);
|
||||
let sandbox_context = sandbox_context_with_cwd(&file_system_policy, cwd.clone());
|
||||
|
||||
let request = runner
|
||||
.sandbox_exec_request(
|
||||
&sandbox_policy,
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
&cwd,
|
||||
&sandbox_context,
|
||||
)
|
||||
.sandbox_exec_request(&permission_profile, &cwd, &sandbox_context)
|
||||
.expect("sandbox exec request");
|
||||
|
||||
assert_eq!(request.env.get(&path_key), Some(&path));
|
||||
|
||||
@@ -24,8 +24,7 @@ async fn spawn_command_under_sandbox(
|
||||
use codex_core::exec::build_exec_request;
|
||||
use codex_core::sandboxing::SandboxPermissions;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use std::process::Stdio;
|
||||
|
||||
let codex_linux_sandbox_exe = None;
|
||||
@@ -43,9 +42,7 @@ async fn spawn_command_under_sandbox(
|
||||
justification: None,
|
||||
arg0: None,
|
||||
},
|
||||
sandbox_policy,
|
||||
&FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, sandbox_cwd),
|
||||
NetworkSandboxPolicy::from(sandbox_policy),
|
||||
&PermissionProfile::from_legacy_sandbox_policy(sandbox_policy),
|
||||
sandbox_cwd,
|
||||
&codex_linux_sandbox_exe,
|
||||
/*use_legacy_landlock*/ false,
|
||||
|
||||
@@ -10,6 +10,8 @@ use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -132,12 +134,15 @@ async fn run_cmd_result_with_policies(
|
||||
};
|
||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
||||
let codex_linux_sandbox_exe = Some(PathBuf::from(sandbox_program));
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
|
||||
process_exec_tool_call(
|
||||
params,
|
||||
&sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
&permission_profile,
|
||||
&sandbox_cwd,
|
||||
&codex_linux_sandbox_exe,
|
||||
use_legacy_landlock,
|
||||
@@ -394,11 +399,10 @@ async fn assert_network_blocked(cmd: &[&str]) {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
||||
let codex_linux_sandbox_exe: Option<PathBuf> = Some(PathBuf::from(sandbox_program));
|
||||
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy);
|
||||
let result = process_exec_tool_call(
|
||||
params,
|
||||
&sandbox_policy,
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
&permission_profile,
|
||||
&sandbox_cwd,
|
||||
&codex_linux_sandbox_exe,
|
||||
/*use_legacy_landlock*/ false,
|
||||
|
||||
@@ -386,6 +386,76 @@ impl Default for PermissionProfile {
|
||||
}
|
||||
|
||||
impl PermissionProfile {
|
||||
/// Managed read-only filesystem access with restricted network access.
|
||||
pub fn read_only() -> Self {
|
||||
Self::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
}
|
||||
}
|
||||
|
||||
/// Managed workspace-write filesystem access with restricted network access.
|
||||
pub fn workspace_write() -> Self {
|
||||
Self::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Restricted {
|
||||
entries: vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::SlashTmp,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".git".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".agents".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(Some(".codex".into())),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_runtime_permissions(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -1762,6 +1832,20 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_profile_presets_match_legacy_defaults() {
|
||||
assert_eq!(
|
||||
PermissionProfile::read_only(),
|
||||
PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy())
|
||||
);
|
||||
assert_eq!(
|
||||
PermissionProfile::workspace_write(),
|
||||
PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_profile_round_trip_preserves_disabled_sandbox() -> Result<()> {
|
||||
let cwd = tempdir()?;
|
||||
|
||||
@@ -17,6 +17,7 @@ pub use manager::SandboxTransformError;
|
||||
pub use manager::SandboxTransformRequest;
|
||||
pub use manager::SandboxType;
|
||||
pub use manager::SandboxablePreference;
|
||||
pub use manager::compatibility_sandbox_policy_for_permission_profile;
|
||||
pub use manager::get_platform_sandbox;
|
||||
|
||||
use codex_protocol::error::CodexErr;
|
||||
|
||||
@@ -5,13 +5,12 @@ use crate::bwrap::is_wsl1;
|
||||
use crate::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use crate::landlock::allow_network_for_proxy;
|
||||
use crate::landlock::create_linux_sandbox_command_args_for_policies;
|
||||
use crate::policy_transforms::EffectiveSandboxPermissions;
|
||||
use crate::policy_transforms::effective_file_system_sandbox_policy;
|
||||
use crate::policy_transforms::effective_network_sandbox_policy;
|
||||
use crate::policy_transforms::effective_permission_profile;
|
||||
use crate::policy_transforms::should_require_platform_sandbox;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -80,6 +79,7 @@ pub struct SandboxExecRequest {
|
||||
pub sandbox: SandboxType,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub windows_sandbox_private_desktop: bool,
|
||||
pub permission_profile: PermissionProfile,
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
pub network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -91,9 +91,7 @@ pub struct SandboxExecRequest {
|
||||
/// This keeps call sites self-documenting when several fields are optional.
|
||||
pub struct SandboxTransformRequest<'a> {
|
||||
pub command: SandboxCommand,
|
||||
pub policy: &'a SandboxPolicy,
|
||||
pub file_system_policy: &'a FileSystemSandboxPolicy,
|
||||
pub network_policy: NetworkSandboxPolicy,
|
||||
pub permissions: &'a PermissionProfile,
|
||||
pub sandbox: SandboxType,
|
||||
pub enforce_managed_network: bool,
|
||||
// TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>>
|
||||
@@ -174,9 +172,7 @@ impl SandboxManager {
|
||||
) -> Result<SandboxExecRequest, SandboxTransformError> {
|
||||
let SandboxTransformRequest {
|
||||
mut command,
|
||||
policy,
|
||||
file_system_policy,
|
||||
network_policy,
|
||||
permissions,
|
||||
sandbox,
|
||||
enforce_managed_network,
|
||||
network,
|
||||
@@ -187,15 +183,16 @@ impl SandboxManager {
|
||||
windows_sandbox_private_desktop,
|
||||
} = request;
|
||||
let additional_permissions = command.additional_permissions.take();
|
||||
let EffectiveSandboxPermissions {
|
||||
sandbox_policy: effective_policy,
|
||||
} = EffectiveSandboxPermissions::new(policy, additional_permissions.as_ref());
|
||||
let effective_file_system_policy = effective_file_system_sandbox_policy(
|
||||
file_system_policy,
|
||||
additional_permissions.as_ref(),
|
||||
let effective_permission_profile =
|
||||
effective_permission_profile(permissions, additional_permissions.as_ref());
|
||||
let (effective_file_system_policy, effective_network_policy) =
|
||||
effective_permission_profile.to_runtime_permissions();
|
||||
let effective_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&effective_file_system_policy,
|
||||
effective_network_policy,
|
||||
sandbox_policy_cwd,
|
||||
);
|
||||
let effective_network_policy =
|
||||
effective_network_sandbox_policy(network_policy, additional_permissions.as_ref());
|
||||
let mut argv = Vec::with_capacity(1 + command.args.len());
|
||||
argv.push(command.program);
|
||||
argv.extend(command.args.into_iter().map(OsString::from));
|
||||
@@ -264,6 +261,7 @@ impl SandboxManager {
|
||||
sandbox,
|
||||
windows_sandbox_level,
|
||||
windows_sandbox_private_desktop,
|
||||
permission_profile: effective_permission_profile,
|
||||
sandbox_policy: effective_policy,
|
||||
file_system_sandbox_policy: effective_file_system_policy,
|
||||
network_sandbox_policy: effective_network_policy,
|
||||
@@ -272,6 +270,50 @@ impl SandboxManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compatibility_sandbox_policy_for_permission_profile(
|
||||
permissions: &PermissionProfile,
|
||||
file_system_policy: &FileSystemSandboxPolicy,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> SandboxPolicy {
|
||||
permissions
|
||||
.to_legacy_sandbox_policy(cwd)
|
||||
.unwrap_or_else(|_| {
|
||||
compatibility_workspace_write_policy(file_system_policy, network_policy, cwd)
|
||||
})
|
||||
}
|
||||
|
||||
fn compatibility_workspace_write_policy(
|
||||
file_system_policy: &FileSystemSandboxPolicy,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> SandboxPolicy {
|
||||
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
||||
let writable_roots = file_system_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.into_iter()
|
||||
.map(|root| root.root)
|
||||
.filter(|root| cwd_abs.as_ref() != Some(root))
|
||||
.collect();
|
||||
let tmpdir_writable = std::env::var_os("TMPDIR")
|
||||
.filter(|tmpdir| !tmpdir.is_empty())
|
||||
.and_then(|tmpdir| {
|
||||
AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok()
|
||||
})
|
||||
.is_some_and(|tmpdir| file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd));
|
||||
let slash_tmp = Path::new("/tmp");
|
||||
let slash_tmp_writable = slash_tmp.is_absolute()
|
||||
&& slash_tmp.is_dir()
|
||||
&& file_system_policy.can_write_path_with_cwd(slash_tmp, cwd);
|
||||
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access: network_policy.is_enabled(),
|
||||
exclude_tmpdir_env_var: !tmpdir_writable,
|
||||
exclude_slash_tmp: !slash_tmp_writable,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn ensure_linux_bubblewrap_is_supported(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
|
||||
@@ -5,9 +5,10 @@ use super::SandboxType;
|
||||
use super::SandboxablePreference;
|
||||
use super::get_platform_sandbox;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -74,6 +75,10 @@ fn restricted_file_system_uses_platform_sandbox_without_managed_network() {
|
||||
fn transform_preserves_unrestricted_file_system_policy_for_restricted_network() {
|
||||
let manager = SandboxManager::new();
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let exec_request = manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command: SandboxCommand {
|
||||
@@ -83,11 +88,7 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network()
|
||||
env: HashMap::new(),
|
||||
additional_permissions: None,
|
||||
},
|
||||
policy: &SandboxPolicy::ExternalSandbox {
|
||||
network_access: NetworkAccess::Restricted,
|
||||
},
|
||||
file_system_policy: &FileSystemSandboxPolicy::unrestricted(),
|
||||
network_policy: NetworkSandboxPolicy::Restricted,
|
||||
permissions: &permissions,
|
||||
sandbox: SandboxType::None,
|
||||
enforce_managed_network: false,
|
||||
network: None,
|
||||
@@ -113,6 +114,9 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network()
|
||||
fn transform_additional_permissions_enable_network_for_external_sandbox() {
|
||||
let manager = SandboxManager::new();
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
|
||||
let permissions = PermissionProfile::External {
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
let temp_dir = TempDir::new().expect("create temp dir");
|
||||
let path = AbsolutePathBuf::from_absolute_path(
|
||||
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
|
||||
@@ -125,7 +129,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
|
||||
args: Vec::new(),
|
||||
cwd: cwd.clone(),
|
||||
env: HashMap::new(),
|
||||
additional_permissions: Some(PermissionProfile {
|
||||
additional_permissions: Some(AdditionalPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
@@ -135,11 +139,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
|
||||
)),
|
||||
}),
|
||||
},
|
||||
policy: &SandboxPolicy::ExternalSandbox {
|
||||
network_access: NetworkAccess::Restricted,
|
||||
},
|
||||
file_system_policy: &FileSystemSandboxPolicy::unrestricted(),
|
||||
network_policy: NetworkSandboxPolicy::Restricted,
|
||||
permissions: &permissions,
|
||||
sandbox: SandboxType::None,
|
||||
enforce_managed_network: false,
|
||||
network: None,
|
||||
@@ -174,6 +174,24 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
.expect("absolute temp dir");
|
||||
let allowed_path = workspace_root.join("allowed");
|
||||
let denied_path = workspace_root.join("denied");
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
]);
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let exec_request = manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command: SandboxCommand {
|
||||
@@ -181,7 +199,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
args: Vec::new(),
|
||||
cwd: cwd.clone(),
|
||||
env: HashMap::new(),
|
||||
additional_permissions: Some(PermissionProfile {
|
||||
additional_permissions: Some(AdditionalPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions::from_read_write_roots(
|
||||
/*read*/ None,
|
||||
Some(vec![allowed_path.clone()]),
|
||||
@@ -189,24 +207,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
policy: &SandboxPolicy::ReadOnly {
|
||||
network_access: false,
|
||||
},
|
||||
file_system_policy: &FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
]),
|
||||
network_policy: NetworkSandboxPolicy::Restricted,
|
||||
permissions: &permissions,
|
||||
sandbox: SandboxType::None,
|
||||
enforce_managed_network: false,
|
||||
network: None,
|
||||
@@ -249,6 +250,7 @@ fn transform_linux_seccomp_request(
|
||||
) -> super::SandboxExecRequest {
|
||||
let manager = SandboxManager::new();
|
||||
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
|
||||
let permissions = PermissionProfile::Disabled;
|
||||
manager
|
||||
.transform(SandboxTransformRequest {
|
||||
command: SandboxCommand {
|
||||
@@ -258,9 +260,7 @@ fn transform_linux_seccomp_request(
|
||||
env: HashMap::new(),
|
||||
additional_permissions: None,
|
||||
},
|
||||
policy: &SandboxPolicy::DangerFullAccess,
|
||||
file_system_policy: &FileSystemSandboxPolicy::unrestricted(),
|
||||
network_policy: NetworkSandboxPolicy::Enabled,
|
||||
permissions: &permissions,
|
||||
sandbox: SandboxType::LinuxSeccomp,
|
||||
enforce_managed_network: false,
|
||||
network: None,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -561,6 +562,22 @@ pub fn effective_network_sandbox_policy(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn effective_permission_profile(
|
||||
permission_profile: &PermissionProfile,
|
||||
additional_permissions: Option<&AdditionalPermissionProfile>,
|
||||
) -> PermissionProfile {
|
||||
let (file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
|
||||
let effective_file_system_policy =
|
||||
effective_file_system_sandbox_policy(&file_system_policy, additional_permissions);
|
||||
let effective_network_policy =
|
||||
effective_network_sandbox_policy(network_policy, additional_permissions);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&effective_file_system_policy,
|
||||
effective_network_policy,
|
||||
)
|
||||
}
|
||||
|
||||
fn sandbox_policy_with_additional_permissions(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
additional_permissions: &AdditionalPermissionProfile,
|
||||
|
||||
@@ -4,13 +4,13 @@ use codex_features::Features;
|
||||
use codex_protocol::config_types::WebSearchConfig;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -121,7 +121,7 @@ pub struct ToolsConfigParams<'a> {
|
||||
pub image_generation_tool_auth_allowed: bool,
|
||||
pub web_search_mode: Option<WebSearchMode>,
|
||||
pub session_source: SessionSource,
|
||||
pub sandbox_policy: &'a SandboxPolicy,
|
||||
pub permission_profile: &'a PermissionProfile,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ impl ToolsConfig {
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode,
|
||||
session_source,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
windows_sandbox_level,
|
||||
} = params;
|
||||
let include_apply_patch_tool = features.enabled(Feature::ApplyPatchFreeform);
|
||||
@@ -167,7 +167,7 @@ impl ToolsConfig {
|
||||
};
|
||||
let unified_exec_allowed = unified_exec_allowed_in_environment(
|
||||
cfg!(target_os = "windows"),
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
*windows_sandbox_level,
|
||||
);
|
||||
let shell_type = if !features.enabled(Feature::ShellTool) {
|
||||
@@ -322,15 +322,19 @@ fn supports_image_generation(model_info: &ModelInfo) -> bool {
|
||||
|
||||
fn unified_exec_allowed_in_environment(
|
||||
is_windows: bool,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> bool {
|
||||
let managed_sandbox_required = match permission_profile {
|
||||
PermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => !file_system.to_sandbox_policy().has_full_disk_write_access() || !network.is_enabled(),
|
||||
PermissionProfile::Disabled | PermissionProfile::External { .. } => false,
|
||||
};
|
||||
!(is_windows
|
||||
&& windows_sandbox_level != WindowsSandboxLevel::Disabled
|
||||
&& !matches!(
|
||||
sandbox_policy,
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
|
||||
))
|
||||
&& managed_sandbox_required)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -3,10 +3,12 @@ use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -50,25 +52,40 @@ fn model_info() -> ModelInfo {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_is_blocked_for_windows_sandboxed_policies_only() {
|
||||
fn unified_exec_is_blocked_for_windows_managed_profiles_only() {
|
||||
assert!(!unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
&PermissionProfile::read_only(),
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(!unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
&PermissionProfile::workspace_write(),
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::External {
|
||||
network: Default::default(),
|
||||
},
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
/*is_windows*/ true,
|
||||
&PermissionProfile::Disabled,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
));
|
||||
}
|
||||
@@ -88,7 +105,7 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -148,7 +165,7 @@ fn subagents_keep_request_user_input_mode_config_and_agent_jobs_workers_opt_in_b
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
"agent_job:test".to_string(),
|
||||
)),
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -176,7 +193,7 @@ fn image_generation_requires_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let supported_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -186,7 +203,7 @@ fn image_generation_requires_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let auth_disallowed_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -196,7 +213,7 @@ fn image_generation_requires_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: false,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let unsupported_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -206,7 +223,7 @@ fn image_generation_requires_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
assert!(!default_tools_config.image_gen_tool);
|
||||
|
||||
@@ -26,11 +26,11 @@ use codex_protocol::config_types::WebSearchConfig;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::VIEW_IMAGE_TOOL_NAME;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -57,7 +57,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -169,7 +169,7 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -207,7 +207,7 @@ fn goal_tools_require_goals_feature() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -228,7 +228,7 @@ fn goal_tools_require_goals_feature() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -254,7 +254,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -397,7 +397,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -432,7 +432,7 @@ fn view_image_tool_omits_detail_without_original_detail_support() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -462,7 +462,7 @@ fn view_image_tool_includes_detail_with_original_detail_support() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -499,7 +499,7 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_has_environment(/*has_environment*/ false);
|
||||
@@ -537,7 +537,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
"agent_job:test".to_string(),
|
||||
)),
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -574,7 +574,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -597,7 +597,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -625,7 +625,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -645,7 +645,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -674,7 +674,7 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -705,7 +705,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (default_tools, _) = build_specs(
|
||||
@@ -728,7 +728,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (supported_tools, _) = build_specs(
|
||||
@@ -754,7 +754,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -784,7 +784,7 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -820,7 +820,7 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -869,7 +869,7 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_web_search_config(Some(web_search_config.clone()));
|
||||
@@ -911,7 +911,7 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -946,7 +946,7 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -977,7 +977,7 @@ fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1011,7 +1011,7 @@ fn test_parallel_support_flags() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1038,7 +1038,7 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1064,7 +1064,7 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1156,7 +1156,7 @@ fn test_build_specs_mcp_namespace_description_falls_back_when_missing() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1196,7 +1196,7 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1246,7 +1246,7 @@ fn search_tool_description_lists_each_mcp_source_once() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1348,7 +1348,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1368,7 +1368,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1386,7 +1386,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
@@ -1411,7 +1411,7 @@ fn search_tool_registers_for_deferred_dynamic_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let dynamic_tools = vec![
|
||||
@@ -1500,7 +1500,7 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
@@ -1540,7 +1540,7 @@ fn tool_suggest_can_be_registered_without_search_tool() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
@@ -1586,7 +1586,7 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1688,7 +1688,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1741,7 +1741,7 @@ fn code_mode_preserves_nullable_and_literal_mcp_input_shapes() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1824,7 +1824,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1860,7 +1860,7 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -1897,7 +1897,7 @@ fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only(
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
@@ -2054,7 +2054,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_structured_output_sample() {
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
|
||||
@@ -72,15 +72,15 @@ impl App {
|
||||
"Failed to carry forward approval policy override: {err}"
|
||||
));
|
||||
}
|
||||
if let Some(policy) = self.runtime_sandbox_policy_override.as_ref() {
|
||||
if let Err(err) = config.permissions.sandbox_policy.set(policy.clone()) {
|
||||
tracing::warn!(%err, "failed to carry forward sandbox policy override");
|
||||
self.chat_widget.add_error_message(format!(
|
||||
"Failed to carry forward sandbox policy override: {err}"
|
||||
));
|
||||
} else {
|
||||
sync_runtime_permissions_from_legacy_sandbox_policy(config);
|
||||
}
|
||||
if let Some(policy) = self.runtime_sandbox_policy_override.as_ref()
|
||||
&& let Err(err) = config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(policy.clone(), config.cwd.as_path())
|
||||
{
|
||||
tracing::warn!(%err, "failed to carry forward sandbox policy override");
|
||||
self.chat_widget.add_error_message(format!(
|
||||
"Failed to carry forward sandbox policy override: {err}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,13 +113,15 @@ impl App {
|
||||
user_message_prefix: &str,
|
||||
log_message: &str,
|
||||
) -> bool {
|
||||
if let Err(err) = config.permissions.sandbox_policy.set(policy) {
|
||||
if let Err(err) = config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(policy, config.cwd.as_path())
|
||||
{
|
||||
tracing::warn!(error = %err, "{log_message}");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("{user_message_prefix}: {err}"));
|
||||
return false;
|
||||
}
|
||||
sync_runtime_permissions_from_legacy_sandbox_policy(config);
|
||||
|
||||
true
|
||||
}
|
||||
@@ -543,17 +545,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_runtime_permissions_from_legacy_sandbox_policy(config: &mut Config) {
|
||||
let sandbox_policy = config.permissions.sandbox_policy.get();
|
||||
config.permissions.file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
sandbox_policy,
|
||||
&config.cwd,
|
||||
);
|
||||
config.permissions.network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(sandbox_policy);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -2218,9 +2218,7 @@ async fn inactive_thread_approval_bubbles_into_active_view() -> Result<()> {
|
||||
ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
|
||||
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
|
||||
},
|
||||
@@ -2380,9 +2378,7 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()>
|
||||
ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
|
||||
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
|
||||
},
|
||||
@@ -2605,9 +2601,7 @@ async fn inactive_thread_approval_badge_clears_after_turn_completion_notificatio
|
||||
ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
|
||||
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
|
||||
},
|
||||
@@ -2661,9 +2655,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
|
||||
let primary_session = ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
|
||||
};
|
||||
|
||||
@@ -2776,9 +2768,7 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_
|
||||
let primary_session = ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
|
||||
};
|
||||
|
||||
@@ -2847,9 +2837,7 @@ async fn thread_read_session_state_does_not_reuse_primary_permission_profile() {
|
||||
let primary_session = ThreadSessionState {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
|
||||
};
|
||||
app.primary_session_configured = Some(primary_session);
|
||||
@@ -3752,9 +3740,7 @@ fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSessionState
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::read_only()),
|
||||
cwd: cwd.abs(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -303,9 +303,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)),
|
||||
permission_profile: Some(PermissionProfile::read_only()),
|
||||
cwd: cwd.abs(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -1776,9 +1776,7 @@ mod tests {
|
||||
AskForApproval::Never,
|
||||
codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)),
|
||||
Some(PermissionProfile::read_only()),
|
||||
test_path_buf("/tmp/project").abs(),
|
||||
Vec::new(),
|
||||
/*reasoning_effort*/ None,
|
||||
@@ -1809,9 +1807,7 @@ mod tests {
|
||||
AskForApproval::Never,
|
||||
codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
Some(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)),
|
||||
Some(PermissionProfile::read_only()),
|
||||
test_path_buf("/tmp/project").abs(),
|
||||
Vec::new(),
|
||||
/*reasoning_effort*/ None,
|
||||
|
||||
@@ -2333,20 +2333,6 @@ impl ChatWidget {
|
||||
display: SessionConfiguredDisplay,
|
||||
fork_parent_title: Option<String>,
|
||||
) {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) = match event
|
||||
.permission_profile
|
||||
.as_ref()
|
||||
{
|
||||
Some(permission_profile) => permission_profile.to_runtime_permissions(),
|
||||
None => (
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&event.sandbox_policy,
|
||||
&event.cwd,
|
||||
),
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(&event.sandbox_policy),
|
||||
),
|
||||
};
|
||||
|
||||
self.last_agent_markdown = None;
|
||||
self.agent_turn_markdowns.clear();
|
||||
self.visible_user_turn_count = 0;
|
||||
@@ -2379,18 +2365,52 @@ impl ChatWidget {
|
||||
self.config.permissions.approval_policy =
|
||||
Constrained::allow_only(event.approval_policy);
|
||||
}
|
||||
if let Err(err) = self
|
||||
.config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(event.sandbox_policy.clone())
|
||||
{
|
||||
tracing::warn!(%err, "failed to sync sandbox_policy from SessionConfigured");
|
||||
let permission_sync = match event.permission_profile.clone() {
|
||||
Some(permission_profile) => self
|
||||
.config
|
||||
.permissions
|
||||
.set_permission_profile(permission_profile, event.cwd.as_path()),
|
||||
None => self
|
||||
.config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(event.sandbox_policy.clone(), event.cwd.as_path()),
|
||||
};
|
||||
if let Err(err) = permission_sync {
|
||||
tracing::warn!(%err, "failed to sync permissions from SessionConfigured");
|
||||
self.config.permissions.sandbox_policy =
|
||||
Constrained::allow_only(event.sandbox_policy.clone());
|
||||
match event.permission_profile.clone() {
|
||||
Some(permission_profile) => {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
self.config.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.config.permissions.network_sandbox_policy = network_sandbox_policy;
|
||||
}
|
||||
None => {
|
||||
self.config.permissions.file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&event.sandbox_policy,
|
||||
&event.cwd,
|
||||
);
|
||||
self.config.permissions.network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(
|
||||
&event.sandbox_policy,
|
||||
);
|
||||
let permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
&event.sandbox_policy,
|
||||
),
|
||||
&self.config.permissions.file_system_sandbox_policy,
|
||||
self.config.permissions.network_sandbox_policy,
|
||||
);
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.config.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.config.permissions.network_sandbox_policy = network_sandbox_policy;
|
||||
self.config.approvals_reviewer = event.approvals_reviewer;
|
||||
self.status_line_project_root_name_cache = None;
|
||||
let forked_from_id = event.forked_from_id;
|
||||
@@ -10284,16 +10304,9 @@ impl ChatWidget {
|
||||
/// Set the sandbox policy in the widget's config copy.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
|
||||
self.config.permissions.sandbox_policy.set(policy)?;
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get();
|
||||
self.config.permissions.file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
sandbox_policy,
|
||||
&self.config.cwd,
|
||||
);
|
||||
self.config.permissions.network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(sandbox_policy);
|
||||
Ok(())
|
||||
self.config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(policy, self.config.cwd.as_path())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
|
||||
@@ -258,7 +258,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
.expect("set sandbox policy");
|
||||
chat.config.cwd = test_path_buf("/home/user/main").abs();
|
||||
|
||||
let expected_sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let legacy_fallback_sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let expected_cwd = test_path_buf("/home/user/sub-agent").abs();
|
||||
let expected_file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
@@ -279,6 +279,9 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
&expected_file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let expected_sandbox = expected_permission_profile
|
||||
.to_legacy_sandbox_policy(expected_cwd.as_path())
|
||||
.expect("permission profile should project to legacy sandbox policy");
|
||||
let configured = codex_protocol::protocol::SessionConfiguredEvent {
|
||||
session_id: ThreadId::new(),
|
||||
forked_from_id: None,
|
||||
@@ -288,7 +291,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: expected_sandbox.clone(),
|
||||
sandbox_policy: legacy_fallback_sandbox,
|
||||
permission_profile: Some(expected_permission_profile.clone()),
|
||||
cwd: expected_cwd.clone(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
|
||||
@@ -4,6 +4,7 @@ use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::de::Error as SerdeError;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::path::Display;
|
||||
use std::path::Path;
|
||||
@@ -46,16 +47,23 @@ impl AbsolutePathBuf {
|
||||
base_path: B,
|
||||
) -> Self {
|
||||
let expanded = Self::maybe_expand_home_directory(path.as_ref());
|
||||
Self(absolutize::absolutize_from(&expanded, base_path.as_ref()))
|
||||
let expanded = normalize_path_for_platform(&expanded);
|
||||
let base_path = normalize_path_for_platform(base_path.as_ref());
|
||||
Self(absolutize::absolutize_from(
|
||||
expanded.as_ref(),
|
||||
base_path.as_ref(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn from_absolute_path<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||
let expanded = Self::maybe_expand_home_directory(path.as_ref());
|
||||
Ok(Self(absolutize::absolutize(&expanded)?))
|
||||
let expanded = normalize_path_for_platform(&expanded);
|
||||
Ok(Self(absolutize::absolutize(expanded.as_ref())?))
|
||||
}
|
||||
|
||||
pub fn from_absolute_path_checked<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||
let expanded = Self::maybe_expand_home_directory(path.as_ref());
|
||||
let expanded = normalize_path_for_platform(&expanded);
|
||||
if !expanded.is_absolute() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
@@ -63,15 +71,14 @@ impl AbsolutePathBuf {
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self(absolutize::absolutize_from(&expanded, Path::new("/"))))
|
||||
Ok(Self(absolutize::absolutize_from(
|
||||
expanded.as_ref(),
|
||||
Path::new("/"),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn current_dir() -> std::io::Result<Self> {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
Ok(Self(absolutize::absolutize_from(
|
||||
¤t_dir,
|
||||
¤t_dir,
|
||||
)))
|
||||
Self::from_absolute_path(std::env::current_dir()?)
|
||||
}
|
||||
|
||||
/// Construct an absolute path from `path`, resolving relative paths against
|
||||
@@ -132,6 +139,45 @@ impl AbsolutePathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_path_for_platform(path: &Path) -> Cow<'_, Path> {
|
||||
if cfg!(windows)
|
||||
&& let Some(path) = path.to_str()
|
||||
&& let Some(normalized) = normalize_windows_device_path(path)
|
||||
{
|
||||
return Cow::Owned(PathBuf::from(normalized));
|
||||
}
|
||||
|
||||
Cow::Borrowed(path)
|
||||
}
|
||||
|
||||
fn normalize_windows_device_path(path: &str) -> Option<String> {
|
||||
if let Some(unc) = path.strip_prefix(r"\\?\UNC\") {
|
||||
return Some(format!(r"\\{unc}"));
|
||||
}
|
||||
if let Some(unc) = path.strip_prefix(r"\\.\UNC\") {
|
||||
return Some(format!(r"\\{unc}"));
|
||||
}
|
||||
if let Some(path) = path.strip_prefix(r"\\?\")
|
||||
&& is_windows_drive_absolute_path(path)
|
||||
{
|
||||
return Some(path.to_string());
|
||||
}
|
||||
if let Some(path) = path.strip_prefix(r"\\.\")
|
||||
&& is_windows_drive_absolute_path(path)
|
||||
{
|
||||
return Some(path.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_windows_drive_absolute_path(path: &str) -> bool {
|
||||
let bytes = path.as_bytes();
|
||||
bytes.len() >= 3
|
||||
&& bytes[0].is_ascii_alphabetic()
|
||||
&& bytes[1] == b':'
|
||||
&& matches!(bytes[2], b'\\' | b'/')
|
||||
}
|
||||
|
||||
/// Canonicalize a path when possible, but preserve the logical absolute path
|
||||
/// whenever canonicalization would rewrite it through a nested symlink.
|
||||
///
|
||||
@@ -391,6 +437,43 @@ mod tests {
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_windows_device_path_strips_supported_verbatim_prefixes() {
|
||||
assert_eq!(
|
||||
normalize_windows_device_path(r"\\?\D:\c\x\worktrees\2508\swift-base"),
|
||||
Some(r"D:\c\x\worktrees\2508\swift-base".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_windows_device_path(r"\\.\D:\c\x\worktrees\2508\swift-base"),
|
||||
Some(r"D:\c\x\worktrees\2508\swift-base".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_windows_device_path(r"\\?\UNC\server\share\workspace"),
|
||||
Some(r"\\server\share\workspace".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_windows_device_path(r"\\.\UNC\server\share\workspace"),
|
||||
Some(r"\\server\share\workspace".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_windows_device_path(r"\\?\GLOBALROOT\Device"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn from_absolute_path_strips_windows_verbatim_prefix() {
|
||||
let path =
|
||||
AbsolutePathBuf::from_absolute_path_checked(r"\\?\D:\c\x\worktrees\2508\swift-base")
|
||||
.expect("verbatim drive path should be absolute");
|
||||
|
||||
assert_eq!(
|
||||
path.as_path(),
|
||||
Path::new(r"D:\c\x\worktrees\2508\swift-base")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_path_is_resolved_against_base_path() {
|
||||
let temp_dir = tempdir().expect("base dir");
|
||||
|
||||
Reference in New Issue
Block a user