mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
permissions: canonicalize workspace_roots and danger-full-access names
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
@@ -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),
|
||||
@@ -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?;
|
||||
@@ -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),
|
||||
@@ -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 {
|
||||
@@ -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()),
|
||||
@@ -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 {
|
||||
@@ -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(())
|
||||
@@ -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,
|
||||
@@ -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") {
|
||||
@@ -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 {
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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")?;
|
||||
@@ -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!(
|
||||
@@ -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")?;
|
||||
@@ -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,
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,6 +23,9 @@ use codex_network_proxy::NetworkProxyConfig;
|
||||
#[cfg(test)]
|
||||
use codex_network_proxy::NetworkUnixSocketPermission as ProxyNetworkUnixSocketPermission;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
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::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -34,9 +37,10 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use super::ProjectConfig;
|
||||
|
||||
pub(crate) const BUILT_IN_READ_ONLY_PROFILE: &str = ":read-only";
|
||||
pub(crate) const BUILT_IN_WORKSPACE_PROFILE: &str = ":workspace";
|
||||
pub(crate) const BUILT_IN_DANGER_NO_SANDBOX_PROFILE: &str = ":danger-no-sandbox";
|
||||
pub(crate) const BUILT_IN_READ_ONLY_PROFILE: &str = BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
|
||||
pub(crate) const BUILT_IN_WORKSPACE_PROFILE: &str = BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
pub(crate) const BUILT_IN_DANGER_FULL_ACCESS_PROFILE: &str =
|
||||
BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||
|
||||
pub(crate) fn default_builtin_permission_profile_name(
|
||||
active_project: &ProjectConfig,
|
||||
@@ -56,7 +60,7 @@ pub(crate) fn is_builtin_permission_profile_name(profile_name: &str) -> bool {
|
||||
profile_name,
|
||||
BUILT_IN_READ_ONLY_PROFILE
|
||||
| BUILT_IN_WORKSPACE_PROFILE
|
||||
| BUILT_IN_DANGER_NO_SANDBOX_PROFILE
|
||||
| BUILT_IN_DANGER_FULL_ACCESS_PROFILE
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@ pub(crate) fn builtin_permission_profile(
|
||||
),
|
||||
None => PermissionProfile::workspace_write(),
|
||||
}),
|
||||
BUILT_IN_DANGER_NO_SANDBOX_PROFILE => Some(PermissionProfile::Disabled),
|
||||
BUILT_IN_DANGER_FULL_ACCESS_PROFILE => Some(PermissionProfile::Disabled),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -489,7 +493,7 @@ fn compile_scoped_filesystem_pattern(
|
||||
|
||||
match parse_special_path(path) {
|
||||
Some(FileSystemSpecialPath::ProjectRoots { .. }) => {
|
||||
// `:project_roots` is represented as a special path, but current
|
||||
// `:workspace_roots` is represented as a special path, but current
|
||||
// filesystem-policy resolution defines it relative to the session
|
||||
// cwd. Use the same policy cwd here so glob entries and exact
|
||||
// scoped entries resolve consistently.
|
||||
@@ -616,7 +620,7 @@ fn parse_special_path(path: &str) -> Option<FileSystemSpecialPath> {
|
||||
match path {
|
||||
":root" => Some(FileSystemSpecialPath::Root),
|
||||
":minimal" => Some(FileSystemSpecialPath::Minimal),
|
||||
":project_roots" => Some(FileSystemSpecialPath::project_roots(/*subpath*/ None)),
|
||||
":workspace_roots" => Some(FileSystemSpecialPath::project_roots(/*subpath*/ None)),
|
||||
":tmpdir" => Some(FileSystemSpecialPath::Tmpdir),
|
||||
_ if path.starts_with(':') => {
|
||||
Some(FileSystemSpecialPath::unknown(path, /*subpath*/ None))
|
||||
|
||||
@@ -289,7 +289,7 @@ fn read_write_glob_warnings_skip_supported_deny_read_globs_and_trailing_subpaths
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
),
|
||||
(
|
||||
":project_roots".to_string(),
|
||||
":workspace_roots".to_string(),
|
||||
FilesystemPermissionToml::Scoped(BTreeMap::from([
|
||||
("**/*.env".to_string(), FileSystemAccessMode::None),
|
||||
("docs/**".to_string(), FileSystemAccessMode::Read),
|
||||
@@ -303,7 +303,7 @@ fn read_write_glob_warnings_skip_supported_deny_read_globs_and_trailing_subpaths
|
||||
unsupported_read_write_glob_paths(&filesystem),
|
||||
vec![
|
||||
"/tmp/**/*.log".to_string(),
|
||||
":project_roots/src/**/*.rs".to_string()
|
||||
":workspace_roots/src/**/*.rs".to_string()
|
||||
],
|
||||
"`none` glob patterns are supported as deny-read rules; only `read`/`write` globs should warn"
|
||||
);
|
||||
@@ -314,7 +314,7 @@ fn unreadable_globstar_warning_is_suppressed_when_scan_depth_is_configured() {
|
||||
let filesystem = FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":project_roots".to_string(),
|
||||
":workspace_roots".to_string(),
|
||||
FilesystemPermissionToml::Scoped(BTreeMap::from([
|
||||
("**/*.env".to_string(), FileSystemAccessMode::None),
|
||||
("*.pem".to_string(), FileSystemAccessMode::None),
|
||||
@@ -324,7 +324,7 @@ fn unreadable_globstar_warning_is_suppressed_when_scan_depth_is_configured() {
|
||||
|
||||
assert_eq!(
|
||||
unbounded_unreadable_globstar_paths(&filesystem),
|
||||
vec![":project_roots/**/*.env".to_string()]
|
||||
vec![":workspace_roots/**/*.env".to_string()]
|
||||
);
|
||||
|
||||
let configured_filesystem = FilesystemPermissionsToml {
|
||||
@@ -362,7 +362,7 @@ fn read_write_trailing_glob_suffix_compiles_as_subpath() -> std::io::Result<()>
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":project_roots".to_string(),
|
||||
":workspace_roots".to_string(),
|
||||
FilesystemPermissionToml::Scoped(BTreeMap::from([(
|
||||
"docs/**".to_string(),
|
||||
FileSystemAccessMode::Read,
|
||||
|
||||
@@ -74,7 +74,7 @@ commands that would enter the bubblewrap path.
|
||||
[permissions.workspace.filesystem]
|
||||
glob_scan_max_depth = 2
|
||||
|
||||
[permissions.workspace.filesystem.":project_roots"]
|
||||
[permissions.workspace.filesystem.":workspace_roots"]
|
||||
"**/*.env" = "none"
|
||||
```
|
||||
|
||||
|
||||
@@ -297,6 +297,15 @@ impl ManagedFileSystemPermissions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserved identifier for the built-in read-only permission profile.
|
||||
pub const BUILT_IN_PERMISSION_PROFILE_READ_ONLY: &str = ":read-only";
|
||||
|
||||
/// Reserved identifier for the built-in workspace-write permission profile.
|
||||
pub const BUILT_IN_PERMISSION_PROFILE_WORKSPACE: &str = ":workspace";
|
||||
|
||||
/// Reserved identifier for the built-in full-access permission profile.
|
||||
pub const BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS: &str = ":danger-full-access";
|
||||
|
||||
/// Canonical active runtime permissions for a conversation, turn, or command.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
|
||||
@@ -1592,6 +1592,8 @@ mod tests {
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
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_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
@@ -1612,7 +1614,7 @@ mod tests {
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(temp_dir.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
|
||||
..ConfigOverrides::default()
|
||||
})
|
||||
.build()
|
||||
@@ -1657,7 +1659,8 @@ mod tests {
|
||||
#[test]
|
||||
fn embedded_turn_permissions_use_active_profile_selection() {
|
||||
let cwd = test_path_buf("/workspace/project").abs();
|
||||
let active_permission_profile = ActivePermissionProfile::new(":workspace");
|
||||
let active_permission_profile =
|
||||
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE);
|
||||
let expected_permissions =
|
||||
permissions_selection_from_active_profile(active_permission_profile.clone());
|
||||
|
||||
@@ -1698,7 +1701,9 @@ mod tests {
|
||||
|
||||
let (sandbox_policy, permissions) = turn_permissions_overrides(
|
||||
&PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new(":read-only")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
|
||||
)),
|
||||
cwd.as_path(),
|
||||
ThreadParamsMode::Remote,
|
||||
);
|
||||
|
||||
@@ -956,7 +956,7 @@ fn special_path_label(value: &FileSystemSpecialPath) -> String {
|
||||
match value {
|
||||
FileSystemSpecialPath::Root => ":root".to_string(),
|
||||
FileSystemSpecialPath::Minimal => ":minimal".to_string(),
|
||||
FileSystemSpecialPath::ProjectRoots { subpath } => path_label(":project_roots", subpath),
|
||||
FileSystemSpecialPath::ProjectRoots { subpath } => path_label(":workspace_roots", subpath),
|
||||
FileSystemSpecialPath::Tmpdir => ":tmpdir".to_string(),
|
||||
FileSystemSpecialPath::SlashTmp => "/tmp".to_string(),
|
||||
FileSystemSpecialPath::Unknown { path, subpath } => path_label(path, subpath),
|
||||
@@ -1771,6 +1771,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn additional_permissions_rule_uses_workspace_roots_label() {
|
||||
let additional_permissions = AdditionalPermissionProfile {
|
||||
network: None,
|
||||
file_system: Some(AdditionalFileSystemPermissions {
|
||||
read: None,
|
||||
write: None,
|
||||
entries: Some(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots {
|
||||
subpath: Some(".git".into()),
|
||||
},
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]),
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format_additional_permissions_rule(&additional_permissions),
|
||||
Some("read `:workspace_roots/.git`".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_session_shortcut_submits_session_scope() {
|
||||
let (tx, mut rx) = unbounded_channel::<AppEvent>();
|
||||
|
||||
@@ -16,6 +16,9 @@ use codex_protocol::account::PlanType;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
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::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_utils_sandbox_summary::summarize_permission_profile;
|
||||
@@ -587,7 +590,7 @@ fn status_permissions_label(
|
||||
count => format!(" + {count} writable roots"),
|
||||
};
|
||||
match active_id {
|
||||
Some(":read-only") => {
|
||||
Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY) => {
|
||||
let label = if sandbox == "read-only with network access" {
|
||||
"Read Only with network access"
|
||||
} else {
|
||||
@@ -595,14 +598,16 @@ fn status_permissions_label(
|
||||
};
|
||||
return format!("{label}{modification_suffix} ({approval})");
|
||||
}
|
||||
Some(":workspace") => match sandbox {
|
||||
Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE) => match sandbox {
|
||||
"workspace" => return format!("Workspace{modification_suffix} ({approval})"),
|
||||
"workspace with network access" => {
|
||||
return format!("Workspace with network access{modification_suffix} ({approval})");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(":danger-no-sandbox") if permission_profile == &PermissionProfile::Disabled => {
|
||||
Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS)
|
||||
if permission_profile == &PermissionProfile::Disabled =>
|
||||
{
|
||||
return if approval_policy == AskForApproval::Never {
|
||||
"Full Access".to_string()
|
||||
} else {
|
||||
|
||||
@@ -31,6 +31,8 @@ use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -296,7 +298,9 @@ async fn status_permissions_named_read_only_profile_shows_builtin_label() {
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new(":read-only")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
|
||||
)),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
@@ -327,11 +331,12 @@ async fn status_permissions_read_only_profile_shows_additional_writable_roots()
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
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,
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
@@ -355,7 +360,9 @@ async fn status_permissions_named_workspace_profile_shows_builtin_label() {
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
@@ -379,7 +386,9 @@ async fn status_permissions_workspace_auto_review_shows_reviewer_label() {
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
@@ -409,11 +418,12 @@ async fn status_permissions_named_profile_shows_additional_writable_roots() {
|
||||
/*exclude_slash_tmp*/ false,
|
||||
),
|
||||
Some(
|
||||
ActivePermissionProfile::new(":workspace").with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot {
|
||||
path: extra_root,
|
||||
},
|
||||
]),
|
||||
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE)
|
||||
.with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot {
|
||||
path: extra_root,
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
@@ -442,7 +452,9 @@ async fn status_permissions_broadened_workspace_profile_shows_builtin_label() {
|
||||
/*exclude_tmpdir_env_var*/ false,
|
||||
/*exclude_slash_tmp*/ false,
|
||||
),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
@@ -578,7 +590,9 @@ async fn status_snapshot_shows_auto_review_permissions() {
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user