mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
permissions: make profiles represent enforcement
This commit is contained in:
@@ -837,7 +837,8 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin
|
||||
"env": { "FOO": "override" }, // optional; merges into the server env and overrides matching names
|
||||
"size": { "rows": 40, "cols": 120 }, // optional; PTY size in character cells, only valid with tty=true
|
||||
"permissionProfile": { // optional; defaults to user config
|
||||
"fileSystem": { "entries": [
|
||||
"type": "managed",
|
||||
"fileSystem": { "type": "restricted", "entries": [
|
||||
{ "path": { "type": "special", "value": { "kind": "root" } }, "access": "read" },
|
||||
{ "path": { "type": "special", "value": { "kind": "current_working_directory" } }, "access": "write" }
|
||||
] },
|
||||
|
||||
@@ -122,7 +122,7 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
||||
use codex_protocol::items::parse_hook_prompt_message;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
|
||||
use codex_protocol::protocol::Event;
|
||||
@@ -2719,7 +2719,7 @@ fn request_permissions_response_from_client_result(
|
||||
strict_auto_review: false,
|
||||
});
|
||||
}
|
||||
let granted_permissions: CorePermissionProfile = response.permissions.into();
|
||||
let granted_permissions: CoreAdditionalPermissionProfile = response.permissions.into();
|
||||
let permissions = if granted_permissions.is_empty() {
|
||||
CoreRequestPermissionProfile::default()
|
||||
} else {
|
||||
|
||||
@@ -314,7 +314,6 @@ use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::ConversationAudioParams;
|
||||
@@ -2361,24 +2360,8 @@ impl CodexMessageProcessor {
|
||||
file_system_sandbox_policy: &mut FileSystemSandboxPolicy,
|
||||
configured_file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
) {
|
||||
if file_system_sandbox_policy.glob_scan_max_depth.is_none() {
|
||||
file_system_sandbox_policy.glob_scan_max_depth =
|
||||
configured_file_system_sandbox_policy.glob_scan_max_depth;
|
||||
}
|
||||
|
||||
for deny_entry in configured_file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.access == 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(configured_file_system_sandbox_policy);
|
||||
}
|
||||
|
||||
async fn command_exec_write(
|
||||
@@ -2800,10 +2783,8 @@ impl CodexMessageProcessor {
|
||||
/*has_in_progress_turn*/ false,
|
||||
);
|
||||
|
||||
let permission_profile = thread_response_permission_profile(
|
||||
&config_snapshot.sandbox_policy,
|
||||
config_snapshot.permission_profile,
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile);
|
||||
|
||||
let response = ThreadStartResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -4608,7 +4589,6 @@ impl CodexMessageProcessor {
|
||||
/*has_live_in_progress_turn*/ false,
|
||||
);
|
||||
let permission_profile = thread_response_permission_profile(
|
||||
&session_configured.sandbox_policy,
|
||||
codex_thread.config_snapshot().await.permission_profile,
|
||||
);
|
||||
|
||||
@@ -5279,7 +5259,6 @@ impl CodexMessageProcessor {
|
||||
/*has_in_progress_turn*/ false,
|
||||
);
|
||||
let permission_profile = thread_response_permission_profile(
|
||||
&session_configured.sandbox_policy,
|
||||
forked_thread.config_snapshot().await.permission_profile,
|
||||
);
|
||||
|
||||
@@ -8794,8 +8773,7 @@ async fn handle_pending_thread_resume_request(
|
||||
..
|
||||
} = pending.config_snapshot;
|
||||
let instruction_sources = pending.instruction_sources;
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(&sandbox_policy, permission_profile);
|
||||
let permission_profile = thread_response_permission_profile(permission_profile);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -9925,17 +9903,9 @@ fn with_thread_spawn_agent_metadata(
|
||||
}
|
||||
|
||||
fn thread_response_permission_profile(
|
||||
sandbox_policy: &codex_protocol::protocol::SandboxPolicy,
|
||||
permission_profile: codex_protocol::models::PermissionProfile,
|
||||
) -> Option<codex_app_server_protocol::PermissionProfile> {
|
||||
match sandbox_policy {
|
||||
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
| codex_protocol::protocol::SandboxPolicy::ReadOnly { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
Some(permission_profile.into())
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. } => None,
|
||||
}
|
||||
Some(permission_profile.into())
|
||||
}
|
||||
|
||||
fn requested_permissions_trust_project(overrides: &ConfigOverrides, cwd: &Path) -> bool {
|
||||
@@ -10252,6 +10222,7 @@ mod tests {
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -10457,25 +10428,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_response_permission_profile_omits_external_sandbox() {
|
||||
fn thread_response_permission_profile_preserves_enforcement() {
|
||||
let cwd = test_path_buf("/tmp").abs();
|
||||
let profile = codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
cwd.as_path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(
|
||||
let full_access_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
cwd.as_path(),
|
||||
);
|
||||
let external_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::ExternalSandbox {
|
||||
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
|
||||
},
|
||||
profile.clone(),
|
||||
),
|
||||
None
|
||||
cwd.as_path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(external_profile.clone()),
|
||||
Some(external_profile.into())
|
||||
);
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(&SandboxPolicy::DangerFullAccess, profile.clone()),
|
||||
Some(profile.into())
|
||||
thread_response_permission_profile(full_access_profile.clone()),
|
||||
Some(full_access_profile.into())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -256,17 +256,18 @@ async fn command_exec_permission_profile_cwd_uses_command_cwd() -> Result<()> {
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let mut permission_profile = root_read_only_permission_profile();
|
||||
permission_profile
|
||||
.file_system
|
||||
.as_mut()
|
||||
.expect("root read-only helper should include filesystem permissions")
|
||||
.entries
|
||||
.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
let PermissionProfile::Managed { file_system, .. } = &mut permission_profile else {
|
||||
panic!("root read-only helper should use managed permissions");
|
||||
};
|
||||
let PermissionProfileFileSystemPermissions::Restricted { entries, .. } = file_system else {
|
||||
panic!("root read-only helper should use restricted filesystem permissions");
|
||||
};
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
|
||||
let command_request_id = mcp
|
||||
.send_command_exec_request(CommandExecParams {
|
||||
@@ -1061,11 +1062,9 @@ fn decode_delta_notification(
|
||||
}
|
||||
|
||||
fn root_read_only_permission_profile() -> PermissionProfile {
|
||||
PermissionProfile {
|
||||
network: Some(PermissionProfileNetworkPermissions {
|
||||
enabled: Some(false),
|
||||
}),
|
||||
file_system: Some(PermissionProfileFileSystemPermissions {
|
||||
PermissionProfile::Managed {
|
||||
network: PermissionProfileNetworkPermissions { enabled: false },
|
||||
file_system: PermissionProfileFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
@@ -1073,7 +1072,7 @@ fn root_read_only_permission_profile() -> PermissionProfile {
|
||||
access: FileSystemAccessMode::Read,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ use codex_app_server_protocol::PatchApplyStatus;
|
||||
use codex_app_server_protocol::PatchChangeKind;
|
||||
use codex_app_server_protocol::PermissionProfile;
|
||||
use codex_app_server_protocol::PermissionProfileFileSystemPermissions;
|
||||
use codex_app_server_protocol::PermissionProfileNetworkPermissions;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::ServerRequestResolvedNotification;
|
||||
@@ -779,9 +780,9 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
text: "Hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
permission_profile: Some(PermissionProfile {
|
||||
network: None,
|
||||
file_system: Some(PermissionProfileFileSystemPermissions {
|
||||
permission_profile: Some(PermissionProfile::Managed {
|
||||
network: PermissionProfileNetworkPermissions { enabled: false },
|
||||
file_system: PermissionProfileFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: unsupported_write_root,
|
||||
@@ -789,7 +790,7 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
access: FileSystemAccessMode::Write,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user