config: default untrusted projects to read-only permissions

This commit is contained in:
Michael Bolin
2026-06-02 14:37:06 -07:00
parent bcaf711e72
commit 97b7ec7534
4 changed files with 61 additions and 40 deletions

View File

@@ -735,11 +735,11 @@ impl ConfigToml {
.or(if sandbox_mode_was_explicit {
None
} else {
// If no sandbox_mode is set but this directory has a trust decision,
// default to workspace-write except on unsandboxed Windows where we
// If no sandbox_mode is set for a trusted directory, default
// to workspace-write except on unsandboxed Windows where we
// default to read-only.
active_project.and_then(|p| {
if p.is_trusted() || p.is_untrusted() {
if p.is_trusted() {
if cfg!(target_os = "windows")
&& windows_sandbox_level == WindowsSandboxLevel::Disabled
{

View File

@@ -1423,11 +1423,7 @@ async fn system_allowed_permissions_keep_builtin_permission_fallbacks() -> anyho
),
(
Some(TrustLevel::Untrusted),
if cfg!(target_os = "windows") {
BUILT_IN_PERMISSION_PROFILE_READ_ONLY
} else {
BUILT_IN_PERMISSION_PROFILE_WORKSPACE
},
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
),
(None, BUILT_IN_PERMISSION_PROFILE_READ_ONLY),
] {

View File

@@ -2549,6 +2549,50 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::
Ok(())
}
#[tokio::test]
async fn empty_config_defaults_to_builtin_read_only_for_untrusted_project() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let project_key = cwd.path().to_string_lossy().to_string();
let config = Config::load_from_base_config_with_overrides(
ConfigToml {
projects: Some(HashMap::from([(
project_key,
ProjectConfig {
trust_level: Some(TrustLevel::Untrusted),
},
)])),
..Default::default()
},
ConfigOverrides {
cwd: Some(cwd.path().to_path_buf()),
..Default::default()
},
codex_home.abs(),
)
.await?;
let policy = config.permissions.file_system_sandbox_policy();
assert_eq!(
config
.permissions
.active_permission_profile()
.as_ref()
.map(|active| active.id.as_str()),
Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY)
);
assert!(
policy.can_read_path_with_cwd(cwd.path(), cwd.path()),
"expected untrusted project fallback to allow reads, policy: {policy:?}"
);
assert!(
!policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
"expected untrusted project fallback to use :read-only, policy: {policy:?}"
);
Ok(())
}
#[tokio::test]
async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_settings()
-> std::io::Result<()> {
@@ -8566,7 +8610,7 @@ async fn test_load_config_rejects_legacy_ollama_chat_provider_with_helpful_error
}
#[tokio::test]
async fn test_untrusted_project_gets_workspace_write_sandbox() -> anyhow::Result<()> {
async fn test_untrusted_project_gets_read_only_sandbox() -> anyhow::Result<()> {
let config_with_untrusted = r#"
[projects."/tmp/test"]
trust_level = "untrusted"
@@ -8587,18 +8631,10 @@ trust_level = "untrusted"
)
.await;
// Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)
if cfg!(target_os = "windows") {
assert!(
matches!(resolution, SandboxPolicy::ReadOnly { .. }),
"Expected ReadOnly on Windows, got {resolution:?}"
);
} else {
assert!(
matches!(resolution, SandboxPolicy::WorkspaceWrite { .. }),
"Expected WorkspaceWrite for untrusted project, got {resolution:?}"
);
}
assert!(
matches!(resolution, SandboxPolicy::ReadOnly { .. }),
"Expected ReadOnly for untrusted project, got {resolution:?}"
);
Ok(())
}
@@ -8955,24 +8991,13 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow:
"Expected UnlessTrusted approval policy for untrusted project"
);
// Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows)
if cfg!(target_os = "windows") {
assert!(
matches!(
&config.legacy_sandbox_policy(),
SandboxPolicy::ReadOnly { .. }
),
"Expected ReadOnly on Windows"
);
} else {
assert!(
matches!(
&config.legacy_sandbox_policy(),
SandboxPolicy::WorkspaceWrite { .. }
),
"Expected WorkspaceWrite sandbox for untrusted project"
);
}
assert!(
matches!(
&config.legacy_sandbox_policy(),
SandboxPolicy::ReadOnly { .. }
),
"Expected ReadOnly sandbox for untrusted project"
);
Ok(())
}

View File

@@ -49,7 +49,7 @@ pub(crate) fn default_builtin_permission_profile_name(
active_project: &ProjectConfig,
windows_sandbox_level: WindowsSandboxLevel,
) -> &'static str {
if (active_project.is_trusted() || active_project.is_untrusted())
if active_project.is_trusted()
&& !(cfg!(target_os = "windows") && windows_sandbox_level == WindowsSandboxLevel::Disabled)
{
BUILT_IN_WORKSPACE_PROFILE