mirror of
https://github.com/openai/codex.git
synced 2026-04-27 16:15:09 +00:00
Run exec-server fs operations through sandbox helper (#17294)
## Summary - run exec-server filesystem RPCs requiring sandboxing through a `codex-fs` arg0 helper over stdin/stdout - keep direct local filesystem execution for `DangerFullAccess` and external sandbox policies - remove the standalone exec-server binary path in favor of top-level arg0 dispatch/runtime paths - add sandbox escape regression coverage for local and remote filesystem paths ## Validation - `just fmt` - `git diff --check` - remote devbox: `cd codex-rs && bazel test --bes_backend= --bes_results_url= //codex-rs/exec-server:all` (6/6 passed) --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -11,7 +11,10 @@ use anyhow::Result;
|
||||
use codex_exec_server::CopyOptions;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::LocalFileSystem;
|
||||
use codex_exec_server::ReadDirectoryEntry;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use codex_protocol::protocol::ReadOnlyAccess;
|
||||
@@ -38,9 +41,15 @@ async fn create_file_system_context(use_remote: bool) -> Result<FileSystemContex
|
||||
_server: Some(server),
|
||||
})
|
||||
} else {
|
||||
let environment = Environment::create(/*exec_server_url*/ None).await?;
|
||||
let codex = codex_utils_cargo_bin::cargo_bin("codex")?;
|
||||
#[cfg(target_os = "linux")]
|
||||
let codex_linux_sandbox_exe =
|
||||
Some(codex_utils_cargo_bin::cargo_bin("codex-linux-sandbox")?);
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let codex_linux_sandbox_exe = None;
|
||||
let runtime_paths = ExecServerRuntimePaths::new(codex, codex_linux_sandbox_exe)?;
|
||||
Ok(FileSystemContext {
|
||||
file_system: environment.get_filesystem(),
|
||||
file_system: Arc::new(LocalFileSystem::with_runtime_paths(runtime_paths)),
|
||||
_server: None,
|
||||
})
|
||||
}
|
||||
@@ -58,18 +67,18 @@ fn absolute_path(path: std::path::PathBuf) -> AbsolutePathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_only_sandbox_policy(readable_root: std::path::PathBuf) -> SandboxPolicy {
|
||||
SandboxPolicy::ReadOnly {
|
||||
fn read_only_sandbox(readable_root: std::path::PathBuf) -> FileSystemSandboxContext {
|
||||
FileSystemSandboxContext::new(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: vec![absolute_path(readable_root)],
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn workspace_write_sandbox_policy(writable_root: std::path::PathBuf) -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
fn workspace_write_sandbox(writable_root: std::path::PathBuf) -> FileSystemSandboxContext {
|
||||
FileSystemSandboxContext::new(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![absolute_path(writable_root)],
|
||||
read_only_access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
@@ -78,6 +87,42 @@ fn workspace_write_sandbox_policy(writable_root: std::path::PathBuf) -> SandboxP
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_sandbox_denied(error: &std::io::Error) {
|
||||
assert!(
|
||||
matches!(
|
||||
error.kind(),
|
||||
std::io::ErrorKind::InvalidInput | std::io::ErrorKind::PermissionDenied
|
||||
),
|
||||
"unexpected sandbox error kind: {error:?}",
|
||||
);
|
||||
let message = error.to_string();
|
||||
assert!(
|
||||
message.contains("is not permitted")
|
||||
|| message.contains("Operation not permitted")
|
||||
|| message.contains("Permission denied"),
|
||||
"unexpected sandbox error message: {message}",
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_normalized_path_rejected(error: &std::io::Error) {
|
||||
match error.kind() {
|
||||
std::io::ErrorKind::NotFound => assert!(
|
||||
error.to_string().contains("No such file or directory"),
|
||||
"unexpected not-found message: {error}",
|
||||
),
|
||||
std::io::ErrorKind::InvalidInput | std::io::ErrorKind::PermissionDenied => {
|
||||
let message = error.to_string();
|
||||
assert!(
|
||||
message.contains("is not permitted")
|
||||
|| message.contains("Operation not permitted")
|
||||
|| message.contains("Permission denied"),
|
||||
"unexpected rejection message: {message}",
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected normalized-path error kind: {other:?}: {error:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +138,7 @@ async fn file_system_get_metadata_returns_expected_fields(use_remote: bool) -> R
|
||||
std::fs::write(&file_path, "hello")?;
|
||||
|
||||
let metadata = file_system
|
||||
.get_metadata(&absolute_path(file_path))
|
||||
.get_metadata(&absolute_path(file_path), /*sandbox*/ None)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
assert_eq!(metadata.is_directory, false);
|
||||
@@ -122,6 +167,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
.create_directory(
|
||||
&absolute_path(nested_dir.clone()),
|
||||
CreateDirectoryOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -130,6 +176,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
.write_file(
|
||||
&absolute_path(nested_file.clone()),
|
||||
b"hello from trait".to_vec(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -137,18 +184,19 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
.write_file(
|
||||
&absolute_path(source_file.clone()),
|
||||
b"hello from source root".to_vec(),
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
|
||||
let nested_file_contents = file_system
|
||||
.read_file(&absolute_path(nested_file.clone()))
|
||||
.read_file(&absolute_path(nested_file.clone()), /*sandbox*/ None)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
assert_eq!(nested_file_contents, b"hello from trait");
|
||||
|
||||
let nested_file_text = file_system
|
||||
.read_file_text(&absolute_path(nested_file.clone()))
|
||||
.read_file_text(&absolute_path(nested_file.clone()), /*sandbox*/ None)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
assert_eq!(nested_file_text, "hello from trait");
|
||||
@@ -158,6 +206,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
&absolute_path(nested_file),
|
||||
&absolute_path(copied_file.clone()),
|
||||
CopyOptions { recursive: false },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -168,6 +217,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
&absolute_path(source_dir.clone()),
|
||||
&absolute_path(copied_dir.clone()),
|
||||
CopyOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -177,7 +227,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
);
|
||||
|
||||
let mut entries = file_system
|
||||
.read_directory(&absolute_path(source_dir))
|
||||
.read_directory(&absolute_path(source_dir), /*sandbox*/ None)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
entries.sort_by(|left, right| left.file_name.cmp(&right.file_name));
|
||||
@@ -204,6 +254,7 @@ async fn file_system_methods_cover_surface_area(use_remote: bool) -> Result<()>
|
||||
recursive: true,
|
||||
force: true,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -228,6 +279,7 @@ async fn file_system_copy_rejects_directory_without_recursive(use_remote: bool)
|
||||
&absolute_path(source_dir),
|
||||
&absolute_path(tmp.path().join("dest")),
|
||||
CopyOptions { recursive: false },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
let error = match error {
|
||||
@@ -246,7 +298,7 @@ async fn file_system_copy_rejects_directory_without_recursive(use_remote: bool)
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_read_with_sandbox_policy_allows_readable_root(use_remote: bool) -> Result<()> {
|
||||
async fn file_system_sandboxed_read_allows_readable_root(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -255,10 +307,10 @@ async fn file_system_read_with_sandbox_policy_allows_readable_root(use_remote: b
|
||||
let file_path = allowed_dir.join("note.txt");
|
||||
std::fs::create_dir_all(&allowed_dir)?;
|
||||
std::fs::write(&file_path, "sandboxed hello")?;
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
|
||||
let contents = file_system
|
||||
.read_file_with_sandbox_policy(&absolute_path(file_path), Some(&sandbox_policy))
|
||||
.read_file(&absolute_path(file_path), Some(&sandbox))
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
assert_eq!(contents, b"sandboxed hello");
|
||||
@@ -269,9 +321,7 @@ async fn file_system_read_with_sandbox_policy_allows_readable_root(use_remote: b
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_write_with_sandbox_policy_rejects_unwritable_path(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_sandboxed_write_rejects_unwritable_path(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -280,26 +330,19 @@ async fn file_system_write_with_sandbox_policy_rejects_unwritable_path(
|
||||
let blocked_path = tmp.path().join("blocked.txt");
|
||||
std::fs::create_dir_all(&allowed_dir)?;
|
||||
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.write_file_with_sandbox_policy(
|
||||
.write_file(
|
||||
&absolute_path(blocked_path.clone()),
|
||||
b"nope".to_vec(),
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("write should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/write is not permitted for path {}",
|
||||
blocked_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert!(!blocked_path.exists());
|
||||
|
||||
Ok(())
|
||||
@@ -308,9 +351,7 @@ async fn file_system_write_with_sandbox_policy_rejects_unwritable_path(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_read_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_sandboxed_read_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -323,25 +364,15 @@ async fn file_system_read_with_sandbox_policy_rejects_symlink_escape(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link").join("secret.txt");
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.read_file_with_sandbox_policy(
|
||||
&absolute_path(requested_path.clone()),
|
||||
Some(&sandbox_policy),
|
||||
)
|
||||
.read_file(&absolute_path(requested_path.clone()), Some(&sandbox))
|
||||
.await
|
||||
{
|
||||
Ok(_) => anyhow::bail!("read should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/read is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -349,7 +380,7 @@ async fn file_system_read_with_sandbox_policy_rejects_symlink_escape(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_read_with_sandbox_policy_rejects_symlink_parent_dotdot_escape(
|
||||
async fn file_system_sandboxed_read_rejects_symlink_parent_dotdot_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
@@ -365,15 +396,17 @@ async fn file_system_read_with_sandbox_policy_rejects_symlink_parent_dotdot_esca
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = absolute_path(allowed_dir.join("link").join("..").join("secret.txt"));
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let error = match file_system
|
||||
.read_file_with_sandbox_policy(&requested_path, Some(&sandbox_policy))
|
||||
.await
|
||||
{
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
let error = match file_system.read_file(&requested_path, Some(&sandbox)).await {
|
||||
Ok(_) => anyhow::bail!("read should fail after path normalization"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
|
||||
// AbsolutePathBuf normalizes `link/../secret.txt` to `allowed/secret.txt`
|
||||
// before the request reaches the filesystem layer. Depending on whether
|
||||
// the platform/runtime resolves that normalized path through a top-level
|
||||
// symlink alias, the request can surface as either "missing file" or an
|
||||
// upfront sandbox rejection.
|
||||
assert_normalized_path_rejected(&error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -381,9 +414,7 @@ async fn file_system_read_with_sandbox_policy_rejects_symlink_parent_dotdot_esca
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_write_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_sandboxed_write_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -395,26 +426,19 @@ async fn file_system_write_with_sandbox_policy_rejects_symlink_escape(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link").join("blocked.txt");
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir);
|
||||
let sandbox = workspace_write_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.write_file_with_sandbox_policy(
|
||||
.write_file(
|
||||
&absolute_path(requested_path.clone()),
|
||||
b"nope".to_vec(),
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("write should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/write is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert!(!outside_dir.join("blocked.txt").exists());
|
||||
|
||||
Ok(())
|
||||
@@ -423,9 +447,7 @@ async fn file_system_write_with_sandbox_policy_rejects_symlink_escape(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_create_directory_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_create_directory_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -437,26 +459,19 @@ async fn file_system_create_directory_with_sandbox_policy_rejects_symlink_escape
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link").join("created");
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir);
|
||||
let sandbox = workspace_write_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.create_directory_with_sandbox_policy(
|
||||
.create_directory(
|
||||
&absolute_path(requested_path.clone()),
|
||||
CreateDirectoryOptions { recursive: false },
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("create_directory should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/write is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert!(!outside_dir.join("created").exists());
|
||||
|
||||
Ok(())
|
||||
@@ -465,9 +480,7 @@ async fn file_system_create_directory_with_sandbox_policy_rejects_symlink_escape
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_get_metadata_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_get_metadata_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -480,25 +493,15 @@ async fn file_system_get_metadata_with_sandbox_policy_rejects_symlink_escape(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link").join("secret.txt");
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.get_metadata_with_sandbox_policy(
|
||||
&absolute_path(requested_path.clone()),
|
||||
Some(&sandbox_policy),
|
||||
)
|
||||
.get_metadata(&absolute_path(requested_path.clone()), Some(&sandbox))
|
||||
.await
|
||||
{
|
||||
Ok(_) => anyhow::bail!("get_metadata should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/read is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -506,9 +509,7 @@ async fn file_system_get_metadata_with_sandbox_policy_rejects_symlink_escape(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_read_directory_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_read_directory_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -521,25 +522,15 @@ async fn file_system_read_directory_with_sandbox_policy_rejects_symlink_escape(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link");
|
||||
let sandbox_policy = read_only_sandbox_policy(allowed_dir);
|
||||
let sandbox = read_only_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.read_directory_with_sandbox_policy(
|
||||
&absolute_path(requested_path.clone()),
|
||||
Some(&sandbox_policy),
|
||||
)
|
||||
.read_directory(&absolute_path(requested_path.clone()), Some(&sandbox))
|
||||
.await
|
||||
{
|
||||
Ok(_) => anyhow::bail!("read_directory should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/read is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -547,9 +538,7 @@ async fn file_system_read_directory_with_sandbox_policy_rejects_symlink_escape(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_copy_with_sandbox_policy_rejects_symlink_escape_destination(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_copy_rejects_symlink_escape_destination(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -562,27 +551,20 @@ async fn file_system_copy_with_sandbox_policy_rejects_symlink_escape_destination
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_destination = allowed_dir.join("link").join("copied.txt");
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir.clone());
|
||||
let sandbox = workspace_write_sandbox(allowed_dir.clone());
|
||||
let error = match file_system
|
||||
.copy_with_sandbox_policy(
|
||||
.copy(
|
||||
&absolute_path(allowed_dir.join("source.txt")),
|
||||
&absolute_path(requested_destination.clone()),
|
||||
CopyOptions { recursive: false },
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("copy should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/write is not permitted for path {}",
|
||||
requested_destination.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert!(!outside_dir.join("copied.txt").exists());
|
||||
|
||||
Ok(())
|
||||
@@ -591,9 +573,7 @@ async fn file_system_copy_with_sandbox_policy_rejects_symlink_escape_destination
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_remove_with_sandbox_policy_removes_symlink_not_target(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_remove_removes_symlink_not_target(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -607,15 +587,15 @@ async fn file_system_remove_with_sandbox_policy_removes_symlink_not_target(
|
||||
let symlink_path = allowed_dir.join("link");
|
||||
symlink(&outside_file, &symlink_path)?;
|
||||
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir);
|
||||
let sandbox = workspace_write_sandbox(allowed_dir);
|
||||
file_system
|
||||
.remove_with_sandbox_policy(
|
||||
.remove(
|
||||
&absolute_path(symlink_path.clone()),
|
||||
RemoveOptions {
|
||||
recursive: false,
|
||||
force: false,
|
||||
},
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -630,9 +610,7 @@ async fn file_system_remove_with_sandbox_policy_removes_symlink_not_target(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_copy_with_sandbox_policy_preserves_symlink_source(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_copy_preserves_symlink_source(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -647,13 +625,13 @@ async fn file_system_copy_with_sandbox_policy_preserves_symlink_source(
|
||||
std::fs::write(&outside_file, "outside")?;
|
||||
symlink(&outside_file, &source_symlink)?;
|
||||
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir.clone());
|
||||
let sandbox = workspace_write_sandbox(allowed_dir.clone());
|
||||
file_system
|
||||
.copy_with_sandbox_policy(
|
||||
.copy(
|
||||
&absolute_path(source_symlink),
|
||||
&absolute_path(copied_symlink.clone()),
|
||||
CopyOptions { recursive: false },
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -668,9 +646,7 @@ async fn file_system_copy_with_sandbox_policy_preserves_symlink_source(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_remove_with_sandbox_policy_rejects_symlink_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_remove_rejects_symlink_escape(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -684,29 +660,22 @@ async fn file_system_remove_with_sandbox_policy_rejects_symlink_escape(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_path = allowed_dir.join("link").join("secret.txt");
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir);
|
||||
let sandbox = workspace_write_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.remove_with_sandbox_policy(
|
||||
.remove(
|
||||
&absolute_path(requested_path.clone()),
|
||||
RemoveOptions {
|
||||
recursive: false,
|
||||
force: false,
|
||||
},
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("remove should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/write is not permitted for path {}",
|
||||
requested_path.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert_eq!(std::fs::read_to_string(outside_file)?, "outside");
|
||||
|
||||
Ok(())
|
||||
@@ -715,9 +684,7 @@ async fn file_system_remove_with_sandbox_policy_rejects_symlink_escape(
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_copy_with_sandbox_policy_rejects_symlink_escape_source(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
async fn file_system_copy_rejects_symlink_escape_source(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
@@ -732,27 +699,20 @@ async fn file_system_copy_with_sandbox_policy_rejects_symlink_escape_source(
|
||||
symlink(&outside_dir, allowed_dir.join("link"))?;
|
||||
|
||||
let requested_source = allowed_dir.join("link").join("secret.txt");
|
||||
let sandbox_policy = workspace_write_sandbox_policy(allowed_dir);
|
||||
let sandbox = workspace_write_sandbox(allowed_dir);
|
||||
let error = match file_system
|
||||
.copy_with_sandbox_policy(
|
||||
.copy(
|
||||
&absolute_path(requested_source.clone()),
|
||||
&absolute_path(requested_destination.clone()),
|
||||
CopyOptions { recursive: false },
|
||||
Some(&sandbox_policy),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => anyhow::bail!("copy should be blocked"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"fs/read is not permitted for path {}",
|
||||
requested_source.display()
|
||||
)
|
||||
);
|
||||
assert_sandbox_denied(&error);
|
||||
assert!(!requested_destination.exists());
|
||||
|
||||
Ok(())
|
||||
@@ -776,6 +736,7 @@ async fn file_system_copy_rejects_copying_directory_into_descendant(
|
||||
&absolute_path(source_dir.clone()),
|
||||
&absolute_path(source_dir.join("nested").join("copy")),
|
||||
CopyOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
let error = match error {
|
||||
@@ -810,6 +771,7 @@ async fn file_system_copy_preserves_symlinks_in_recursive_copy(use_remote: bool)
|
||||
&absolute_path(source_dir),
|
||||
&absolute_path(copied_dir.clone()),
|
||||
CopyOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -855,6 +817,7 @@ async fn file_system_copy_ignores_unknown_special_files_in_recursive_copy(
|
||||
&absolute_path(source_dir),
|
||||
&absolute_path(copied_dir.clone()),
|
||||
CopyOptions { recursive: true },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
@@ -891,6 +854,7 @@ async fn file_system_copy_rejects_standalone_fifo_source(use_remote: bool) -> Re
|
||||
&absolute_path(fifo_path),
|
||||
&absolute_path(tmp.path().join("copied")),
|
||||
CopyOptions { recursive: false },
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await;
|
||||
let error = match error {
|
||||
|
||||
Reference in New Issue
Block a user