File size: 6,461 Bytes
52a9af3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | use anyhow::Context;
use anyhow::Result;
use codex_config::CloudConfigBundleLoader;
use codex_config::test_support::CloudConfigBundleFixture;
use codex_core::CodexThreadSettingsOverrides;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_target_windows;
use core_test_support::submit_thread_settings;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use std::sync::Arc;
use std::sync::RwLock;
use tempfile::TempDir;
#[tokio::test]
async fn refreshed_cloud_bundle_updates_later_sessions() -> Result<()> {
let server = start_mock_server().await;
let home = Arc::new(TempDir::new()?);
let initial_bundle = CloudConfigBundleFixture::enterprise_requirement(
r#"allowed_approval_policies = ["never"]"#,
)
.add_enterprise_config(r#"developer_instructions = "initial managed instructions""#)
.into_bundle();
let latest = Arc::new(RwLock::new(initial_bundle));
let getter_latest = Arc::clone(&latest);
let loader = CloudConfigBundleLoader::from_getter(move || {
let latest = Arc::clone(&getter_latest);
async move { Ok(Some(latest.read().expect("bundle state lock").clone())) }
});
let mut initial_builder = test_codex()
.with_home(Arc::clone(&home))
.with_cloud_config_bundle(loader.clone());
let initial = initial_builder.build_with_auto_env(&server).await?;
assert_eq!(
initial.session_configured.approval_policy,
AskForApproval::Never
);
assert_eq!(
initial
.codex
.config()
.await
.developer_instructions
.as_deref(),
Some("initial managed instructions")
);
*latest.write().expect("bundle state lock") = CloudConfigBundleFixture::enterprise_requirement(
r#"allowed_approval_policies = ["on-request"]"#,
)
.add_enterprise_config(r#"developer_instructions = "refreshed managed instructions""#)
.into_bundle();
let mut refreshed_builder = test_codex()
.with_home(home)
.with_cloud_config_bundle(loader);
let refreshed = refreshed_builder.build_with_auto_env(&server).await?;
assert_eq!(
refreshed.session_configured.approval_policy,
AskForApproval::OnRequest
);
assert_eq!(
refreshed
.codex
.config()
.await
.developer_instructions
.as_deref(),
Some("refreshed managed instructions")
);
Ok(())
}
#[tokio::test]
async fn managed_deny_read_requirements_follow_thread_permission_updates() -> Result<()> {
skip_if_target_windows!(
Ok(()),
"Windows restricted-token sandbox cannot enforce deny-read policies"
);
let server = start_mock_server().await;
let home = Arc::new(TempDir::new()?);
let denied_root = home.path().join("managed-private");
let nested_root = denied_root.join("nested");
std::fs::create_dir_all(&nested_root)?;
let nested_root = AbsolutePathBuf::from_absolute_path(nested_root)?;
let mut builder = test_codex()
.with_home(home)
.with_cloud_config_bundle(
CloudConfigBundleFixture::loader_with_enterprise_requirement(format!(
"[permissions.filesystem]\ndeny_read = [{denied_root:?}]\n"
)),
)
.with_config(|config| {
config
.permissions
.set_permission_profile(PermissionProfile::workspace_write())
.expect("safe workspace permissions should preserve managed denies");
});
let test = builder.build_with_auto_env(&server).await?;
submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
permission_profile: Some(PermissionProfile::read_only()),
..Default::default()
},
)
.await?;
let snapshot = test.codex.config_snapshot().await;
assert!(
!snapshot
.permission_profile
.file_system_sandbox_policy()
.can_read_local_path_with_cwd(nested_root.as_path(), snapshot.cwd().as_path()),
"a live permission change must preserve managed deny-read rules"
);
let conflicting_profile = PermissionProfile::workspace_write_with(
std::slice::from_ref(&nested_root),
NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ false,
/*exclude_slash_tmp*/ false,
);
let error = test
.codex
.preview_thread_settings_overrides(CodexThreadSettingsOverrides {
permission_profile: Some(conflicting_profile),
..Default::default()
})
.await
.err()
.context("a concrete writable root must not override a managed deny")?;
assert!(error.to_string().contains("permissions.filesystem"));
Ok(())
}
#[tokio::test]
async fn managed_guardian_v1_requirements_disable_guardian_v2() -> Result<()> {
let server = start_mock_server().await;
for (requirements, guardian_v2_enabled) in [
(r#"allowed_approvals_reviewers = ["auto_review"]"#, false),
(
r#"allowed_approvals_reviewers = ["guardian_subagent"]"#,
false,
),
(
r#"allowed_approvals_reviewers = ["auto_review", "user"]"#,
true,
),
("[features]\nguardian_approval = true\n", true),
("[features]\nauto_review = true\n", true),
] {
let mut builder = test_codex()
.with_pre_build_hook(|home| {
std::fs::write(home.join("config.toml"), "[features]\nguardianv2 = true\n")
.expect("Guardian v2 configuration should be written");
})
.with_cloud_config_bundle(
CloudConfigBundleFixture::loader_with_enterprise_requirement(requirements),
);
let test = builder.build_with_auto_env(&server).await?;
assert!(test.config.features.enabled(Feature::GuardianApproval));
assert_eq!(
test.config.features.enabled(Feature::GuardianV2),
guardian_v2_enabled,
"{requirements}"
);
}
Ok(())
}
|