Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use super::*;
use crate::error_code::method_not_found;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;

const THREAD_LIST_DEFAULT_LIMIT: usize = 25;
const THREAD_LIST_MAX_LIMIT: usize = 100;
Expand Down Expand Up @@ -3903,7 +3905,9 @@ fn requested_permissions_trust_project(overrides: &ConfigOverrides, cwd: &Path)

if matches!(
overrides.default_permissions.as_deref(),
Some(":workspace" | ":danger-no-sandbox")
Some(
BUILT_IN_PERMISSION_PROFILE_WORKSPACE | BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS
)
) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ mod thread_processor_behavior_tests {
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_protocol::ThreadId;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
Expand Down Expand Up @@ -467,14 +470,16 @@ mod thread_processor_behavior_tests {
));
assert!(requested_permissions_trust_project(
&ConfigOverrides {
default_permissions: Some(":workspace".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
..Default::default()
},
cwd.as_path()
));
assert!(requested_permissions_trust_project(
&ConfigOverrides {
default_permissions: Some(":danger-no-sandbox".to_string()),
default_permissions: Some(
BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()
),
..Default::default()
},
cwd.as_path()
Expand All @@ -488,7 +493,7 @@ mod thread_processor_behavior_tests {
));
assert!(!requested_permissions_trust_project(
&ConfigOverrides {
default_permissions: Some(":read-only".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY.to_string()),
..Default::default()
},
cwd.as_path()
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server/tests/suite/v2/command_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ async fn command_exec_permission_profile_project_roots_use_command_cwd() -> Resu
);
assert!(
!codex_home.path().join("parent.txt").exists(),
"permissionProfile :project_roots write should not grant the server cwd when command cwd differs"
"permissionProfile :workspace_roots write should not grant the server cwd when command cwd differs"
);

Ok(())
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/app-server/tests/suite/v2/turn_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::Settings;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;
use core_test_support::responses;
Expand Down Expand Up @@ -780,7 +781,7 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn()
text_elements: Vec::new(),
}],
permissions: Some(PermissionProfileSelectionParams::Profile {
id: ":danger-no-sandbox".to_string(),
id: BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string(),
modifications: None,
}),
..Default::default()
Expand Down
94 changes: 68 additions & 26 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ use codex_models_manager::bundled_models_response;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::ActivePermissionProfileModification;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
Expand Down Expand Up @@ -718,7 +721,7 @@ default_permissions = "workspace"
[permissions.workspace.filesystem]
":minimal" = "read"

[permissions.workspace.filesystem.":project_roots"]
[permissions.workspace.filesystem.":workspace_roots"]
"." = "write"
"docs" = "read"

Expand Down Expand Up @@ -749,7 +752,7 @@ allow_upstream_proxy = false
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
),
(
":project_roots".to_string(),
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
(".".to_string(), FileSystemAccessMode::Write),
("docs".to_string(), FileSystemAccessMode::Read),
Expand Down Expand Up @@ -1303,7 +1306,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
),
(
":project_roots".to_string(),
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
(".".to_string(), FileSystemAccessMode::Write),
("docs".to_string(), FileSystemAccessMode::Read),
Expand Down Expand Up @@ -1603,7 +1606,7 @@ async fn permission_profile_override_preserves_configured_network_policy_without
}

#[tokio::test]
async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::io::Result<()> {
async fn workspace_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
tokio::fs::write(cwd.path().join(".git"), "gitdir: nowhere").await?;
Expand All @@ -1618,7 +1621,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
filesystem: Some(FilesystemPermissionsToml {
glob_scan_max_depth: Some(2),
entries: BTreeMap::from([(
":project_roots".to_string(),
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([
(".".to_string(), FileSystemAccessMode::Write),
("**/*.env".to_string(), FileSystemAccessMode::None),
Expand Down Expand Up @@ -1728,7 +1731,7 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl

let config = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some(":workspace".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
..Default::default()
},
ConfigOverrides {
Expand All @@ -1746,7 +1749,7 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
.active_permission_profile()
.as_ref()
.map(|active| active.id.as_str()),
Some(":workspace")
Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE)
);
assert!(
policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
Expand All @@ -1769,7 +1772,7 @@ async fn default_permissions_read_only_applies_additional_writable_roots_as_modi

let config = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some(":read-only".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY.to_string()),
..Default::default()
},
ConfigOverrides {
Expand All @@ -1789,9 +1792,13 @@ async fn default_permissions_read_only_applies_additional_writable_roots_as_modi
assert_eq!(
config.permissions.active_permission_profile(),
Some(
ActivePermissionProfile::new(":read-only").with_modifications(vec![
ActivePermissionProfileModification::AdditionalWritableRoot { path: extra_root },
])
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_READ_ONLY).with_modifications(
vec![
ActivePermissionProfileModification::AdditionalWritableRoot {
path: extra_root,
},
]
)
)
);
Ok(())
Expand All @@ -1806,7 +1813,7 @@ async fn explicit_builtin_workspace_profile_ignores_legacy_workspace_write_setti

let config = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some(":workspace".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
writable_roots: vec![extra_root.path().abs()],
network_access: true,
Expand Down Expand Up @@ -1871,9 +1878,9 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::
.as_ref()
.map(|active| active.id.as_str()),
Some(if cfg!(target_os = "windows") {
":read-only"
BUILT_IN_PERMISSION_PROFILE_READ_ONLY
} else {
":workspace"
BUILT_IN_PERMISSION_PROFILE_WORKSPACE
})
);
if cfg!(target_os = "windows") {
Expand Down Expand Up @@ -2042,13 +2049,13 @@ async fn empty_config_defaults_to_builtin_read_only_without_trust_decision() ->
}

#[tokio::test]
async fn default_permissions_can_select_builtin_no_sandbox_profile() -> std::io::Result<()> {
async fn default_permissions_can_select_builtin_full_access_profile() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;

let config = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some(":danger-no-sandbox".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()),
..Default::default()
},
ConfigOverrides {
Expand All @@ -2069,7 +2076,33 @@ async fn default_permissions_can_select_builtin_no_sandbox_profile() -> std::io:
.active_permission_profile()
.as_ref()
.map(|active| active.id.as_str()),
Some(":danger-no-sandbox")
Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS)
);
Ok(())
}

#[tokio::test]
async fn legacy_danger_no_sandbox_is_rejected() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;

let err = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some(":danger-no-sandbox".to_string()),
..Default::default()
},
ConfigOverrides {
cwd: Some(cwd.path().to_path_buf()),
..Default::default()
},
codex_home.abs(),
)
.await
.expect_err("legacy full-access alias should be rejected");

assert_eq!(
err.to_string(),
"default_permissions refers to unknown built-in profile `:danger-no-sandbox`"
);
Ok(())
}
Expand Down Expand Up @@ -2194,7 +2227,8 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
}

#[tokio::test]
async fn permissions_profiles_reject_nested_entries_for_non_project_roots() -> std::io::Result<()> {
async fn permissions_profiles_reject_nested_entries_for_non_workspace_roots() -> std::io::Result<()>
{
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
Expand Down Expand Up @@ -2229,7 +2263,7 @@ async fn permissions_profiles_reject_nested_entries_for_non_project_roots() -> s
codex_home.abs(),
)
.await
.expect_err("nested entries outside :project_roots should be rejected");
.expect_err("nested entries outside :workspace_roots should be rejected");

assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
Expand Down Expand Up @@ -2396,7 +2430,7 @@ async fn permissions_profiles_allow_empty_filesystem_with_warning() -> std::io::
}

#[tokio::test]
async fn permissions_profiles_reject_project_root_parent_traversal() -> std::io::Result<()> {
async fn permissions_profiles_reject_workspace_root_parent_traversal() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
Expand All @@ -2411,7 +2445,7 @@ async fn permissions_profiles_reject_project_root_parent_traversal() -> std::io:
filesystem: Some(FilesystemPermissionsToml {
glob_scan_max_depth: None,
entries: BTreeMap::from([(
":project_roots".to_string(),
":workspace_roots".to_string(),
FilesystemPermissionToml::Scoped(BTreeMap::from([(
"../sibling".to_string(),
FileSystemAccessMode::Read,
Expand Down Expand Up @@ -7269,7 +7303,9 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
permissions: Permissions {
approval_policy: Constrained::allow_any(AskForApproval::Never),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
active_permission_profile: Some(ActivePermissionProfile::new(
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
)),
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
Expand Down Expand Up @@ -7716,7 +7752,9 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
permissions: Permissions {
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
active_permission_profile: Some(ActivePermissionProfile::new(
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
)),
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
Expand Down Expand Up @@ -7877,7 +7915,9 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
permissions: Permissions {
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
active_permission_profile: Some(ActivePermissionProfile::new(
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
)),
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
Expand Down Expand Up @@ -8023,7 +8063,9 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
permissions: Permissions {
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
active_permission_profile: Some(ActivePermissionProfile::new(
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
)),
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
Expand Down Expand Up @@ -8990,7 +9032,7 @@ async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.harness_overrides(ConfigOverrides {
default_permissions: Some(":danger-no-sandbox".to_string()),
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()),
..Default::default()
})
.cloud_requirements(CloudRequirementsLoader::new(async move {
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2522,7 +2522,7 @@ impl Config {
// Keep legacy behavior for extra writable roots while storing
// the result as the canonical permission profile. Explicit
// extra roots are concrete paths, so their metadata carveouts
// are also concrete rather than symbolic `:project_roots`
// are also concrete rather than symbolic `:workspace_roots`
// entries.
file_system_sandbox_policy = file_system_sandbox_policy
.with_additional_legacy_workspace_writable_roots(&additional_writable_roots);
Expand Down
Loading
Loading