mirror of
https://github.com/openai/codex.git
synced 2026-10-21 07:42:01 +00:00
Use turn environment permissions for context and discovery (#37040)
## What changed - Build filesystem and permission context from the primary turn environment's permission profile, working directory, and workspace roots, falling back to the thread context when no primary environment is available. - Build capability-discovery sandbox contexts from each environment's permission profile and treat discovery as restricted when any selected environment has restricted filesystem access. ## Testing - Add regression tests covering environment-specific capability discovery and permission context updates. GitOrigin-RevId: 6bbbd9b88aafa5e6583971ce4e427fc7557f2d93
This commit is contained in:
@@ -6,7 +6,6 @@ use crate::context::environment_context::NetworkContext;
|
||||
use crate::context::environment_context::push_xml_escaped_text;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -29,19 +28,17 @@ impl EnvironmentsState {
|
||||
environments: &TurnEnvironmentSnapshot,
|
||||
current_date: Option<String>,
|
||||
) -> Self {
|
||||
let workspace_roots = environments
|
||||
.primary()
|
||||
.map(TurnEnvironment::workspace_roots)
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
environments: environment_states(environments),
|
||||
current_date,
|
||||
timezone: turn_context.timezone.clone(),
|
||||
network: network_from_turn_context(turn_context),
|
||||
filesystem: Some(FileSystemContext::from_permission_profile(
|
||||
turn_context.config.permissions.permission_profile(),
|
||||
workspace_roots,
|
||||
)),
|
||||
filesystem: environments.primary().map(|environment| {
|
||||
FileSystemContext::from_permission_profile(
|
||||
environment.permission_profile(),
|
||||
environment.workspace_roots(),
|
||||
)
|
||||
}),
|
||||
subagents: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,10 +317,28 @@ impl Session {
|
||||
environments: &TurnEnvironmentSnapshot,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> Option<Arc<ExecutorCapabilityDiscoverySnapshot>> {
|
||||
let restricted_file_system = !config
|
||||
.permissions
|
||||
.file_system_sandbox_policy()
|
||||
.has_full_disk_read_access();
|
||||
// Capability roots can currently be selected independently of turn environments, so a
|
||||
// root may be ready when there is no primary `TurnEnvironment`. Keep using the thread
|
||||
// policy in that case so restricted discovery fails closed below. Once every selected
|
||||
// root belongs to a thread/environment attachment whose `EnvironmentConfig` is installed
|
||||
// before the root becomes ready, discovery can use the root owner's policy and this
|
||||
// fallback can be removed.
|
||||
let restricted_file_system = environments.primary().map_or_else(
|
||||
|| {
|
||||
!config
|
||||
.permissions
|
||||
.file_system_sandbox_policy()
|
||||
.has_full_disk_read_access()
|
||||
},
|
||||
|_| {
|
||||
environments.turn_environments().any(|environment| {
|
||||
!environment
|
||||
.permission_profile()
|
||||
.file_system_sandbox_policy()
|
||||
.has_full_disk_read_access()
|
||||
})
|
||||
},
|
||||
);
|
||||
if !restricted_file_system
|
||||
&& !config
|
||||
.features
|
||||
@@ -333,7 +351,7 @@ impl Session {
|
||||
.turn_environments()
|
||||
.map(|environment| {
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
config.permissions.permission_profile().clone(),
|
||||
environment.permission_profile().clone(),
|
||||
environment.cwd().clone(),
|
||||
);
|
||||
sandbox.workspace_roots = environment.workspace_roots().to_vec();
|
||||
|
||||
@@ -8591,6 +8591,52 @@ async fn conflicting_ready_environment_root_ids_keep_first_location() {
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn capability_discovery_uses_environment_permission_profile() {
|
||||
let (session, mut turn_context) = make_session_and_context().await;
|
||||
Arc::make_mut(&mut turn_context.config)
|
||||
.permissions
|
||||
.set_permission_profile(PermissionProfile::Disabled)
|
||||
.expect("unrestricted permission profile should be allowed");
|
||||
let mut environment = turn_context
|
||||
.environments
|
||||
.primary()
|
||||
.expect("primary environment")
|
||||
.clone();
|
||||
let mut file_system_policy = PermissionProfile::read_only().file_system_sandbox_policy();
|
||||
file_system_policy.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
missing_path_behavior: None,
|
||||
});
|
||||
environment.config.permission_profile =
|
||||
PermissionProfileSnapshot::legacy(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
let expected_sandbox = turn_context
|
||||
.file_system_sandbox_context(/*additional_permissions*/ None, &environment);
|
||||
let environment_id = environment.environment_id.clone();
|
||||
turn_context.environments.environments[0] = TurnEnvironmentState::Ready(environment);
|
||||
|
||||
let discovery = session
|
||||
.executor_capability_discovery_for_step(
|
||||
&turn_context.config,
|
||||
/*ready_selected_capability_roots*/ &[],
|
||||
&turn_context.environments,
|
||||
turn_context.windows_sandbox_level,
|
||||
)
|
||||
.await
|
||||
.expect("restricted environment should trigger capability discovery");
|
||||
|
||||
assert_eq!(
|
||||
discovery.sandbox_contexts().get(&environment_id),
|
||||
Some(&expected_sandbox)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn step_context_keeps_its_mcp_runtime_for_tools() -> anyhow::Result<()> {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
@@ -8786,6 +8832,70 @@ async fn record_context_updates_emits_environment_item_for_cwd_changes() {
|
||||
assert!(!environment_update.contains("<environments>"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_context_updates_use_environment_permission_profile_and_workspace_roots() {
|
||||
let (session, mut previous_context) = make_session_and_context().await;
|
||||
Arc::make_mut(&mut previous_context.config)
|
||||
.permissions
|
||||
.set_permission_profile(PermissionProfile::Disabled)
|
||||
.expect("unrestricted permission profile should be allowed");
|
||||
let mut previous_environment = previous_context
|
||||
.environments
|
||||
.primary()
|
||||
.expect("primary environment")
|
||||
.clone();
|
||||
previous_environment.config.permission_profile =
|
||||
PermissionProfileSnapshot::legacy(PermissionProfile::Disabled);
|
||||
previous_context.environments.environments[0] =
|
||||
TurnEnvironmentState::Ready(previous_environment);
|
||||
let previous_context = Arc::new(previous_context);
|
||||
let mut current_context = previous_context
|
||||
.with_model(
|
||||
previous_context.model_info.slug.clone(),
|
||||
&session.services.models_manager,
|
||||
)
|
||||
.await;
|
||||
let environment = current_context
|
||||
.environments
|
||||
.primary()
|
||||
.expect("primary environment")
|
||||
.clone();
|
||||
let cwd = environment.cwd().clone();
|
||||
let workspace_root = current_context.config.cwd.join("selected-workspace");
|
||||
let mut environment_config = environment.config;
|
||||
environment_config.permission_profile =
|
||||
PermissionProfileSnapshot::legacy(PermissionProfile::workspace_write());
|
||||
current_context.environments.environments[0] =
|
||||
TurnEnvironmentState::Ready(TurnEnvironment::new(
|
||||
environment.environment_id,
|
||||
environment.environment,
|
||||
cwd,
|
||||
vec![PathUri::from_abs_path(&workspace_root)],
|
||||
environment.shell,
|
||||
environment_config,
|
||||
));
|
||||
|
||||
let update_items =
|
||||
record_context_update_items(&session, previous_context, current_context).await;
|
||||
let permissions_update = developer_input_texts(&update_items)
|
||||
.into_iter()
|
||||
.find(|text| text.contains("<permissions instructions>"))
|
||||
.expect("permissions update should be emitted");
|
||||
assert!(
|
||||
permissions_update.contains(workspace_root.to_string_lossy().as_ref()),
|
||||
"selected workspace root should be visible in permissions: {permissions_update}"
|
||||
);
|
||||
let environment_update = user_input_texts(&update_items)
|
||||
.into_iter()
|
||||
.find(|text| text.contains("<environment_context>"))
|
||||
.expect("environment update should be emitted");
|
||||
assert!(
|
||||
environment_update.contains("<permission_profile type=\"managed\">")
|
||||
&& environment_update.contains(workspace_root.to_string_lossy().as_ref()),
|
||||
"selected environment permissions should be visible: {environment_update}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_context_updates_emits_environment_item_for_time_changes() {
|
||||
let (session, previous_context) = make_session_and_context().await;
|
||||
|
||||
@@ -114,7 +114,24 @@ impl Session {
|
||||
));
|
||||
world_state.add_section(AgentsMdState::new(step_context.loaded_agents_md.as_deref()));
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
let permission_profile = turn_context.permission_profile();
|
||||
let environment = step_context.environments.primary();
|
||||
let permission_profile = environment
|
||||
.map(|environment| {
|
||||
let workspace_roots = environment
|
||||
.workspace_roots()
|
||||
.iter()
|
||||
.filter_map(|workspace_root| workspace_root.to_abs_path().ok())
|
||||
.collect::<Vec<_>>();
|
||||
environment
|
||||
.permission_profile()
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&workspace_roots)
|
||||
})
|
||||
.unwrap_or_else(|| turn_context.permission_profile());
|
||||
#[allow(deprecated)]
|
||||
let cwd = environment
|
||||
.and_then(|environment| environment.cwd().to_abs_path().ok())
|
||||
.unwrap_or_else(|| turn_context.cwd.clone());
|
||||
let model_messages = turn_context.model_info.model_messages.as_ref();
|
||||
let exec_policy = self.services.exec_policy.current();
|
||||
world_state.add_section(PermissionsState::new(
|
||||
@@ -126,8 +143,7 @@ impl Session {
|
||||
model_messages.and_then(|messages| messages.permissions.as_ref()),
|
||||
),
|
||||
exec_policy.as_ref(),
|
||||
#[allow(deprecated)]
|
||||
&turn_context.cwd,
|
||||
&cwd,
|
||||
turn_context
|
||||
.config
|
||||
.features
|
||||
|
||||
Reference in New Issue
Block a user