mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Use ApiPathString in app-server filesystem permission paths (#28367)
## Why Clients running an app-server on one OS and an exec-server on another OS need to be able to pass sandbox config to app-server that refers to resources on the executor's foreign OS. ## What `AbsolutePathBuf` can't represent these paths and we don't want users to be exposed to `PathUri` yet, so this moves the public app-server API to be expressed in terms of `ApiPathString`. Stacked on #28165. - change app-server v2 filesystem permission paths, including legacy read/write roots, to `ApiPathString` - localize API paths through `PathUri` when converting into the current native core permission types - make path-bearing permission conversions fallible and surface localization failures instead of silently treating malformed grants as ordinary denials - propagate conversion failures through app-server and TUI approval handling - regenerate the app-server JSON and TypeScript schemas - leave migration TODOs on native-path conversions so they can be removed once core permission paths use `PathUri`
This commit is contained in:
@@ -35,6 +35,7 @@ use codex_protocol::user_input::UserInput as CoreUserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
@@ -379,8 +380,8 @@ fn external_agent_config_import_params_accept_legacy_plugin_details() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_request_approval_rejects_relative_additional_permission_paths() {
|
||||
let err = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
fn command_execution_request_approval_localization_rejects_relative_additional_permission_paths() {
|
||||
let params = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
"turnId": "turn_123",
|
||||
"itemId": "call_123",
|
||||
@@ -401,12 +402,14 @@ fn command_execution_request_approval_rejects_relative_additional_permission_pat
|
||||
"proposedNetworkPolicyAmendments": null,
|
||||
"availableDecisions": null
|
||||
}))
|
||||
.expect_err("relative additional permission paths should fail");
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("AbsolutePathBuf deserialized without a base path"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
.expect("API paths should deserialize before localization");
|
||||
let additional_permissions = params
|
||||
.additional_permissions
|
||||
.expect("additional permissions should be present");
|
||||
|
||||
let err = CoreAdditionalPermissionProfile::try_from(additional_permissions)
|
||||
.expect_err("relative additional permission paths should fail localization");
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -451,12 +454,12 @@ fn permissions_request_approval_uses_request_permission_profile() {
|
||||
}),
|
||||
file_system: Some(AdditionalFileSystemPermissions {
|
||||
read: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_only_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_only_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
write: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_write_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_write_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: None,
|
||||
@@ -465,7 +468,8 @@ fn permissions_request_approval_uses_request_permission_profile() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoreRequestPermissionProfile::from(params.permissions),
|
||||
CoreRequestPermissionProfile::try_from(params.permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
CoreRequestPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
@@ -559,7 +563,8 @@ fn additional_file_system_permissions_preserves_canonical_entries() {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
CoreFileSystemPermissions::from(permissions),
|
||||
CoreFileSystemPermissions::try_from(permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
core_permissions
|
||||
);
|
||||
}
|
||||
@@ -574,23 +579,25 @@ fn additional_file_system_permissions_populates_entries_for_legacy_roots() {
|
||||
);
|
||||
|
||||
let permissions = AdditionalFileSystemPermissions::from(core_permissions.clone());
|
||||
let read_only_api_path = ApiPathString::from_abs_path(&read_only_path);
|
||||
let read_write_api_path = ApiPathString::from_abs_path(&read_write_path);
|
||||
|
||||
assert_eq!(
|
||||
permissions,
|
||||
AdditionalFileSystemPermissions {
|
||||
read: Some(vec![read_only_path.clone()]),
|
||||
write: Some(vec![read_write_path.clone()]),
|
||||
read: Some(vec![read_only_api_path.clone()]),
|
||||
write: Some(vec![read_write_api_path.clone()]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: Some(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: read_only_path,
|
||||
path: read_only_api_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: read_write_path,
|
||||
path: read_write_api_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -598,7 +605,8 @@ fn additional_file_system_permissions_populates_entries_for_legacy_roots() {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
CoreFileSystemPermissions::from(permissions),
|
||||
CoreFileSystemPermissions::try_from(permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
core_permissions
|
||||
);
|
||||
}
|
||||
@@ -667,12 +675,12 @@ fn permissions_request_approval_response_uses_granted_permission_profile_without
|
||||
}),
|
||||
file_system: Some(AdditionalFileSystemPermissions {
|
||||
read: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_only_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_only_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
write: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_write_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_write_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: None,
|
||||
@@ -681,7 +689,8 @@ fn permissions_request_approval_response_uses_granted_permission_profile_without
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoreAdditionalPermissionProfile::from(response.permissions),
|
||||
CoreAdditionalPermissionProfile::try_from(response.permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
CoreAdditionalPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
|
||||
Reference in New Issue
Block a user