permissions: make profiles represent enforcement

This commit is contained in:
Michael Bolin
2026-04-23 17:08:51 -07:00
parent a9f75e5cda
commit 6c5b77a080
73 changed files with 2091 additions and 890 deletions
+2 -30
View File
@@ -55,8 +55,6 @@ 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::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
@@ -810,20 +808,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
async fn permission_profile_override_populates_runtime_permissions() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let permission_profile = PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: None,
}),
};
let permission_profile = PermissionProfile::Disabled;
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
@@ -848,20 +833,7 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
async fn permission_profile_override_preserves_configured_network_proxy() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let permission_profile = PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: None,
}),
};
let permission_profile = PermissionProfile::Disabled;
let config = Config::load_from_base_config_with_overrides(
ConfigToml {
+6 -4
View File
@@ -79,6 +79,7 @@ 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::models::SandboxEnforcement;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemSandboxPolicy;
@@ -221,7 +222,8 @@ 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(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(self.sandbox_policy.get()),
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
@@ -1773,9 +1775,9 @@ impl Config {
})?;
let profile = resolve_permission_profile(permissions, default_permissions)?;
// PermissionProfile only carries the network enabled bit today. Keep the
// configured proxy/allowlist policy so active profiles can round-trip without
// broadening network behavior.
// PermissionProfile carries the active network sandbox bit, not the configured
// proxy/allowlist policy. Keep that config so active profiles can round-trip
// without broadening network behavior.
network_proxy_config_from_profile_network(profile.network.as_ref())
} else {
NetworkProxyConfig::default()
@@ -4,7 +4,7 @@ use codex_analytics::GuardianReviewedAction;
use codex_protocol::approvals::GuardianAssessmentAction;
use codex_protocol::approvals::GuardianCommandSource;
use codex_protocol::approvals::NetworkApprovalProtocol;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::request_permissions::RequestPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Serialize;
@@ -20,7 +20,7 @@ pub(crate) enum GuardianApprovalRequest {
command: Vec<String>,
cwd: AbsolutePathBuf,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
justification: Option<String>,
},
ExecCommand {
@@ -28,7 +28,7 @@ pub(crate) enum GuardianApprovalRequest {
command: Vec<String>,
cwd: AbsolutePathBuf,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
justification: Option<String>,
tty: bool,
},
@@ -39,7 +39,7 @@ pub(crate) enum GuardianApprovalRequest {
program: String,
argv: Vec<String>,
cwd: AbsolutePathBuf,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
},
ApplyPatch {
id: String,
@@ -85,7 +85,7 @@ pub(crate) struct GuardianNetworkAccessTrigger {
pub(crate) cwd: AbsolutePathBuf,
pub(crate) sandbox_permissions: crate::sandboxing::SandboxPermissions,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) additional_permissions: Option<PermissionProfile>,
pub(crate) additional_permissions: Option<AdditionalPermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) justification: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -109,7 +109,7 @@ struct CommandApprovalAction<'a> {
cwd: &'a Path,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
#[serde(skip_serializing_if = "Option::is_none")]
additional_permissions: Option<&'a PermissionProfile>,
additional_permissions: Option<&'a AdditionalPermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
justification: Option<&'a String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -124,7 +124,7 @@ struct ExecveApprovalAction<'a> {
argv: &'a [String],
cwd: &'a Path,
#[serde(skip_serializing_if = "Option::is_none")]
additional_permissions: Option<&'a PermissionProfile>,
additional_permissions: Option<&'a AdditionalPermissionProfile>,
}
#[derive(Serialize)]
@@ -178,7 +178,7 @@ fn serialize_command_guardian_action(
command: &[String],
cwd: &Path,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
additional_permissions: Option<&PermissionProfile>,
additional_permissions: Option<&AdditionalPermissionProfile>,
justification: Option<&String>,
tty: Option<bool>,
) -> serde_json::Result<Value> {
+7 -5
View File
@@ -91,11 +91,12 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::models::format_allow_prefixes;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::permissions::FileSystemSandboxKind;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::FileChange;
@@ -1800,7 +1801,7 @@ impl Session {
reason: Option<String>,
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
available_decisions: Option<Vec<ReviewDecision>>,
) -> ReviewDecision {
// command-level approvals use `call_id`.
@@ -2222,7 +2223,8 @@ impl Session {
PermissionGrantScope::Turn => {
if let Some(turn_state) = originating_turn_state {
let mut ts = turn_state.lock().await;
let permissions: PermissionProfile = response.permissions.clone().into();
let permissions: AdditionalPermissionProfile =
response.permissions.clone().into();
ts.record_granted_permissions(permissions);
if response.strict_auto_review {
ts.enable_strict_auto_review();
@@ -2240,7 +2242,7 @@ impl Session {
clippy::await_holding_invalid_type,
reason = "active turn reads must stay consistent with the matching turn state"
)]
pub(crate) async fn granted_turn_permissions(&self) -> Option<PermissionProfile> {
pub(crate) async fn granted_turn_permissions(&self) -> Option<AdditionalPermissionProfile> {
let active = self.active_turn.lock().await;
let active = active.as_ref()?;
let ts = active.turn_state.lock().await;
@@ -2260,7 +2262,7 @@ impl Session {
ts.strict_auto_review_enabled()
}
pub(crate) async fn granted_session_permissions(&self) -> Option<PermissionProfile> {
pub(crate) async fn granted_session_permissions(&self) -> Option<AdditionalPermissionProfile> {
let state = self.state.lock().await;
state.granted_permissions()
}
+5 -30
View File
@@ -92,7 +92,8 @@ impl SessionConfiguration {
}
pub(super) fn permission_profile(&self) -> PermissionProfile {
PermissionProfile::from_runtime_permissions(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(self.sandbox_policy.get()),
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
@@ -175,26 +176,8 @@ impl SessionConfiguration {
next_configuration.sandbox_policy.set(sandbox_policy)?;
let (mut file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
if file_system_sandbox_policy.glob_scan_max_depth.is_none() {
file_system_sandbox_policy.glob_scan_max_depth =
self.file_system_sandbox_policy.glob_scan_max_depth;
}
for deny_entry in self
.file_system_sandbox_policy
.entries
.iter()
.filter(|entry| {
entry.access == codex_protocol::permissions::FileSystemAccessMode::None
})
{
if !file_system_sandbox_policy
.entries
.iter()
.any(|entry| entry == deny_entry)
{
file_system_sandbox_policy.entries.push(deny_entry.clone());
}
}
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;
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
@@ -814,14 +797,6 @@ impl Session {
// Dispatch the SessionConfiguredEvent first and then report any errors.
// If resuming, include converted initial messages in the payload so UIs can render them immediately.
let initial_messages = initial_history.get_event_msgs();
let permission_profile = if matches!(
session_configuration.file_system_sandbox_policy.kind,
FileSystemSandboxKind::ExternalSandbox
) {
None
} else {
Some(session_configuration.permission_profile())
};
let events = std::iter::once(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
@@ -834,7 +809,7 @@ impl Session {
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
permission_profile,
permission_profile: Some(session_configuration.permission_profile()),
cwd: session_configuration.cwd.clone(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
history_log_id,
+10 -4
View File
@@ -1471,7 +1471,8 @@ async fn record_initial_history_reconstructs_forked_transcript() {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn session_configured_omits_permission_profile_for_external_sandbox() -> anyhow::Result<()> {
async fn session_configured_reports_permission_profile_for_external_sandbox() -> anyhow::Result<()>
{
let server = start_mock_server().await;
let sandbox_policy = SandboxPolicy::ExternalSandbox {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
@@ -1489,10 +1490,15 @@ async fn session_configured_omits_permission_profile_for_external_sandbox() -> a
test.session_configured.sandbox_policy,
expected_sandbox_policy
);
let expected_permission_profile =
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&expected_sandbox_policy,
test.session_configured.cwd.as_path(),
);
assert_eq!(
test.session_configured.permission_profile, None,
"ExternalSandbox is enforced outside the PermissionProfile model, so SessionConfigured must \
not expose a lossy root-write profile"
test.session_configured.permission_profile,
Some(expected_permission_profile),
"ExternalSandbox is represented explicitly instead of as a lossy root-write profile"
);
Ok(())
}
@@ -19,9 +19,9 @@ use codex_execpolicy::RuleMatch;
use codex_features::Feature;
use codex_model_provider::create_model_provider;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
use codex_protocol::models::ContentItem;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::permissions::FileSystemSandboxPolicy;
+20 -7
View File
@@ -1,8 +1,11 @@
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::merge_permission_profiles;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
@@ -89,7 +92,8 @@ pub(crate) struct TurnContext {
}
impl TurnContext {
pub(crate) fn permission_profile(&self) -> PermissionProfile {
PermissionProfile::from_runtime_permissions(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&self.sandbox_policy),
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
@@ -236,12 +240,21 @@ impl TurnContext {
pub(crate) fn file_system_sandbox_context(
&self,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> FileSystemSandboxContext {
let base_permissions = self.permission_profile();
let permissions =
merge_permission_profiles(Some(&base_permissions), additional_permissions.as_ref())
.unwrap_or(base_permissions);
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
&self.file_system_sandbox_policy,
additional_permissions.as_ref(),
);
let network_sandbox_policy = effective_network_sandbox_policy(
self.network_sandbox_policy,
additional_permissions.as_ref(),
);
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&self.sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
);
FileSystemSandboxContext {
permissions,
cwd: Some(self.cwd.clone()),
+4 -4
View File
@@ -1,6 +1,6 @@
//! Session-wide mutable state.
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::ResponseItem;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use std::collections::HashMap;
@@ -32,7 +32,7 @@ pub(crate) struct SessionState {
pub(crate) startup_prewarm: Option<SessionStartupPrewarmHandle>,
pub(crate) active_connector_selection: HashSet<String>,
pub(crate) pending_session_start_source: Option<codex_hooks::SessionStartSource>,
granted_permissions: Option<PermissionProfile>,
granted_permissions: Option<AdditionalPermissionProfile>,
next_turn_is_first: bool,
}
@@ -218,12 +218,12 @@ impl SessionState {
self.pending_session_start_source.take()
}
pub(crate) fn record_granted_permissions(&mut self, permissions: PermissionProfile) {
pub(crate) fn record_granted_permissions(&mut self, permissions: AdditionalPermissionProfile) {
self.granted_permissions =
merge_permission_profiles(self.granted_permissions.as_ref(), Some(&permissions));
}
pub(crate) fn granted_permissions(&self) -> Option<PermissionProfile> {
pub(crate) fn granted_permissions(&self) -> Option<AdditionalPermissionProfile> {
self.granted_permissions.clone()
}
}
+4 -4
View File
@@ -21,7 +21,7 @@ use tokio::sync::oneshot;
use crate::session::turn_context::TurnContext;
use crate::tasks::AnySessionTask;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::TokenUsage;
@@ -105,7 +105,7 @@ pub(crate) struct TurnState {
pending_dynamic_tools: HashMap<String, oneshot::Sender<DynamicToolResponse>>,
pending_input: Vec<ResponseInputItem>,
mailbox_delivery_phase: MailboxDeliveryPhase,
granted_permissions: Option<PermissionProfile>,
granted_permissions: Option<AdditionalPermissionProfile>,
strict_auto_review_enabled: bool,
pub(crate) tool_calls: u64,
pub(crate) has_memory_citation: bool,
@@ -247,12 +247,12 @@ impl TurnState {
self.mailbox_delivery_phase = phase;
}
pub(crate) fn record_granted_permissions(&mut self, permissions: PermissionProfile) {
pub(crate) fn record_granted_permissions(&mut self, permissions: AdditionalPermissionProfile) {
self.granted_permissions =
merge_permission_profiles(self.granted_permissions.as_ref(), Some(&permissions));
}
pub(crate) fn granted_permissions(&self) -> Option<PermissionProfile> {
pub(crate) fn granted_permissions(&self) -> Option<AdditionalPermissionProfile> {
self.granted_permissions.clone()
}
@@ -39,8 +39,8 @@ use codex_apply_patch::Hunk;
use codex_apply_patch::parse_patch_streaming;
use codex_exec_server::ExecutorFileSystem;
use codex_features::Feature;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::PatchApplyUpdatedEvent;
@@ -215,7 +215,7 @@ fn write_permissions_for_paths(
file_paths: &[AbsolutePathBuf],
file_system_sandbox_policy: &codex_protocol::permissions::FileSystemSandboxPolicy,
cwd: &AbsolutePathBuf,
) -> Option<PermissionProfile> {
) -> Option<AdditionalPermissionProfile> {
let write_paths = file_paths
.iter()
.map(|path| {
@@ -232,7 +232,7 @@ fn write_permissions_for_paths(
.collect::<Result<Vec<_>, _>>()
.ok()?;
let permissions = (!write_paths.is_empty()).then_some(PermissionProfile {
let permissions = (!write_paths.is_empty()).then_some(AdditionalPermissionProfile {
file_system: Some(FileSystemPermissions::from_read_write_roots(
Some(vec![]),
Some(write_paths),
+15 -15
View File
@@ -34,7 +34,7 @@ use crate::session::session::Session;
pub(crate) use crate::tools::code_mode::CodeModeExecuteHandler;
pub(crate) use crate::tools::code_mode::CodeModeWaitHandler;
pub use apply_patch::ApplyPatchHandler;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::AskForApproval;
pub use dynamic::DynamicToolHandler;
pub use js_repl::JsReplHandler;
@@ -93,10 +93,10 @@ pub(crate) fn normalize_and_validate_additional_permissions(
additional_permissions_allowed: bool,
approval_policy: AskForApproval,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
permissions_preapproved: bool,
_cwd: &Path,
) -> Result<Option<PermissionProfile>, String> {
) -> Result<Option<AdditionalPermissionProfile>, String> {
let uses_additional_permissions = matches!(
sandbox_permissions,
SandboxPermissions::WithAdditionalPermissions
@@ -146,15 +146,15 @@ pub(crate) fn normalize_and_validate_additional_permissions(
pub(super) struct EffectiveAdditionalPermissions {
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
pub permissions_preapproved: bool,
}
pub(super) fn implicit_granted_permissions(
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<&PermissionProfile>,
additional_permissions: Option<&AdditionalPermissionProfile>,
effective_additional_permissions: &EffectiveAdditionalPermissions,
) -> Option<PermissionProfile> {
) -> Option<AdditionalPermissionProfile> {
if !sandbox_permissions.uses_additional_permissions()
&& !matches!(sandbox_permissions, SandboxPermissions::RequireEscalated)
&& additional_permissions.is_none()
@@ -171,7 +171,7 @@ pub(super) async fn apply_granted_turn_permissions(
session: &Session,
cwd: &std::path::Path,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> EffectiveAdditionalPermissions {
if matches!(sandbox_permissions, SandboxPermissions::RequireEscalated) {
return EffectiveAdditionalPermissions {
@@ -213,8 +213,8 @@ pub(super) async fn apply_granted_turn_permissions(
}
fn permissions_are_preapproved(
effective_permissions: &PermissionProfile,
granted_permissions: PermissionProfile,
effective_permissions: &AdditionalPermissionProfile,
granted_permissions: AdditionalPermissionProfile,
cwd: &Path,
) -> bool {
let materialized_effective_permissions = intersect_permission_profiles(
@@ -233,9 +233,9 @@ mod tests {
use super::normalize_and_validate_additional_permissions;
use super::permissions_are_preapproved;
use crate::sandboxing::SandboxPermissions;
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;
@@ -248,8 +248,8 @@ mod tests {
use pretty_assertions::assert_eq;
use tempfile::tempdir;
fn network_permissions() -> PermissionProfile {
PermissionProfile {
fn network_permissions() -> AdditionalPermissionProfile {
AdditionalPermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
@@ -257,8 +257,8 @@ mod tests {
}
}
fn file_system_permissions(path: &std::path::Path) -> PermissionProfile {
PermissionProfile {
fn file_system_permissions(path: &std::path::Path) -> AdditionalPermissionProfile {
AdditionalPermissionProfile {
file_system: Some(FileSystemPermissions::from_read_write_roots(
/*read*/ None,
Some(vec![
@@ -350,7 +350,7 @@ mod tests {
#[test]
fn relative_deny_glob_grants_remain_preapproved_after_materialization() {
let cwd = tempdir().expect("tempdir");
let requested_permissions = PermissionProfile {
let requested_permissions = AdditionalPermissionProfile {
file_system: Some(FileSystemPermissions {
entries: vec![
FileSystemSandboxEntry {
+2 -2
View File
@@ -36,7 +36,7 @@ use crate::tools::runtimes::shell::ShellRuntime;
use crate::tools::runtimes::shell::ShellRuntimeBackend;
use crate::tools::sandboxing::ToolCtx;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::ExecCommandSource;
use codex_shell_command::is_safe_command::is_known_safe_command;
use codex_tools::ShellCommandBackendConfig;
@@ -79,7 +79,7 @@ struct RunExecLikeArgs {
tool_name: String,
exec_params: ExecParams,
hook_command: String,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
prefix_rule: Option<Vec<String>>,
session: Arc<crate::session::session::Session>,
turn: Arc<TurnContext>,
@@ -28,7 +28,7 @@ use crate::unified_exec::generate_chunk_id;
use codex_features::Feature;
use codex_otel::SessionTelemetry;
use codex_otel::TOOL_CALL_UNIFIED_EXEC_METRIC;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::TerminalInteractionEvent;
use codex_shell_command::is_safe_command::is_known_safe_command;
@@ -58,7 +58,7 @@ pub(crate) struct ExecCommandArgs {
#[serde(default)]
sandbox_permissions: SandboxPermissions,
#[serde(default)]
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
#[serde(default)]
justification: Option<String>,
#[serde(default)]
@@ -2,8 +2,8 @@ use super::*;
use crate::shell::default_user_shell;
use crate::tools::handlers::parse_arguments_with_base_path;
use crate::tools::handlers::resolve_workdir_base_path;
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_tools::UnifiedExecShellMode;
use codex_tools::ZshForkConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -23,7 +23,9 @@ use codex_protocol::error::CodexErr;
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;
@@ -33,7 +35,8 @@ use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::path::PathBuf;
@@ -45,7 +48,7 @@ pub struct ApplyPatchRequest {
pub file_paths: Vec<AbsolutePathBuf>,
pub changes: std::collections::HashMap<PathBuf, FileChange>,
pub exec_approval_requirement: ExecApprovalRequirement,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
pub permissions_preapproved: bool,
}
@@ -77,13 +80,19 @@ impl ApplyPatchRuntime {
return None;
}
let base_permissions = PermissionProfile::from_runtime_permissions(
let file_system_policy = effective_file_system_sandbox_policy(
attempt.file_system_policy,
attempt.network_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 =
merge_permission_profiles(Some(&base_permissions), req.additional_permissions.as_ref())
.unwrap_or(base_permissions);
Some(FileSystemSandboxContext {
permissions,
cwd: Some(attempt.sandbox_cwd.clone()),
@@ -1,6 +1,7 @@
use super::*;
use crate::tools::sandboxing::SandboxAttempt;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
@@ -9,7 +10,8 @@ use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use core_test_support::PathBufExt;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@@ -116,7 +118,7 @@ fn file_system_sandbox_context_uses_active_attempt() {
let path = std::env::temp_dir()
.join("apply-patch-runtime-attempt.txt")
.abs();
let additional_permissions = PermissionProfile {
let additional_permissions = AdditionalPermissionProfile {
network: None,
file_system: Some(FileSystemPermissions::from_read_write_roots(
Some(vec![path.clone()]),
@@ -154,15 +156,14 @@ fn file_system_sandbox_context_uses_active_attempt() {
let sandbox = ApplyPatchRuntime::file_system_sandbox_context_for_attempt(&req, &attempt)
.expect("sandbox context");
let base_permissions = PermissionProfile::from_runtime_permissions(
&file_system_policy,
let file_system_policy =
effective_file_system_sandbox_policy(&file_system_policy, Some(&additional_permissions));
let network_policy = effective_network_sandbox_policy(
NetworkSandboxPolicy::Restricted,
Some(&additional_permissions),
);
let Some(expected_permissions) =
merge_permission_profiles(Some(&base_permissions), Some(&additional_permissions))
else {
panic!("merged permissions should not be empty");
};
let expected_permissions =
PermissionProfile::from_runtime_permissions(&file_system_policy, network_policy);
assert_eq!(sandbox.permissions, expected_permissions);
assert_eq!(sandbox.cwd, Some(path.clone()));
assert_eq!(
+2 -2
View File
@@ -14,7 +14,7 @@ use codex_network_proxy::PROXY_ACTIVE_ENV_KEY;
use codex_network_proxy::PROXY_ENV_KEYS;
#[cfg(target_os = "macos")]
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_sandboxing::SandboxCommand;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
@@ -29,7 +29,7 @@ pub(crate) fn build_sandbox_command(
command: &[String],
cwd: &AbsolutePathBuf,
env: &HashMap<String, String>,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> Result<SandboxCommand, ToolError> {
let (program, args) = command
.split_first()
+3 -3
View File
@@ -35,7 +35,7 @@ use crate::tools::sandboxing::sandbox_override_for_first_attempt;
use crate::tools::sandboxing::with_cached_approval;
use codex_network_proxy::NetworkProxy;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
@@ -53,7 +53,7 @@ pub struct ShellRequest {
pub explicit_env_overrides: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
#[cfg(unix)]
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
@@ -97,7 +97,7 @@ pub(crate) struct ApprovalKey {
command: Vec<String>,
cwd: AbsolutePathBuf,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
}
impl ShellRuntime {
@@ -30,7 +30,9 @@ use codex_protocol::error::CodexErr;
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::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
@@ -311,7 +313,7 @@ struct CoreShellActionProvider {
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_permissions: SandboxPermissions,
approval_sandbox_permissions: SandboxPermissions,
prompt_permissions: Option<PermissionProfile>,
prompt_permissions: Option<AdditionalPermissionProfile>,
stopwatch: Stopwatch,
}
@@ -361,7 +363,7 @@ impl CoreShellActionProvider {
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
additional_permissions: Option<&PermissionProfile>,
additional_permissions: Option<&AdditionalPermissionProfile>,
) -> EscalationExecution {
match sandbox_permissions {
SandboxPermissions::UseDefault => EscalationExecution::TurnDefault,
@@ -373,10 +375,14 @@ impl CoreShellActionProvider {
EscalationExecution::Permissions(
EscalationPermissions::ResolvedPermissionProfile(
ResolvedPermissionProfile {
permission_profile: PermissionProfile::from_runtime_permissions(
file_system_sandbox_policy,
network_sandbox_policy,
),
permission_profile:
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(
sandbox_policy,
),
file_system_sandbox_policy,
network_sandbox_policy,
),
sandbox_policy: sandbox_policy.clone(),
},
),
@@ -392,7 +398,7 @@ impl CoreShellActionProvider {
argv: &[String],
workdir: &AbsolutePathBuf,
stopwatch: &Stopwatch,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> anyhow::Result<PromptDecision> {
let command = join_program_and_argv(program, argv);
let workdir = workdir.clone();
@@ -491,7 +497,7 @@ impl CoreShellActionProvider {
program: &AbsolutePathBuf,
argv: &[String],
workdir: &AbsolutePathBuf,
prompt_permissions: Option<PermissionProfile>,
prompt_permissions: Option<AdditionalPermissionProfile>,
escalation_execution: EscalationExecution,
decision_source: DecisionSource,
) -> anyhow::Result<EscalationDecision> {
@@ -757,7 +763,7 @@ struct PrepareSandboxedExecParams<'a> {
sandbox_policy: &'a SandboxPolicy,
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
additional_permissions: Option<PermissionProfile>,
additional_permissions: Option<AdditionalPermissionProfile>,
}
#[async_trait::async_trait]
@@ -16,6 +16,7 @@ use codex_execpolicy::PolicyParser;
use codex_execpolicy::RuleMatch;
use codex_hooks::Hooks;
use codex_hooks::HooksConfig;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
@@ -257,7 +258,7 @@ fn map_exec_result_preserves_stdout_and_stderr() {
#[test]
fn shell_request_escalation_execution_is_explicit() {
let requested_permissions = PermissionProfile {
let requested_permissions = AdditionalPermissionProfile {
file_system: Some(FileSystemPermissions::from_read_write_roots(
/*read*/ None,
Some(vec![
@@ -38,7 +38,7 @@ use crate::unified_exec::UnifiedExecProcessManager;
use codex_network_proxy::NetworkProxy;
use codex_protocol::error::CodexErr;
use codex_protocol::error::SandboxErr;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
@@ -61,7 +61,7 @@ pub struct UnifiedExecRequest {
pub network: Option<NetworkProxy>,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
#[cfg(unix)]
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
@@ -76,7 +76,7 @@ pub struct UnifiedExecApprovalKey {
pub cwd: AbsolutePathBuf,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
}
/// Runtime adapter that keeps policy and sandbox orchestration on the
+2 -2
View File
@@ -28,7 +28,7 @@ use std::sync::Arc;
use std::sync::Weak;
use codex_network_proxy::NetworkProxy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use rand::Rng;
use rand::rng;
@@ -96,7 +96,7 @@ pub(crate) struct ExecCommandRequest {
pub network: Option<NetworkProxy>,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
pub prefix_rule: Option<Vec<String>>,
@@ -5,8 +5,8 @@ use codex_core::config::Constrained;
use codex_core::sandboxing::SandboxPermissions;
use codex_features::Feature;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecApprovalRequestEvent;