mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Propagate workspace roots to exec-server sandboxes (#32214)
## What changed - Pass configured workspace roots from core to the exec server so filesystem and process sandbox permissions are materialized against the intended roots. - Preserve an explicitly empty workspace-root list instead of treating the sandbox working directory as an implicit root. - Initialize filesystem sandbox contexts with their working directory as the default workspace root. ## Testing - Add end-to-end coverage for patch and command writes inside and outside workspace roots. - Verify remote filesystem and process sandboxes do not grant access through an empty workspace-root list. GitOrigin-RevId: 6684c8f7de50970b45a29e2a6323df954c96f9f6
This commit is contained in:
@@ -13,26 +13,26 @@ use codex_exec_server::ExecOutputStream;
|
||||
use codex_exec_server::ExecParams;
|
||||
use codex_exec_server::ExecProcess;
|
||||
use codex_exec_server::ExecProcessEvent;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::ProcessId;
|
||||
use codex_exec_server::ProcessSignal;
|
||||
use codex_exec_server::ReadResponse;
|
||||
use codex_exec_server::StartedExecProcess;
|
||||
use codex_exec_server::WriteStatus;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -240,6 +240,56 @@ async fn remote_tty_process_uses_configured_sandbox_helper_with_hostile_path() -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_process_preserves_empty_workspace_roots() -> Result<()> {
|
||||
if let Some(warning) = codex_sandboxing::system_bwrap_warning(&PermissionProfile::read_only()) {
|
||||
eprintln!("skipping bwrap test: {warning}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let context = create_process_context(/*use_remote*/ true).await?;
|
||||
let tmp = TempDir::new()?;
|
||||
let file = tmp.path().join("excluded.txt");
|
||||
std::fs::write(&file, b"excluded")?;
|
||||
let cwd = PathUri::from_host_native_path(tmp.path())?;
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]);
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
cwd.clone(),
|
||||
);
|
||||
sandbox.workspace_roots.clear();
|
||||
|
||||
let session = context
|
||||
.backend
|
||||
.start(ExecParams {
|
||||
process_id: ProcessId::from("proc-empty-workspace-roots"),
|
||||
argv: vec!["/bin/cat".to_string(), file.to_string_lossy().into_owned()],
|
||||
cwd,
|
||||
env_policy: None,
|
||||
env: HashMap::new(),
|
||||
tty: false,
|
||||
pipe_stdin: false,
|
||||
arg0: None,
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
let (stdout, _stderr, exit_code, closed) =
|
||||
collect_process_output_from_events(session.process).await?;
|
||||
|
||||
assert!(!stdout.contains("excluded"), "unexpected stdout: {stdout}");
|
||||
assert_ne!(exit_code, Some(0));
|
||||
assert!(closed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_process_until_change(
|
||||
session: Arc<dyn ExecProcess>,
|
||||
wake_rx: &mut watch::Receiver<u64>,
|
||||
|
||||
@@ -263,6 +263,35 @@ async fn remote_read_file_materializes_environment_workspace_roots() -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_read_file_preserves_empty_workspace_roots() -> Result<()> {
|
||||
let context = create_file_system_context(FileSystemImplementation::Remote).await?;
|
||||
let file_system = context.file_system;
|
||||
let tmp = TempDir::new()?;
|
||||
let file = tmp.path().join("excluded.txt");
|
||||
std::fs::write(&file, b"excluded")?;
|
||||
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]);
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
PathUri::from_host_native_path(tmp.path())?,
|
||||
);
|
||||
sandbox.workspace_roots.clear();
|
||||
|
||||
let error = file_system
|
||||
.read_file(&PathUri::from_host_native_path(&file)?, Some(&sandbox))
|
||||
.await
|
||||
.expect_err("empty workspace roots should not grant cwd access");
|
||||
assert_sandbox_denied(&error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(FileSystemImplementation::Local ; "local")]
|
||||
#[test_case(FileSystemImplementation::Remote ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
Reference in New Issue
Block a user