Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ codex-exec-server = { workspace = true }
codex-extension-api = { workspace = true }
codex-features = { workspace = true }
codex-feedback = { workspace = true }
codex-file-system = { workspace = true }
codex-login = { workspace = true }
codex-memories-read = { workspace = true }
codex-mcp = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_exec_server::Environment;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::FileSystemSandboxContext;
use codex_extension_api::ExtensionDataInit;
use codex_extension_api::LoadedUserInstructions;
use codex_extension_api::PromptSlot;
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/src/session/turn_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::agents_md::LoadedAgentsMd;
use crate::config::GhostSnapshotConfig;
use crate::environment_selection::ResolvedTurnEnvironments;
use codex_core_skills::HostLoadedSkills;
use codex_file_system::FileSystemSandboxContext;
use codex_model_provider::SharedModelProvider;
use codex_model_provider::create_model_provider;
use codex_protocol::SessionId;
Expand Down Expand Up @@ -341,7 +342,7 @@ impl TurnContext {
network_sandbox_policy,
);
FileSystemSandboxContext {
permissions,
permissions: permissions.into(),
cwd: Some(cwd.clone()),
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/runtimes/apply_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl ApplyPatchRuntime {
let permissions =
effective_permission_profile(attempt.permissions, req.additional_permissions.as_ref());
Some(FileSystemSandboxContext {
permissions,
permissions: permissions.into(),
cwd: Some(attempt.sandbox_cwd.clone()),
windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
Expand Down
7 changes: 6 additions & 1 deletion codex-rs/core/src/tools/runtimes/apply_patch_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,12 @@ async fn file_system_sandbox_context_uses_active_attempt() {
);
let expected_permissions =
PermissionProfile::from_runtime_permissions(&file_system_policy, network_policy);
assert_eq!(sandbox.permissions, expected_permissions);
let native_permissions: PermissionProfile = sandbox
.permissions
.clone()
.try_into()
.expect("native sandbox permissions");
assert_eq!(native_permissions, expected_permissions);
assert_eq!(
sandbox.cwd,
Some(codex_utils_path_uri::PathUri::from_abs_path(&path))
Expand Down
12 changes: 8 additions & 4 deletions codex-rs/exec-server/src/fs_sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ impl FileSystemSandboxRunner {
request: FsHelperRequest,
) -> Result<FsHelperPayload, JSONRPCErrorError> {
let cwd = sandbox_cwd(sandbox)?;
let mut file_system_policy = sandbox.permissions.file_system_sandbox_policy();
let native_permissions: PermissionProfile =
sandbox.permissions.clone().try_into().map_err(|err| {
invalid_request(format!("invalid sandbox permission path URI: {err}"))
})?;
let mut file_system_policy = native_permissions.file_system_sandbox_policy();
let helper_read_roots = if sandbox.use_legacy_landlock {
Vec::new()
} else {
Expand All @@ -80,7 +84,7 @@ impl FileSystemSandboxRunner {
normalize_file_system_policy_root_aliases(&mut file_system_policy);
let network_policy = NetworkSandboxPolicy::Restricted;
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
sandbox.permissions.enforcement(),
native_permissions.enforcement(),
&file_system_policy,
network_policy,
);
Expand Down Expand Up @@ -578,7 +582,7 @@ mod tests {
},
access: FileSystemAccessMode::Write,
}]);
let sandbox_context = crate::FileSystemSandboxContext::from_permission_profile(
let sandbox_context = codex_file_system::FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
);

Expand Down Expand Up @@ -650,7 +654,7 @@ mod tests {
policy: &FileSystemSandboxPolicy,
cwd: PathUri,
) -> crate::FileSystemSandboxContext {
crate::FileSystemSandboxContext::from_permission_profile_with_cwd(
codex_file_system::FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::from_runtime_permissions(policy, NetworkSandboxPolicy::Restricted),
cwd,
)
Expand Down
9 changes: 5 additions & 4 deletions codex-rs/exec-server/src/protocol.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use crate::FileSystemSandboxContext;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_file_system::FileSystemSandboxContext;
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
Expand Down Expand Up @@ -454,7 +454,7 @@ mod base64_bytes {
mod tests {
use super::FsReadFileParams;
use super::HttpRequestParams;
use crate::FileSystemSandboxContext;
use codex_file_system::FileSystemSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
Expand All @@ -465,18 +465,19 @@ mod tests {
.expect("current directory")
.join("legacy-file.txt");
let legacy_cwd = std::env::current_dir().expect("current directory");
let expected_sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
let native_sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::default(),
PathUri::from_path(&legacy_cwd).expect("cwd URI"),
);
let mut legacy_sandbox =
serde_json::to_value(&expected_sandbox).expect("sandbox should serialize");
serde_json::to_value(&native_sandbox).expect("sandbox should serialize");
legacy_sandbox["cwd"] = serde_json::json!(legacy_cwd.to_string_lossy());
let params: FsReadFileParams = serde_json::from_value(serde_json::json!({
"path": legacy_path.to_string_lossy(),
"sandbox": legacy_sandbox,
}))
.expect("legacy absolute path should deserialize");
let expected_sandbox = native_sandbox;
let expected = FsReadFileParams {
path: PathUri::from_path(legacy_path).expect("path URI"),
sandbox: Some(expected_sandbox.clone()),
Expand Down
13 changes: 8 additions & 5 deletions codex-rs/exec-server/tests/file_system/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ fn sandbox_context_from_profile_preserves_workspace_write_read_only_subpaths() -
std::fs::create_dir_all(&git_dir)?;

let sandbox = workspace_write_sandbox(writable_dir.clone());
let policy = sandbox.permissions.file_system_sandbox_policy();
let permissions: PermissionProfile = sandbox.permissions.try_into()?;
let policy = permissions.file_system_sandbox_policy();
let cwd = absolute_path(writable_dir.clone());
let writable_roots = policy.get_writable_roots_with_cwd(cwd.as_path());
let writable_dir = absolute_path(std::fs::canonicalize(writable_dir)?);
Expand Down Expand Up @@ -534,19 +535,21 @@ async fn file_system_sandboxed_write_allows_additional_write_root(
Some(vec![absolute_path(writable_dir)]),
)),
};
let native_permissions: PermissionProfile = sandbox.permissions.clone().try_into()?;
let file_system_policy = effective_file_system_sandbox_policy(
&sandbox.permissions.file_system_sandbox_policy(),
&native_permissions.file_system_sandbox_policy(),
Some(&additional_permissions),
);
let network_policy = effective_network_sandbox_policy(
sandbox.permissions.network_sandbox_policy(),
native_permissions.network_sandbox_policy(),
Some(&additional_permissions),
);
sandbox.permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
sandbox.permissions.enforcement(),
native_permissions.enforcement(),
&file_system_policy,
network_policy,
);
)
.into();

file_system
.write_file(
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/ext/image-generation/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,10 @@ async fn image_url(
environment: &ToolEnvironment,
) -> Result<ImageUrl, FunctionCallError> {
let path_uri = PathUri::from_abs_path(path);
let sandbox = environment.file_system_sandbox_context.clone();
let bytes = environment
.file_system
.read_file(&path_uri, Some(&environment.file_system_sandbox_context))
.read_file(&path_uri, Some(&sandbox))
.await
.map_err(|error| {
FunctionCallError::RespondToModel(format!(
Expand Down
1 change: 1 addition & 0 deletions codex-rs/file-system/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ workspace = true

[dependencies]
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-path-uri = { workspace = true }
serde = { workspace = true, features = ["derive"] }

Expand Down
72 changes: 44 additions & 28 deletions codex-rs/file-system/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::permissions::FileSystemPath;
Expand All @@ -7,6 +8,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use std::future::Future;
use std::io;
Expand Down Expand Up @@ -50,7 +52,7 @@ pub struct ReadDirectoryEntry {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileSystemSandboxContext {
pub permissions: PermissionProfile,
pub permissions: PermissionProfile<PathUri>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathUri>,
pub windows_sandbox_level: WindowsSandboxLevel,
Expand All @@ -73,25 +75,32 @@ impl FileSystemSandboxContext {
&sandbox_policy,
&native_cwd,
);
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
let permissions =
PermissionProfile::<AbsolutePathBuf>::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
Ok(Self::from_permission_profile_with_cwd(permissions, cwd))
}

pub fn from_permission_profile(permissions: PermissionProfile) -> Self {
pub fn from_permission_profile(permissions: PermissionProfile<AbsolutePathBuf>) -> Self {
Self::from_permissions_and_cwd(permissions, /*cwd*/ None)
}

pub fn from_permission_profile_with_cwd(permissions: PermissionProfile, cwd: PathUri) -> Self {
pub fn from_permission_profile_with_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: PathUri,
) -> Self {
Self::from_permissions_and_cwd(permissions, Some(cwd))
}

fn from_permissions_and_cwd(permissions: PermissionProfile, cwd: Option<PathUri>) -> Self {
fn from_permissions_and_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: Option<PathUri>,
) -> Self {
Self {
permissions,
permissions: permissions.into(),
cwd,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
Expand All @@ -100,14 +109,36 @@ impl FileSystemSandboxContext {
}

pub fn should_run_in_sandbox(&self) -> bool {
let file_system_policy = self.permissions.file_system_sandbox_policy();
let Ok(permissions) =
PermissionProfile::<AbsolutePathBuf>::try_from(self.permissions.clone())
else {
// A sandbox context for another host must not select the unsandboxed filesystem.
return true;
};
let file_system_policy = permissions.file_system_sandbox_policy();
matches!(file_system_policy.kind, FileSystemSandboxKind::Restricted)
&& !file_system_policy.has_full_disk_write_access()
}

pub fn has_cwd_dependent_permissions(&self) -> bool {
let file_system_policy = self.permissions.file_system_sandbox_policy();
file_system_policy_has_cwd_dependent_entries(&file_system_policy)
match &self.permissions {
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted { entries, .. },
..
} => entries.iter().any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
}),
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Unrestricted,
..
}
| PermissionProfile::Disabled
| PermissionProfile::External { .. } => false,
}
}

pub fn drop_cwd_if_unused(mut self) -> Self {
Expand All @@ -118,21 +149,6 @@ impl FileSystemSandboxContext {
}
}

fn file_system_policy_has_cwd_dependent_entries(
file_system_policy: &FileSystemSandboxPolicy,
) -> bool {
file_system_policy
.entries
.iter()
.any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
})
}

pub type FileSystemResult<T> = io::Result<T>;

/// Future returned by [`ExecutorFileSystem`] operations.
Expand Down
Loading
Loading