mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Test workspace-root patch protection for metadata (#36171)
## What changed Add integration coverage for `apply_patch` with unified exec workspace roots. The test verifies that a normal file can be added while writes under existing `.git`, `.agents`, and `.codex` directories are rejected and do not create files. GitOrigin-RevId: 21ad5ce7fb06e7c47b4086c1439f12d52d9235de
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -187,6 +188,96 @@ async fn workspace_roots_allow_file_and_command_writes() -> Result<()> {
|
||||
remove_files(&test, &[&patch_path, &command_path]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn workspace_roots_allow_patches_but_protect_metadata_directories() -> Result<()> {
|
||||
const PATCH_CONTENTS: &str = "workspace root patch access";
|
||||
const PROTECTED_METADATA_DIRECTORIES: [&str; 3] = [".git", ".agents", ".codex"];
|
||||
|
||||
skip_if_wine_exec!(
|
||||
Ok(()),
|
||||
"Wine does not emulate Windows restricted-token and ACL sandbox semantics"
|
||||
);
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let test = workspace_roots_test(&server).await?;
|
||||
let cwd = PathUri::from_abs_path(&test.config.cwd);
|
||||
let allowed_path = cwd.join("workspace-root-allowed-patch.txt")?;
|
||||
for directory in PROTECTED_METADATA_DIRECTORIES {
|
||||
test.fs()
|
||||
.create_directory(
|
||||
&cwd.join(directory)?,
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let allowed_patch = format!(
|
||||
"*** Begin Patch\n*** Add File: workspace-root-allowed-patch.txt\n+{PATCH_CONTENTS}\n*** End Patch\n"
|
||||
);
|
||||
let mut patch_calls = vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_apply_patch_custom_tool_call(PATCH_CALL_ID, &allowed_patch),
|
||||
];
|
||||
for directory in PROTECTED_METADATA_DIRECTORIES {
|
||||
let call_id = format!("workspace-root-protected-{directory}");
|
||||
let patch = format!(
|
||||
"*** Begin Patch\n*** Add File: {directory}/protected.txt\n+metadata write\n*** End Patch\n"
|
||||
);
|
||||
patch_calls.push(ev_apply_patch_custom_tool_call(&call_id, &patch));
|
||||
}
|
||||
patch_calls.push(ev_completed("resp-1"));
|
||||
let response_mock = mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
sse(patch_calls),
|
||||
sse(vec![
|
||||
ev_response_created("resp-2"),
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-2"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
submit_workspace_turn(&test, "patch the workspace and protected metadata").await?;
|
||||
|
||||
let request = response_mock
|
||||
.last_request()
|
||||
.context("model should receive every workspace-root patch result")?;
|
||||
let (_, patch_success) = request
|
||||
.custom_tool_call_output_content_and_success(PATCH_CALL_ID)
|
||||
.context("workspace patch result should be present")?;
|
||||
assert_ne!(patch_success, Some(false));
|
||||
assert_eq!(
|
||||
read_file(&test, &allowed_path).await?,
|
||||
format!("{PATCH_CONTENTS}\n")
|
||||
);
|
||||
|
||||
for directory in PROTECTED_METADATA_DIRECTORIES {
|
||||
let call_id = format!("workspace-root-protected-{directory}");
|
||||
let (output, success) = request
|
||||
.custom_tool_call_output_content_and_success(&call_id)
|
||||
.with_context(|| format!("{directory} patch result should be present"))?;
|
||||
assert_ne!(success, Some(true));
|
||||
assert!(
|
||||
output
|
||||
.as_deref()
|
||||
.is_some_and(|output| output.contains("outside of the project")),
|
||||
"{directory} patch should be denied, got {output:?}"
|
||||
);
|
||||
let protected_path = cwd.join(directory)?.join("protected.txt")?;
|
||||
let error = test
|
||||
.fs()
|
||||
.get_metadata(&protected_path, /*sandbox*/ None)
|
||||
.await
|
||||
.expect_err("protected metadata file should not be created");
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
|
||||
}
|
||||
|
||||
remove_files(&test, &[&allowed_path]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn workspace_roots_deny_file_and_command_writes_outside_roots() -> Result<()> {
|
||||
const PATCH_CONTENTS: &str = "outside workspace root patch";
|
||||
|
||||
Reference in New Issue
Block a user